mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Support icons in panel (#895)
* support icons in panel * formatting * address Smitha comments * address comments
This commit is contained in:
@@ -35,6 +35,7 @@ panel {
|
|||||||
|
|
||||||
.tabbedPanel .tabList .tab {
|
.tabbedPanel .tabList .tab {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabbedPanel .tabList .tab .tabLabel {
|
.tabbedPanel .tabList .tab .tabLabel {
|
||||||
@@ -43,6 +44,16 @@ panel {
|
|||||||
padding-bottom: 4px;
|
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 {
|
.tabbedPanel .tabList .tab-header {
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export interface IPanelOptions {
|
|||||||
*/
|
*/
|
||||||
showTabsWhenOne?: boolean;
|
showTabsWhenOne?: boolean;
|
||||||
layout?: NavigationBarLayout;
|
layout?: NavigationBarLayout;
|
||||||
|
showIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NavigationBarLayout {
|
export enum NavigationBarLayout {
|
||||||
@@ -33,7 +34,8 @@ export enum NavigationBarLayout {
|
|||||||
|
|
||||||
const defaultOptions: IPanelOptions = {
|
const defaultOptions: IPanelOptions = {
|
||||||
showTabsWhenOne: true,
|
showTabsWhenOne: true,
|
||||||
layout: NavigationBarLayout.horizontal
|
layout: NavigationBarLayout.horizontal,
|
||||||
|
showIcon: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const verticalLayout = 'vertical';
|
const verticalLayout = 'vertical';
|
||||||
@@ -49,7 +51,7 @@ let idPool = 0;
|
|||||||
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title" #titleContainer>
|
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title" #titleContainer>
|
||||||
<div class="tabList" #tabList>
|
<div class="tabList" #tabList>
|
||||||
<div *ngFor="let tab of _tabs">
|
<div *ngFor="let tab of _tabs">
|
||||||
<tab-header [tab]="tab" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
|
<tab-header [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="title-actions">
|
<div class="title-actions">
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class TabComponent implements OnDestroy {
|
|||||||
@Input() public title: string;
|
@Input() public title: string;
|
||||||
@Input() public canClose: boolean;
|
@Input() public canClose: boolean;
|
||||||
@Input() public actions: Array<Action>;
|
@Input() public actions: Array<Action>;
|
||||||
|
@Input() public iconClass: string;
|
||||||
public _active = false;
|
public _active = false;
|
||||||
@Input() public identifier: string;
|
@Input() public identifier: string;
|
||||||
@Input() private visibilityType: 'if' | 'visibility' = 'if';
|
@Input() private visibilityType: 'if' | 'visibility' = 'if';
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { CloseTabAction } from './tabActions';
|
|||||||
template: `
|
template: `
|
||||||
<div #actionHeader class="tab-header" style="display: flex; flex: 0 0; flex-direction: row;" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
|
<div #actionHeader class="tab-header" style="display: flex; flex: 0 0; flex-direction: row;" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
|
||||||
<span class="tab" (click)="selectTab(tab)">
|
<span class="tab" (click)="selectTab(tab)">
|
||||||
<a class="tabLabel" [class.active]="tab.active">
|
<a class="tabLabel" [class.active]="tab.active" #tabLabel>
|
||||||
{{tab.title}}
|
{{tab.title}}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
@@ -31,6 +31,7 @@ import { CloseTabAction } from './tabActions';
|
|||||||
})
|
})
|
||||||
export class TabHeaderComponent extends Disposable implements AfterContentInit, OnDestroy {
|
export class TabHeaderComponent extends Disposable implements AfterContentInit, OnDestroy {
|
||||||
@Input() public tab: TabComponent;
|
@Input() public tab: TabComponent;
|
||||||
|
@Input() public showIcon: boolean;
|
||||||
@Output() public onSelectTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
@Output() public onSelectTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
||||||
@Output() public onCloseTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
@Output() public onCloseTab: EventEmitter<TabComponent> = new EventEmitter<TabComponent>();
|
||||||
|
|
||||||
@@ -38,18 +39,29 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
|
|||||||
|
|
||||||
@ViewChild('actionHeader', { read: ElementRef }) private _actionHeaderRef: ElementRef;
|
@ViewChild('actionHeader', { read: ElementRef }) private _actionHeaderRef: ElementRef;
|
||||||
@ViewChild('actionbar', { read: ElementRef }) private _actionbarRef: ElementRef;
|
@ViewChild('actionbar', { read: ElementRef }) private _actionbarRef: ElementRef;
|
||||||
|
@ViewChild('tabLabel', { read: ElementRef }) private _tabLabelRef: ElementRef;
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentInit(): void {
|
ngAfterContentInit(): void {
|
||||||
this._actionbar = new ActionBar(this._actionbarRef.nativeElement);
|
if (this.tab.canClose || this.tab.actions) {
|
||||||
if (this.tab.actions) {
|
this._actionbar = new ActionBar(this._actionbarRef.nativeElement);
|
||||||
this._actionbar.push(this.tab.actions, { icon: true, label: false });
|
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));
|
let tabLabelcontainer = this._tabLabelRef.nativeElement as HTMLElement;
|
||||||
this._actionbar.push(closeAction, { icon: true, label: false });
|
if (this.showIcon && this.tab.iconClass) {
|
||||||
|
tabLabelcontainer.className = 'tabLabel icon';
|
||||||
|
tabLabelcontainer.classList.add(this.tab.iconClass);
|
||||||
|
} else {
|
||||||
|
tabLabelcontainer.className = 'tabLabel';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as types from 'vs/base/common/types';
|
|||||||
|
|
||||||
import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry';
|
||||||
import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
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 { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||||
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
|
|||||||
result = validateGridContainerContribution(extension, containerValue);
|
result = validateGridContainerContribution(extension, containerValue);
|
||||||
break;
|
break;
|
||||||
case NAV_SECTION:
|
case NAV_SECTION:
|
||||||
result = validateNavSectionContribution(extension, containerValue);
|
result = validateNavSectionContributionAndRegisterIcon(extension, containerValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface TabConfig extends IDashboardTab {
|
|||||||
editable: boolean;
|
editable: boolean;
|
||||||
canClose: boolean;
|
canClose: boolean;
|
||||||
actions?: Array<Action>;
|
actions?: Array<Action>;
|
||||||
|
iconClass?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IUserFriendlyIcon = string | { light: string; dark: string; };
|
export type IUserFriendlyIcon = string | { light: string; dark: string; };
|
||||||
@@ -47,6 +48,7 @@ export type IUserFriendlyIcon = string | { light: string; dark: string; };
|
|||||||
export interface NavSectionConfig {
|
export interface NavSectionConfig {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
iconClass?: string;
|
||||||
icon?: IUserFriendlyIcon;
|
icon?: IUserFriendlyIcon;
|
||||||
container: object;
|
container: object;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createCSSRule } from 'vs/base/browser/dom';
|
|||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
|
|
||||||
import { registerContainer, generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
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 { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||||
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
import { GRID_CONTAINER, validateGridContainerContribution } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
|
||||||
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
||||||
@@ -91,7 +91,7 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardContainerContrib | IDashboar
|
|||||||
result = validateGridContainerContribution(extension, containerValue);
|
result = validateGridContainerContribution(extension, containerValue);
|
||||||
break;
|
break;
|
||||||
case NAV_SECTION:
|
case NAV_SECTION:
|
||||||
result = validateNavSectionContribution(extension, containerValue);
|
result = validateNavSectionContributionAndRegisterIcon(extension, containerValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
-->
|
-->
|
||||||
<panel [options]="panelOpt" style="flex: 1 1 auto;" class="dashboard-panel" (onTabChange)="handleTabChange($event)">
|
<panel [options]="panelOpt" style="flex: 1 1 auto;" class="dashboard-panel" (onTabChange)="handleTabChange($event)">
|
||||||
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize" [identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions">
|
<tab [visibilityType]="'visibility'" *ngFor="let tab of tabs" [title]="tab.title" class="fullsize"
|
||||||
|
[identifier]="tab.id" [canClose]="tab.canClose" [actions]="tab.actions" [iconClass]="tab.iconClass">
|
||||||
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
<dashboard-webview-container *ngIf="getContentType(tab) === 'webview-container'" [tab]="tab">
|
||||||
</dashboard-webview-container>
|
</dashboard-webview-container>
|
||||||
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
|||||||
let navSectionContainers: NavSectionConfig[] = [];
|
let navSectionContainers: NavSectionConfig[] = [];
|
||||||
if (this.tab.container) {
|
if (this.tab.container) {
|
||||||
navSectionContainers = Object.values(this.tab.container)[0];
|
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);
|
this.loadNewTabs(navSectionContainers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,13 +108,13 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
|
|||||||
configs = cb.apply(this, [configs]);
|
configs = cb.apply(this, [configs]);
|
||||||
});
|
});
|
||||||
if (key === WIDGETS_CONTAINER) {
|
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 {
|
} 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 => {
|
}).map(v => {
|
||||||
let config = v as TabConfig;
|
let config = v as TabConfig;
|
||||||
config.context = this.tab.context;
|
config.context = this.tab.context;
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
import { IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry';
|
import { IExtensionPointUser } from 'vs/platform/extensions/common/extensionsRegistry';
|
||||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||||
import * as nls from 'vs/nls';
|
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 { registerContainerType, generateNavSectionContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
|
||||||
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
import { WIDGETS_CONTAINER, validateWidgetContainerContribution } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
|
||||||
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
|
||||||
@@ -60,7 +64,39 @@ let NavSectionSchema: IJSONSchema = {
|
|||||||
|
|
||||||
registerContainerType(NAV_SECTION, NavSectionSchema);
|
registerContainerType(NAV_SECTION, NavSectionSchema);
|
||||||
|
|
||||||
export function validateNavSectionContribution(extension: IExtensionPointUser<any>, navSectionConfigs: NavSectionConfig[]): boolean {
|
function isValidIcon(icon: IUserFriendlyIcon, extension: IExtensionPointUser<any>): 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<any>): 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<any>, navSectionConfigs: NavSectionConfig[]): boolean {
|
||||||
let result = true;
|
let result = true;
|
||||||
navSectionConfigs.forEach(section => {
|
navSectionConfigs.forEach(section => {
|
||||||
if (!section.title) {
|
if (!section.title) {
|
||||||
@@ -78,6 +114,10 @@ export function validateNavSectionContribution(extension: IExtensionPointUser<an
|
|||||||
extension.collector.error(nls.localize('navSection.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space.'));
|
extension.collector.error(nls.localize('navSection.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isValidIcon(section.icon, extension)) {
|
||||||
|
section.iconClass = createCSSRuleForIcon(section.icon, extension);
|
||||||
|
}
|
||||||
|
|
||||||
let containerKey = Object.keys(section.container)[0];
|
let containerKey = Object.keys(section.container)[0];
|
||||||
let containerValue = Object.values(section.container)[0];
|
let containerValue = Object.values(section.container)[0];
|
||||||
|
|
||||||
@@ -93,6 +133,7 @@ export function validateNavSectionContribution(extension: IExtensionPointUser<an
|
|||||||
extension.collector.error(nls.localize('navSection.invalidContainer_error', 'NAV_SECTION within NAV_SECTION is an invalid container for extension.'));
|
extension.collector.error(nls.localize('navSection.invalidContainer_error', 'NAV_SECTION within NAV_SECTION is an invalid container for extension.'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user