add ability to dynamically update tabs (#9911)

* add dashboard and tabbedPanel samples

* add updateTabs to tabbedPanel component

* add updateTabs to tabbedPanel component
This commit is contained in:
Alan Ren
2020-04-09 12:02:00 -07:00
committed by GitHub
parent 82f21faf79
commit 23f1a08aa0
6 changed files with 117 additions and 50 deletions

View File

@@ -11,37 +11,33 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext):
dashboard.registerTabs(async (view: azdata.ModelView) => { dashboard.registerTabs(async (view: azdata.ModelView) => {
// Tab with toolbar // Tab with toolbar
const button = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ const button = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({
label: 'Run', label: 'Add databases tab',
iconPath: { iconPath: {
light: context.asAbsolutePath('images/compare.svg'), light: context.asAbsolutePath('images/compare.svg'),
dark: context.asAbsolutePath('images/compare-inverse.svg') dark: context.asAbsolutePath('images/compare-inverse.svg')
} }
}).component(); }).component();
button.onDidClick(() => {
vscode.window.showInformationMessage('Run button clicked');
});
const toolbar = view.modelBuilder.toolbarContainer().withItems([button]).withLayout({ const toolbar = view.modelBuilder.toolbarContainer().withItems([button]).withLayout({
orientation: azdata.Orientation.Horizontal orientation: azdata.Orientation.Horizontal
}).component(); }).component();
const textComponent1 = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: 'text 1' }).component(); const input1 = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ value: 'input 1' }).component();
const homeTab: azdata.DashboardTab = { const homeTab: azdata.DashboardTab = {
id: 'home', id: 'home',
toolbar: toolbar, toolbar: toolbar,
content: textComponent1, content: input1,
title: 'Home', title: 'Home',
icon: context.asAbsolutePath('images/home.svg') // icon can be the path of a svg file icon: context.asAbsolutePath('images/home.svg') // icon can be the path of a svg file
}; };
// Tab with nested tabbed Panel // Tab with nested tabbed Panel
const textComponent2 = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: 'text 2' }).component(); const addTabButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ label: 'Add a tab', width: '150px' }).component();
const textComponent3 = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: 'text 3' }).component(); const removeTabButton = view.modelBuilder.button().withProperties<azdata.ButtonProperties>({ label: 'Remove a tab', width: '150px' }).component();
const container = view.modelBuilder.flexContainer().withItems([addTabButton, removeTabButton]).withLayout({ flexFlow: 'column' }).component();
const nestedTab1 = { const nestedTab1 = {
title: 'Tab1', title: 'Tab1',
content: textComponent2, content: container,
id: 'tab1', id: 'tab1',
icon: { icon: {
light: context.asAbsolutePath('images/user.svg'), light: context.asAbsolutePath('images/user.svg'),
@@ -49,9 +45,10 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext):
} }
}; };
const input2 = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ value: 'input 2' }).component();
const nestedTab2 = { const nestedTab2 = {
title: 'Tab2', title: 'Tab2',
content: textComponent3, content: input2,
icon: { icon: {
light: context.asAbsolutePath('images/group.svg'), light: context.asAbsolutePath('images/group.svg'),
dark: context.asAbsolutePath('images/group_inverse.svg') dark: context.asAbsolutePath('images/group_inverse.svg')
@@ -59,6 +56,13 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext):
id: 'tab2' id: 'tab2'
}; };
const input3 = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ value: 'input 4' }).component();
const nestedTab3 = {
title: 'Tab3',
content: input3,
id: 'tab3'
};
const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs([ const tabbedPanel = view.modelBuilder.tabbedPanel().withTabs([
nestedTab1, nestedTab2 nestedTab1, nestedTab2
]).withLayout({ ]).withLayout({
@@ -66,6 +70,15 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext):
showIcon: true showIcon: true
}).component(); }).component();
addTabButton.onDidClick(() => {
tabbedPanel.updateTabs([nestedTab1, nestedTab2, nestedTab3]);
});
removeTabButton.onDidClick(() => {
tabbedPanel.updateTabs([nestedTab1, nestedTab3]);
});
const settingsTab: azdata.DashboardTab = { const settingsTab: azdata.DashboardTab = {
id: 'settings', id: 'settings',
content: tabbedPanel, content: tabbedPanel,
@@ -81,10 +94,23 @@ export async function openModelViewDashboard(context: vscode.ExtensionContext):
] ]
}; };
// Databases tab
const databasesText = view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({ 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 [ return [
homeTab, homeTab,
securityTabGroup securityTabGroup
]; ];
}); });
dashboard.open(); await dashboard.open();
} }

View File

@@ -210,6 +210,12 @@ declare module 'azdata' {
* The event argument is the id of the selected tab. * The event argument is the id of the selected tab.
*/ */
onTabChanged: vscode.Event<string>; onTabChanged: vscode.Event<string>;
/**
* update the tabs.
* @param tabs new tabs
*/
updateTabs(tabs: (Tab | TabGroup)[]): void;
} }
/** /**
@@ -310,6 +316,7 @@ declare module 'azdata' {
export interface ModelViewDashboard { export interface ModelViewDashboard {
registerTabs(handler: (view: ModelView) => Thenable<(DashboardTab | DashboardTabGroup)[]>): void; registerTabs(handler: (view: ModelView) => Thenable<(DashboardTab | DashboardTabGroup)[]>): void;
open(): Thenable<void>; open(): Thenable<void>;
updateTabs(tabs: (DashboardTab | DashboardTabGroup)[]): void;
} }
export function createModelViewDashboard(title: string, options?: ModelViewDashboardOptions): ModelViewDashboard; export function createModelViewDashboard(title: string, options?: ModelViewDashboardOptions): ModelViewDashboard;

View File

@@ -495,29 +495,33 @@ class ToolbarContainerBuilder extends GenericContainerBuilder<azdata.ToolbarCont
class TabbedPanelComponentBuilder extends ContainerBuilderImpl<azdata.TabbedPanelComponent, azdata.TabbedPanelLayout, any> implements azdata.TabbedPanelComponentBuilder { class TabbedPanelComponentBuilder extends ContainerBuilderImpl<azdata.TabbedPanelComponent, azdata.TabbedPanelLayout, any> implements azdata.TabbedPanelComponentBuilder {
withTabs(items: (azdata.Tab | azdata.TabGroup)[]): azdata.ContainerBuilder<azdata.TabbedPanelComponent, azdata.TabbedPanelLayout, any> { withTabs(items: (azdata.Tab | azdata.TabGroup)[]): azdata.ContainerBuilder<azdata.TabbedPanelComponent, azdata.TabbedPanelLayout, any> {
const itemConfigs = []; this._component.itemConfigs = createFromTabs(items);
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 = <azdata.Tab>item;
itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon));
}
});
this._component.itemConfigs = itemConfigs;
return this; return this;
} }
}
toItemConfig(content: azdata.Component, title: string, id?: string, group?: string, icon?: string | URI | { light: string | URI; dark: string | URI }): InternalItemConfig { function createFromTabs(items: (azdata.Tab | azdata.TabGroup)[]): InternalItemConfig[] {
return new InternalItemConfig(content as ComponentWrapper, { const itemConfigs = [];
title: title, items.forEach(item => {
group: group, if (item && 'tabs' in item) {
id: id, item.tabs.forEach(tab => {
icon: icon itemConfigs.push(toTabItemConfig(tab.content, tab.title, tab.id, item.title, tab.icon));
}); });
} } else {
const tab = <azdata.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<azdata.LoadingComponent> implements azdata.LoadingComponentBuilder { class LoadingComponentBuilder extends ComponentBuilderImpl<azdata.LoadingComponent> implements azdata.LoadingComponentBuilder {
@@ -1728,6 +1732,13 @@ class TabbedPanelComponentWrapper extends ComponentWrapper implements azdata.Tab
this.properties = {}; this.properties = {};
this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<string>()); this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<string>());
} }
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<string> { public get onTabChanged(): vscode.Event<string> {
let emitter = this._emitterMap.get(ComponentEventType.onDidChange); let emitter = this._emitterMap.get(ComponentEventType.onDidChange);

View File

@@ -458,34 +458,34 @@ class WizardImpl implements azdata.window.Wizard {
} }
class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard { class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard {
private _tabbedPanel: azdata.TabbedPanelComponent;
private _view: azdata.ModelView;
constructor( constructor(
private _editor: ModelViewEditorImpl, private _editor: ModelViewEditorImpl,
private _options?: azdata.ModelViewDashboardOptions 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 { registerTabs(handler: (view: azdata.ModelView) => Thenable<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>): void {
this._editor.registerContent(async (view) => { this._editor.registerContent(async (view) => {
this._view = view;
const dashboardTabs = await handler(view); const dashboardTabs = await handler(view);
const tabs: (azdata.TabGroup | azdata.Tab)[] = []; const tabs = this.createTabs(dashboardTabs, view);
dashboardTabs.forEach((item: azdata.DashboardTab | azdata.DashboardTabGroup) => { this._tabbedPanel = view.modelBuilder.tabbedPanel().withTabs(tabs).withLayout({
if ('tabs' in item) {
tabs.push(<azdata.TabGroup>{
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({
orientation: 'vertical', orientation: 'vertical',
showIcon: this._options?.showIcon ?? true, showIcon: this._options?.showIcon ?? true,
alwaysShowTabs: this._options?.alwaysShowTabs ?? false alwaysShowTabs: this._options?.alwaysShowTabs ?? false
}).component(); }).component();
return view.initializeModel(tabbedPanel); return view.initializeModel(this._tabbedPanel);
}); });
} }
@@ -508,6 +508,23 @@ class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard {
return tab; 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(<azdata.TabGroup>{
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 { export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {

View File

@@ -352,6 +352,7 @@ export abstract class ContainerBase<T> extends ComponentBase {
public clearContainer(): void { public clearContainer(): void {
this.items = []; this.items = [];
this.onItemsUpdated();
this._changeRef.detectChanges(); this._changeRef.detectChanges();
} }

View File

@@ -99,7 +99,12 @@ export default class TabbedPanelComponent extends ContainerBase<TabConfig> imple
} }
onItemsUpdated(): void { 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) { if (firstTabIndex >= 0) {
this._panel.selectTab(firstTabIndex); this._panel.selectTab(firstTabIndex);
} }