diff --git a/samples/sqlservices/src/controllers/modelViewDashboard.ts b/samples/sqlservices/src/controllers/modelViewDashboard.ts index 0b17bc1fe9..d57b93ab93 100644 --- a/samples/sqlservices/src/controllers/modelViewDashboard.ts +++ b/samples/sqlservices/src/controllers/modelViewDashboard.ts @@ -11,37 +11,33 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): dashboard.registerTabs(async (view: azdata.ModelView) => { // Tab with toolbar const button = view.modelBuilder.button().withProperties({ - label: 'Run', + label: 'Add databases tab', iconPath: { light: context.asAbsolutePath('images/compare.svg'), dark: context.asAbsolutePath('images/compare-inverse.svg') } }).component(); - button.onDidClick(() => { - vscode.window.showInformationMessage('Run button clicked'); - }); - const toolbar = view.modelBuilder.toolbarContainer().withItems([button]).withLayout({ orientation: azdata.Orientation.Horizontal }).component(); - const textComponent1 = view.modelBuilder.text().withProperties({ value: 'text 1' }).component(); + const input1 = view.modelBuilder.inputBox().withProperties({ value: 'input 1' }).component(); const homeTab: azdata.DashboardTab = { id: 'home', toolbar: toolbar, - content: textComponent1, + content: input1, title: 'Home', icon: context.asAbsolutePath('images/home.svg') // icon can be the path of a svg file }; // Tab with nested tabbed Panel - const textComponent2 = view.modelBuilder.text().withProperties({ value: 'text 2' }).component(); - const textComponent3 = view.modelBuilder.text().withProperties({ value: 'text 3' }).component(); - + const addTabButton = view.modelBuilder.button().withProperties({ label: 'Add a tab', width: '150px' }).component(); + const removeTabButton = view.modelBuilder.button().withProperties({ label: 'Remove a tab', width: '150px' }).component(); + const container = view.modelBuilder.flexContainer().withItems([addTabButton, removeTabButton]).withLayout({ flexFlow: 'column' }).component(); const nestedTab1 = { title: 'Tab1', - content: textComponent2, + content: container, id: 'tab1', icon: { light: context.asAbsolutePath('images/user.svg'), @@ -49,9 +45,10 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): } }; + const input2 = view.modelBuilder.inputBox().withProperties({ value: 'input 2' }).component(); const nestedTab2 = { title: 'Tab2', - content: textComponent3, + content: input2, icon: { light: context.asAbsolutePath('images/group.svg'), dark: context.asAbsolutePath('images/group_inverse.svg') @@ -59,6 +56,13 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): id: 'tab2' }; + const input3 = view.modelBuilder.inputBox().withProperties({ value: 'input 4' }).component(); + const nestedTab3 = { + title: 'Tab3', + content: input3, + id: 'tab3' + }; + const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs([ nestedTab1, nestedTab2 ]).withLayout({ @@ -66,6 +70,15 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): showIcon: true }).component(); + + addTabButton.onDidClick(() => { + tabbedPanel.updateTabs([nestedTab1, nestedTab2, nestedTab3]); + }); + + removeTabButton.onDidClick(() => { + tabbedPanel.updateTabs([nestedTab1, nestedTab3]); + }); + const settingsTab: azdata.DashboardTab = { id: 'settings', content: tabbedPanel, @@ -81,10 +94,23 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext): ] }; + // Databases tab + const databasesText = view.modelBuilder.inputBox().withProperties({ value: 'This is databases tab', width: '200px' }).component(); + const databasesTab: azdata.DashboardTab = { + id: 'databases', + content: databasesText, + title: 'Databases', + icon: context.asAbsolutePath('images/default.svg') + }; + button.onDidClick(() => { + dashboard.updateTabs([homeTab, databasesTab, securityTabGroup]); + }); + return [ homeTab, securityTabGroup ]; }); - dashboard.open(); + await dashboard.open(); } + diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index b8bc105470..dd41fa4bdf 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -210,6 +210,12 @@ declare module 'azdata' { * The event argument is the id of the selected tab. */ onTabChanged: vscode.Event; + + /** + * update the tabs. + * @param tabs new tabs + */ + updateTabs(tabs: (Tab | TabGroup)[]): void; } /** @@ -310,6 +316,7 @@ declare module 'azdata' { export interface ModelViewDashboard { registerTabs(handler: (view: ModelView) => Thenable<(DashboardTab | DashboardTabGroup)[]>): void; open(): Thenable; + updateTabs(tabs: (DashboardTab | DashboardTabGroup)[]): void; } export function createModelViewDashboard(title: string, options?: ModelViewDashboardOptions): ModelViewDashboard; diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 1787aef328..ebde4907a6 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -495,29 +495,33 @@ class ToolbarContainerBuilder extends GenericContainerBuilder implements azdata.TabbedPanelComponentBuilder { withTabs(items: (azdata.Tab | azdata.TabGroup)[]): azdata.ContainerBuilder { - const itemConfigs = []; - items.forEach(item => { - if (item && 'tabs' in item) { - item.tabs.forEach(tab => { - itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon)); - }); - } else { - const tab = item; - itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon)); - } - }); - this._component.itemConfigs = itemConfigs; + this._component.itemConfigs = createFromTabs(items); return this; } +} - toItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig { - return new InternalItemConfig(content as ComponentWrapper, { - title: title, - group: group, - id: id, - icon: icon - }); - } +function createFromTabs(items: (azdata.Tab | azdata.TabGroup)[]): InternalItemConfig[] { + const itemConfigs = []; + items.forEach(item => { + if (item && 'tabs' in item) { + item.tabs.forEach(tab => { + itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon)); + }); + } else { + const tab = item; + itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon)); + } + }); + return itemConfigs; +} + +function toTabItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig { + return new InternalItemConfig(content as ComponentWrapper, { + title: title, + group: group, + id: id, + icon: icon + }); } class LoadingComponentBuilder extends ComponentBuilderImpl implements azdata.LoadingComponentBuilder { @@ -1728,6 +1732,13 @@ class TabbedPanelComponentWrapper extends ComponentWrapper implements azdata.Tab this.properties = {}; this._emitterMap.set(ComponentEventType.onDidChange, new Emitter()); } + updateTabs(tabs: (azdata.Tab | azdata.TabGroup)[]): void { + this.clearItems(); + const itemConfigs = createFromTabs(tabs); + itemConfigs.forEach(itemConfig => { + this.addItem(itemConfig.component, itemConfig.config); + }); + } public get onTabChanged(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidChange); diff --git a/src/sql/workbench/api/common/extHostModelViewDialog.ts b/src/sql/workbench/api/common/extHostModelViewDialog.ts index 90db838a59..221bfefcfe 100644 --- a/src/sql/workbench/api/common/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/common/extHostModelViewDialog.ts @@ -458,34 +458,34 @@ class WizardImpl implements azdata.window.Wizard { } class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard { + private _tabbedPanel: azdata.TabbedPanelComponent; + private _view: azdata.ModelView; + constructor( private _editor: ModelViewEditorImpl, private _options?: azdata.ModelViewDashboardOptions ) { } + + updateTabs(tabs: (azdata.DashboardTab | azdata.DashboardTabGroup)[]): void { + if (this._tabbedPanel === undefined || this._view === undefined) { + throw new Error(nls.localize('dashboardNotInitialized', "Tabs are not initialized")); + } + + this._tabbedPanel.updateTabs(this.createTabs(tabs, this._view)); + } + registerTabs(handler: (view: azdata.ModelView) => Thenable<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>): void { this._editor.registerContent(async (view) => { + this._view = view; const dashboardTabs = await handler(view); - const tabs: (azdata.TabGroup | azdata.Tab)[] = []; - dashboardTabs.forEach((item: azdata.DashboardTab | azdata.DashboardTabGroup) => { - if ('tabs' in item) { - tabs.push({ - title: item.title, - tabs: item.tabs.map(tab => { - return this.createTab(tab, view); - }) - }); - } else { - tabs.push(this.createTab(item, view)); - } - }); - - const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs(tabs).withLayout({ + const tabs = this.createTabs(dashboardTabs, view); + this._tabbedPanel = view.modelBuilder.tabbedPanel().withTabs(tabs).withLayout({ orientation: 'vertical', showIcon: this._options?.showIcon ?? true, alwaysShowTabs: this._options?.alwaysShowTabs ?? false }).component(); - return view.initializeModel(tabbedPanel); + return view.initializeModel(this._tabbedPanel); }); } @@ -508,6 +508,23 @@ class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard { return tab; } } + + createTabs(dashboardTabs: (azdata.DashboardTab | azdata.DashboardTabGroup)[], view: azdata.ModelView): (azdata.TabGroup | azdata.Tab)[] { + const tabs: (azdata.TabGroup | azdata.Tab)[] = []; + dashboardTabs.forEach((item: azdata.DashboardTab | azdata.DashboardTabGroup) => { + if ('tabs' in item) { + tabs.push({ + title: item.title, + tabs: item.tabs.map(tab => { + return this.createTab(tab, view); + }) + }); + } else { + tabs.push(this.createTab(item, view)); + } + }); + return tabs; + } } export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index ec5ea4aeba..c46297ef47 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -352,6 +352,7 @@ export abstract class ContainerBase extends ComponentBase { public clearContainer(): void { this.items = []; + this.onItemsUpdated(); this._changeRef.detectChanges(); } diff --git a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts index feff21e625..268e94014e 100644 --- a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts +++ b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts @@ -99,7 +99,12 @@ export default class TabbedPanelComponent extends ContainerBase imple } onItemsUpdated(): void { - const firstTabIndex = this.tabs.findIndex(tab => tab.type === 'tab'); + if (this.items.length === 0) { + this._itemIndexToProcess = 0; + this._tabs = []; + } + + const firstTabIndex = this._tabs.findIndex(tab => tab.type === 'tab'); if (firstTabIndex >= 0) { this._panel.selectTab(firstTabIndex); }