diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index aeafcb309d..eb9ab370d2 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -225,6 +225,7 @@ declare module 'azdata' { */ export interface TabbedPanelLayout { orientation: TabOrientation; + showIcon: boolean; } /** @@ -245,6 +246,11 @@ declare module 'azdata' { * Id of the tab */ id: string; + + /** + * Icon of the tab + */ + icon?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; } /** @@ -287,5 +293,33 @@ declare module 'azdata' { */ export const onDidChangeActiveNotebookEditor: vscode.Event; } + + export namespace window { + export interface ModelViewDashboard { + registerTabs(handler: (view: ModelView) => Thenable<(DashboardTab | DashboardTabGroup)[]>): void; + open(): Thenable; + } + + export function createModelViewDashboard(title: string): ModelViewDashboard; + } + + export interface DashboardTab extends Tab { + /** + * Toolbar of the tab, optional. + */ + toolbar?: ToolbarContainer; + } + + export interface DashboardTabGroup { + /** + * * Title of the tab group + */ + title: string; + + /** + * children of the tab group + */ + tabs: DashboardTab[]; + } } diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index b68539e8b1..e8e7b9b6df 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -4,9 +4,6 @@ *--------------------------------------------------------------------------------------------*/ .tabbedPanel { - border-top-color: rgba(128, 128, 128, 0.35); - border-top-width: 1px; - border-top-style: solid; box-sizing: border-box; display: flex; flex-direction: column; @@ -18,15 +15,22 @@ panel { width: 100%; } -.tabbedPanel .composite.title { +.tabbedPanel.horizontal>.title { display: flex; flex: 0 0 auto; position: relative; } +.tabbedPanel.vertical>.title { + flex: 0 0 auto; + flex-direction: column; + height: 100%; +} + .tabbedPanel .tabContainer { flex: 1 1 auto; overflow: hidden; + height: 100%; } .tabbedPanel .tabList { @@ -45,31 +49,20 @@ panel { margin: auto; } -.tabbedPanel .tabList .tab .tabLabel { - font-size: 13px; - padding-bottom: 4px; - font-weight: 600; -} - -.tabbedPanel.vertical .tabList .tab .tabLabel { - font-size: 11px; -} - .tabbedPanel .tabList .tab-header { - display: flex; padding-left: 5px; - padding-right: 5px; - cursor: pointer; + padding-right: 10px; + line-height: 35px; } -.tabbedPanel.vertical .tabList .tab-header { +.tabbedPanel.horizontal > .title .tabList .tab-header { + display: flex; + min-width: 80px; +} + +.tabbedPanel.vertical > .title .tabList .tab-header { display: block; - text-transform: none; - text-overflow: ellipsis; - overflow: hidden; - width: auto; - height: 50px; - line-height: 45px; + min-width: 150px; } .tabbedPanel .tabList .tab .tabIcon.codicon { @@ -85,8 +78,6 @@ panel { .tabbedPanel .composite.title .title-actions .action-label { display: block; - height: 35px; - line-height: 35px; min-width: 28px; background-size: 16px; background-position: center center; @@ -110,22 +101,22 @@ panel { flex-direction: row; } -.tabbedPanel.vertical > .title { +.tabbedPanel.vertical>.title { flex: 0 0 auto; flex-direction: column; height: 100%; } -.tabbedPanel > .tab-content { - flex: 1; +.tabbedPanel>.tab-content { + flex: 1 1 auto; position: relative; } -.tabbedPanel.vertical > .title > .tabContainer > .monaco-scrollable-element > .tabList { +.tabbedPanel.vertical>.title > .tabContainer .tabList { flex-flow: column; } -.tabbedPanel.horizontal > .title > .tabContainer > .monaco-scrollable-element > .tabList { +.tabbedPanel.horizontal > .title > .tabContainer .tabList { flex-flow: row; } @@ -169,7 +160,7 @@ panel { background-size: 11px 11px; } -.vs .tabbedPanel .tab-action.collapse{ +.vs .tabbedPanel .tab-action.collapse { background-image: url("collapse.svg"); } diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index fd658c22e7..6b79433717 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -55,13 +55,13 @@ let idPool = 0;
-
+
-
+
{{tab.title}}
diff --git a/src/sql/base/browser/ui/panel/tabHeader.component.ts b/src/sql/base/browser/ui/panel/tabHeader.component.ts index 4781c4b935..5da411aac2 100644 --- a/src/sql/base/browser/ui/panel/tabHeader.component.ts +++ b/src/sql/base/browser/ui/panel/tabHeader.component.ts @@ -69,7 +69,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit, const tabLabelContainer = this._tabLabelRef.nativeElement as HTMLElement; if (this.showIcon && this.tab.iconClass) { const tabIconContainer = this._tabIconRef.nativeElement as HTMLElement; - tabIconContainer.className = 'tabIcon codicon'; + tabIconContainer.className = 'tabIcon codicon icon'; tabIconContainer.classList.add(this.tab.iconClass); } diff --git a/src/sql/workbench/api/common/extHostModelView.ts b/src/sql/workbench/api/common/extHostModelView.ts index 6f921aaf80..1787aef328 100644 --- a/src/sql/workbench/api/common/extHostModelView.ts +++ b/src/sql/workbench/api/common/extHostModelView.ts @@ -499,22 +499,23 @@ class TabbedPanelComponentBuilder extends ContainerBuilderImpl { if (item && 'tabs' in item) { item.tabs.forEach(tab => { - itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, item.title)); + 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)); + itemConfigs.push(this.toItemConfig(tab.content, tab.title, tab.id, undefined, tab.icon)); } }); this._component.itemConfigs = itemConfigs; return this; } - toItemConfig(content: azdata.Component, title: string, id?: string, group?: string): InternalItemConfig { + 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 + id: id, + icon: icon }); } } diff --git a/src/sql/workbench/api/common/extHostModelViewDialog.ts b/src/sql/workbench/api/common/extHostModelViewDialog.ts index 9961fd0527..6ad4798717 100644 --- a/src/sql/workbench/api/common/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/common/extHostModelViewDialog.ts @@ -457,6 +457,57 @@ class WizardImpl implements azdata.window.Wizard { } } +class ModelViewDashboardImpl implements azdata.window.ModelViewDashboard { + constructor( + private _editor: ModelViewEditorImpl + ) { + } + registerTabs(handler: (view: azdata.ModelView) => Thenable<(azdata.DashboardTab | azdata.DashboardTabGroup)[]>): void { + this._editor.registerContent(async (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({ + orientation: 'vertical', + showIcon: true + }).component(); + return view.initializeModel(tabbedPanel); + }); + } + + open(): Thenable { + return this._editor.openEditor(); + } + + createTab(tab: azdata.DashboardTab, view: azdata.ModelView): azdata.Tab { + if (tab.toolbar) { + const flexContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component(); + flexContainer.addItem(tab.toolbar, { flex: '0 0 auto' }); + flexContainer.addItem(tab.content, { flex: '1 1 auto' }); + return { + title: tab.title, + id: tab.id, + content: flexContainer, + icon: tab.icon + }; + } else { + return tab; + } + } +} + export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { private static _currentHandle = 0; @@ -562,6 +613,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { return editor; } + public createModelViewDashboard(title: string, extension: IExtensionDescription): azdata.window.ModelViewDashboard { + const editor = this.createModelViewEditor(title, extension, { supportsSave: false }) as ModelViewEditorImpl; + return new ModelViewDashboardImpl(editor); + } + public updateDialogContent(dialog: azdata.window.Dialog): void { let handle = this.getHandle(dialog); let tabs = dialog.content; diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 524c9f8b4d..815e1a179e 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -418,6 +418,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp createWizard(title: string): azdata.window.Wizard { return extHostModelViewDialog.createWizard(title); }, + createModelViewDashboard(title: string): azdata.window.ModelViewDashboard { + return extHostModelViewDialog.createModelViewDashboard(title, extension); + }, MessageLevel: sqlExtHostTypes.MessageLevel }; diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index e77115b758..4fd1e5fc49 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -839,4 +839,5 @@ export enum TabOrientation { export interface TabbedPanelLayout { orientation: TabOrientation; + showIcon: boolean; } diff --git a/src/sql/workbench/browser/modelComponents/componentBase.ts b/src/sql/workbench/browser/modelComponents/componentBase.ts index 6003c3680a..ec5ea4aeba 100644 --- a/src/sql/workbench/browser/modelComponents/componentBase.ts +++ b/src/sql/workbench/browser/modelComponents/componentBase.ts @@ -332,6 +332,7 @@ export abstract class ContainerBase extends ComponentBase { } })); this._changeRef.detectChanges(); + this.onItemsUpdated(); return; } @@ -343,6 +344,7 @@ export abstract class ContainerBase extends ComponentBase { if (index >= 0) { this.items.splice(index, 1); this._changeRef.detectChanges(); + this.onItemsUpdated(); return true; } return false; @@ -373,4 +375,7 @@ export abstract class ContainerBase extends ComponentBase { } abstract setLayout(layout: any): void; + + protected onItemsUpdated(): void { + } } diff --git a/src/sql/workbench/browser/modelComponents/media/tabbedPanel.css b/src/sql/workbench/browser/modelComponents/media/tabbedPanel.css index 668df2748f..00cbab04b0 100644 --- a/src/sql/workbench/browser/modelComponents/media/tabbedPanel.css +++ b/src/sql/workbench/browser/modelComponents/media/tabbedPanel.css @@ -14,19 +14,20 @@ border-width: 0px; } -.vs-dark .tabbedpanel-component .tabbedPanel .tabContainer, .hc-black .tabbedpanel-component .tabbedPanel .tabContainer { - border-color: rgba(128, 128, 128, 0.5);; +.vs-dark .tabbedpanel-component .tabbedPanel .tabContainer, +.hc-black .tabbedpanel-component .tabbedPanel .tabContainer { + border-color: rgba(128, 128, 128, 0.5); } -.tabbedpanel-component .tabbedPanel.vertical .tabContainer { +.tabbedpanel-component .tabbedPanel.vertical > .title .tabContainer { border-right-width: 1px; } -.tabbedpanel-component .tabbedPanel.horizontal .tabContainer { +.tabbedpanel-component .tabbedPanel.horizontal > .title .tabContainer { border-bottom-width: 1px; } -.tabbedpanel-component .tabbedPanel .tab>.tabLabel.active { +.tabbedpanel-component .tabbedPanel .tab > .tabLabel.active { border-bottom: 0px solid; } @@ -45,9 +46,10 @@ } .tabbedpanel-component .tabList .tab-header.active { - background-color: rgb(237, 235, 233); + background-color: #E1F0FE; } -.vs-dark .tabbedpanel-component .tabList .tab-header.active, .hc-black .tabbedpanel-component .tabList .tab-header.active { +.vs-dark .tabbedpanel-component .tabList .tab-header.active, +.hc-black .tabbedpanel-component .tabList .tab-header.active { background-color: rgba(128, 128, 128, 0.5); } diff --git a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.html b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.html index b692d6f1fe..b09c655266 100644 --- a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.html +++ b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.html @@ -7,10 +7,9 @@
+ [identifier]="tab.id" [type]="tab.type" [iconClass]="tab.iconClass"> - {{tab.title}} diff --git a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts index 3682e5400d..21b6a98c32 100644 --- a/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts +++ b/src/sql/workbench/browser/modelComponents/tabbedPanel.component.ts @@ -10,11 +10,13 @@ import { TabOrientation, TabbedPanelLayout } from 'sql/workbench/api/common/sqlE import { ContainerBase } from 'sql/workbench/browser/modelComponents/componentBase'; import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; import 'vs/css!./media/tabbedPanel'; +import { IUserFriendlyIcon, createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils'; export interface TabConfig { title: string; id?: string; group: string; + icon?: IUserFriendlyIcon; } interface Tab { @@ -22,6 +24,7 @@ interface Tab { content?: IComponentDescriptor; id?: string; type: TabType; + iconClass?: string; } @Component({ @@ -56,7 +59,7 @@ export default class TabbedPanelComponent extends ContainerBase imple this._panel.options = { showTabsWhenOne: true, layout: layout.orientation === TabOrientation.Horizontal ? NavigationBarLayout.horizontal : NavigationBarLayout.vertical, - showIcon: false + showIcon: layout.showIcon }; } @@ -86,6 +89,7 @@ export default class TabbedPanelComponent extends ContainerBase imple title: item.config.title, id: item.config.id, content: item.descriptor, + iconClass: item.config.icon ? createIconCssClass(item.config.icon) : undefined, type: 'tab' }); } @@ -93,4 +97,11 @@ export default class TabbedPanelComponent extends ContainerBase imple } return this._tabs; } + + onItemsUpdated(): void { + const firstTabIndex = this.tabs.findIndex(tab => tab.type === 'tab'); + if (firstTabIndex >= 0) { + this._panel.selectTab(firstTabIndex); + } + } }