1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import {Disposable} from '../base/disposable'; 16import {DetailsPanel, LegacyDetailsPanel, TabDescriptor} from '../public'; 17 18export interface ResolvedTab { 19 uri: string; 20 tab?: TabDescriptor; 21} 22 23/** 24 * Stores tab & current selection section registries. 25 * Keeps track of tab lifecycles. 26 */ 27export class TabManager implements Disposable { 28 private _registry = new Map<string, TabDescriptor>(); 29 private _defaultTabs = new Set<string>(); 30 private _legacyDetailsPanelRegistry = new Set<LegacyDetailsPanel>(); 31 private _detailsPanelRegistry = new Set<DetailsPanel>(); 32 private _currentTabs = new Map<string, TabDescriptor>(); 33 34 dispose(): void { 35 // Dispose of all tabs that are currently alive 36 for (const tab of this._currentTabs.values()) { 37 this.disposeTab(tab); 38 } 39 this._currentTabs.clear(); 40 } 41 42 registerTab(desc: TabDescriptor): Disposable { 43 this._registry.set(desc.uri, desc); 44 return { 45 dispose: () => this._registry.delete(desc.uri), 46 }; 47 } 48 49 addDefaultTab(uri: string): Disposable { 50 this._defaultTabs.add(uri); 51 return { 52 dispose: () => this._defaultTabs.delete(uri), 53 }; 54 } 55 56 registerLegacyDetailsPanel(section: LegacyDetailsPanel): Disposable { 57 this._legacyDetailsPanelRegistry.add(section); 58 return { 59 dispose: () => this._legacyDetailsPanelRegistry.delete(section), 60 }; 61 } 62 63 registerDetailsPanel(section: DetailsPanel): Disposable { 64 this._detailsPanelRegistry.add(section); 65 return { 66 dispose: () => this._detailsPanelRegistry.delete(section), 67 }; 68 } 69 70 resolveTab(uri: string): TabDescriptor | undefined { 71 return this._registry.get(uri); 72 } 73 74 get tabs(): TabDescriptor[] { 75 return Array.from(this._registry.values()); 76 } 77 78 get defaultTabs(): string[] { 79 return Array.from(this._defaultTabs); 80 } 81 82 get legacyDetailsPanels(): LegacyDetailsPanel[] { 83 return Array.from(this._legacyDetailsPanelRegistry); 84 } 85 86 get detailsPanels(): DetailsPanel[] { 87 return Array.from(this._detailsPanelRegistry); 88 } 89 90 /** 91 * Resolves a list of URIs to tabs and manages tab lifecycles. 92 * @param tabUris List of tabs. 93 * @return List of resolved tabs. 94 */ 95 resolveTabs(tabUris: string[]): ResolvedTab[] { 96 // Refresh the list of old tabs 97 const newTabs = new Map<string, TabDescriptor>(); 98 const tabs: ResolvedTab[] = []; 99 100 tabUris.forEach((uri) => { 101 const newTab = this._registry.get(uri); 102 tabs.push({uri, tab: newTab}); 103 104 if (newTab) { 105 newTabs.set(uri, newTab); 106 } 107 }); 108 109 // Call onShow() on any new tabs. 110 for (const [uri, tab] of newTabs) { 111 const oldTab = this._currentTabs.get(uri); 112 if (!oldTab) { 113 this.initTab(tab); 114 } 115 } 116 117 // Call onHide() on any tabs that have been removed. 118 for (const [uri, tab] of this._currentTabs) { 119 const newTab = newTabs.get(uri); 120 if (!newTab) { 121 this.disposeTab(tab); 122 } 123 } 124 125 this._currentTabs = newTabs; 126 127 return tabs; 128 } 129 130 /** 131 * Call onShow() on this tab. 132 * @param tab The tab to initialize. 133 */ 134 private initTab(tab: TabDescriptor): void { 135 tab.onShow?.(); 136 } 137 138 /** 139 * Call onHide() and maybe remove from registry if tab is ephemeral. 140 * @param tab The tab to dispose. 141 */ 142 private disposeTab(tab: TabDescriptor): void { 143 // Attempt to call onHide 144 tab.onHide?.(); 145 146 // If ephemeral, also unregister the tab 147 if (tab.isEphemeral) { 148 this._registry.delete(tab.uri); 149 } 150 } 151} 152