diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index 053dd183ff..f18afd9253 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -35,6 +35,7 @@ panel { .tabbedPanel .tabList .tab { cursor: pointer; + margin: auto; } .tabbedPanel .tabList .tab .tabLabel { @@ -43,6 +44,16 @@ panel { padding-bottom: 4px; } +.tabbedPanel .tabList .tab .tabLabel.icon { + background-repeat: no-repeat; + background-position: center 10px; + background-size: 25px; + line-height: 15px; + padding-top: 40px; + display: inline-block; + text-transform: none; +} + .tabbedPanel .tabList .tab-header { padding-left: 10px; padding-right: 10px; diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index 27562588a3..21e74bf4c7 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -24,6 +24,7 @@ export interface IPanelOptions { */ showTabsWhenOne?: boolean; layout?: NavigationBarLayout; + showIcon?: boolean; } export enum NavigationBarLayout { @@ -33,7 +34,8 @@ export enum NavigationBarLayout { const defaultOptions: IPanelOptions = { showTabsWhenOne: true, - layout: NavigationBarLayout.horizontal + layout: NavigationBarLayout.horizontal, + showIcon: false }; const verticalLayout = 'vertical'; @@ -49,7 +51,7 @@ let idPool = 0;
- +
diff --git a/src/sql/base/browser/ui/panel/tab.component.ts b/src/sql/base/browser/ui/panel/tab.component.ts index 4610c9da00..e19ae4b81b 100644 --- a/src/sql/base/browser/ui/panel/tab.component.ts +++ b/src/sql/base/browser/ui/panel/tab.component.ts @@ -23,6 +23,7 @@ export class TabComponent implements OnDestroy { @Input() public title: string; @Input() public canClose: boolean; @Input() public actions: Array; + @Input() public iconClass: string; public _active = false; @Input() public identifier: string; @Input() private visibilityType: 'if' | 'visibility' = 'if'; diff --git a/src/sql/base/browser/ui/panel/tabHeader.component.ts b/src/sql/base/browser/ui/panel/tabHeader.component.ts index cbb4442095..b8502969e9 100644 --- a/src/sql/base/browser/ui/panel/tabHeader.component.ts +++ b/src/sql/base/browser/ui/panel/tabHeader.component.ts @@ -21,7 +21,7 @@ import { CloseTabAction } from './tabActions'; template: `
- + {{tab.title}} @@ -31,6 +31,7 @@ import { CloseTabAction } from './tabActions'; }) export class TabHeaderComponent extends Disposable implements AfterContentInit, OnDestroy { @Input() public tab: TabComponent; + @Input() public showIcon: boolean; @Output() public onSelectTab: EventEmitter = new EventEmitter(); @Output() public onCloseTab: EventEmitter = new EventEmitter(); @@ -38,18 +39,29 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit, @ViewChild('actionHeader', { read: ElementRef }) private _actionHeaderRef: ElementRef; @ViewChild('actionbar', { read: ElementRef }) private _actionbarRef: ElementRef; + @ViewChild('tabLabel', { read: ElementRef }) private _tabLabelRef: ElementRef; constructor() { super(); } ngAfterContentInit(): void { - this._actionbar = new ActionBar(this._actionbarRef.nativeElement); - if (this.tab.actions) { - this._actionbar.push(this.tab.actions, { icon: true, label: false }); + if (this.tab.canClose || this.tab.actions) { + this._actionbar = new ActionBar(this._actionbarRef.nativeElement); + if (this.tab.actions) { + this._actionbar.push(this.tab.actions, { icon: true, label: false }); + } + if (this.tab.canClose) { + let closeAction = this._register(new CloseTabAction(this.closeTab, this)); + this._actionbar.push(closeAction, { icon: true, label: false }); + } } - if (this.tab.canClose) { - let closeAction = this._register(new CloseTabAction(this.closeTab, this)); - this._actionbar.push(closeAction, { icon: true, label: false }); + + let tabLabelcontainer = this._tabLabelRef.nativeElement as HTMLElement; + if (this.showIcon && this.tab.iconClass) { + tabLabelcontainer.className = 'tabLabel icon'; + tabLabelcontainer.classList.add(this.tab.iconClass); + } else { + tabLabelcontainer.className = 'tabLabel'; } } diff --git a/src/sql/parts/dashboard/common/dashboardTab.contribution.ts b/src/sql/parts/dashboard/common/dashboardTab.contribution.ts index f43a6805b0..a22a77fa25 100644 --- a/src/sql/parts/dashboard/common/dashboardTab.contribution.ts +++ b/src/sql/parts/dashboard/common/dashboardTab.contribution.ts @@ -9,7 +9,7 @@ import * as types from 'vs/base/common/types'; import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry'; import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; -import { NAV_SECTION, validateNavSectionContribution } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution'; +import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution'; import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution'; import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution'; @@ -130,7 +130,7 @@ ExtensionsRegistry.registerExtensionPoint; + iconClass?: string; } export type IUserFriendlyIcon = string | { light: string; dark: string; }; @@ -47,6 +48,7 @@ export type IUserFriendlyIcon = string | { light: string; dark: string; }; export interface NavSectionConfig { id: string; title: string; + iconClass?: string; icon?: IUserFriendlyIcon; container: object; } diff --git a/src/sql/parts/dashboard/containers/dashboardContainer.contribution.ts b/src/sql/parts/dashboard/containers/dashboardContainer.contribution.ts index 187bcfaa3c..922d13fb9c 100644 --- a/src/sql/parts/dashboard/containers/dashboardContainer.contribution.ts +++ b/src/sql/parts/dashboard/containers/dashboardContainer.contribution.ts @@ -10,7 +10,7 @@ import { createCSSRule } from 'vs/base/browser/dom'; import URI from 'vs/base/common/uri'; import { registerContainer, generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; -import { NAV_SECTION, validateNavSectionContribution } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution'; +import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution'; import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution'; import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution'; import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution'; @@ -91,7 +91,7 @@ ExtensionsRegistry.registerExtensionPoint - + diff --git a/src/sql/parts/dashboard/containers/dashboardNavSection.component.ts b/src/sql/parts/dashboard/containers/dashboardNavSection.component.ts index 1a8478957c..b15eabd238 100644 --- a/src/sql/parts/dashboard/containers/dashboardNavSection.component.ts +++ b/src/sql/parts/dashboard/containers/dashboardNavSection.component.ts @@ -64,6 +64,13 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh let navSectionContainers: NavSectionConfig[] = []; if (this.tab.container) { navSectionContainers = Object.values(this.tab.container)[0]; + let hasIcon = true; + navSectionContainers.forEach(navSection => { + if (!navSection.iconClass) { + hasIcon = false; + } + }); + this.panelOpt.showIcon = hasIcon; this.loadNewTabs(navSectionContainers); } } @@ -101,13 +108,13 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh configs = cb.apply(this, [configs]); }); if (key === WIDGETS_CONTAINER) { - return { id: v.id, title: v.title, container: { 'widgets-container': configs } }; + return { id: v.id, title: v.title, container: { 'widgets-container': configs }, iconClass: v.iconClass }; } else { - return { id: v.id, title: v.title, container: { 'grid-container': configs } }; + return { id: v.id, title: v.title, container: { 'grid-container': configs }, iconClass: v.iconClass }; } } - return { id: v.id, title: v.title, container: containerResult.container }; + return { id: v.id, title: v.title, container: containerResult.container, iconClass: v.iconClass }; }).map(v => { let config = v as TabConfig; config.context = this.tab.context; diff --git a/src/sql/parts/dashboard/containers/dashboardNavSection.contribution.ts b/src/sql/parts/dashboard/containers/dashboardNavSection.contribution.ts index 736b8645bc..0fb9de3552 100644 --- a/src/sql/parts/dashboard/containers/dashboardNavSection.contribution.ts +++ b/src/sql/parts/dashboard/containers/dashboardNavSection.contribution.ts @@ -5,8 +5,12 @@ import { IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as nls from 'vs/nls'; +import { join } from 'path'; +import { createCSSRule } from 'vs/base/browser/dom'; +import URI from 'vs/base/common/uri'; +import { IdGenerator } from 'vs/base/common/idGenerator'; -import { NavSectionConfig } from 'sql/parts/dashboard/common/dashboardWidget'; +import { NavSectionConfig, IUserFriendlyIcon } from 'sql/parts/dashboard/common/dashboardWidget'; import { registerContainerType, generateNavSectionContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry'; import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution'; import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution'; @@ -60,7 +64,39 @@ let NavSectionSchema: IJSONSchema = { registerContainerType(NAV_SECTION, NavSectionSchema); -export function validateNavSectionContribution(extension: IExtensionPointUser, navSectionConfigs: NavSectionConfig[]): boolean { +function isValidIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser): boolean { + if (typeof icon === 'undefined') { + return false; + } + if (typeof icon === 'string') { + return true; + } else if (typeof icon.dark === 'string' && typeof icon.light === 'string') { + return true; + } + extension.collector.error(nls.localize('opticon', "property `icon` can be omitted or must be either a string or a literal like `{dark, light}`")); + return false; +} + +const ids = new IdGenerator('contrib-dashboardNavSection-icon-'); + +function createCSSRuleForIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser): string { + let iconClass: string; + if (icon) { + iconClass = ids.nextId(); + if (typeof icon === 'string') { + const path = join(extension.description.extensionFolderPath, icon); + createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(path).toString()}")`); + } else { + const light = join(extension.description.extensionFolderPath, icon.light); + const dark = join(extension.description.extensionFolderPath, icon.dark); + createCSSRule(`.icon.${iconClass}`, `background-image: url("${URI.file(light).toString()}")`); + createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${URI.file(dark).toString()}")`); + } + } + return iconClass; +} + +export function validateNavSectionContributionAndRegisterIcon(extension: IExtensionPointUser, navSectionConfigs: NavSectionConfig[]): boolean { let result = true; navSectionConfigs.forEach(section => { if (!section.title) { @@ -78,6 +114,10 @@ export function validateNavSectionContribution(extension: IExtensionPointUser