diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index 44be117871..71408bd3fa 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -42,7 +42,7 @@ export default class MainController implements vscode.Disposable { public activate(): Promise { const connectionProvider = new ConnectionProvider(); const iconProvider = new IconProvider(); - const objectExplorer = new ObjectExplorerProvider(); + const objectExplorer = new ObjectExplorerProvider(this.context); azdata.dataprotocol.registerConnectionProvider(connectionProvider); azdata.dataprotocol.registerIconProvider(iconProvider); azdata.dataprotocol.registerObjectExplorerProvider(objectExplorer); diff --git a/samples/sqlservices/src/featureProviders/objectExplorerProvider.ts b/samples/sqlservices/src/featureProviders/objectExplorerProvider.ts index f6bba8a130..c70aa162a0 100644 --- a/samples/sqlservices/src/featureProviders/objectExplorerProvider.ts +++ b/samples/sqlservices/src/featureProviders/objectExplorerProvider.ts @@ -10,6 +10,9 @@ import { ProviderId } from './connectionProvider'; * This class implements the ObjectExplorerProvider interface that is responsible for providing the connection tree view content. */ export class ObjectExplorerProvider implements azdata.ObjectExplorerProvider { + constructor(private context: vscode.ExtensionContext) { + } + onSessionCreatedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); onSessionCreated: vscode.Event = this.onSessionCreatedEmitter.event; @@ -60,13 +63,18 @@ export class ObjectExplorerProvider implements azdata.ObjectExplorerProvider { nodes: [ { nodePath: 'root/1', - nodeType: 'Database', - label: 'abc1', + nodeType: '', + icon: { + light: this.context.asAbsolutePath('images/group.svg'), + dark: this.context.asAbsolutePath('images/group_inverse.svg') + }, + label: 'obj 1', isLeaf: false }, { nodePath: 'root/2', - nodeType: 'Database', - label: 'abc2', + nodeType: '', + icon: azdata.SqlThemeIcon.Column, + label: 'obj 2', isLeaf: false } ] diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 8610a81db6..d8b2212f84 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -921,4 +921,11 @@ declare module 'azdata' { */ physicalMemoryInMb?: number; } + + export interface NodeInfo { + /** + * Specify the icon for the node. The value could the path to the icon or and ADS icon defined in {@link SqlThemeIcon}. + */ + icon?: IconPath | SqlThemeIcon; + } } diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index f09978cad5..484d642a29 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -5,6 +5,7 @@ import { nb, IConnectionProfile } from 'azdata'; import * as vsExtTypes from 'vs/workbench/api/common/extHostTypes'; +import { URI } from 'vs/base/common/uri'; // SQL added extension host types export enum ServiceOptionType { @@ -447,6 +448,9 @@ export class TreeItem extends vsExtTypes.TreeItem { providerHandle?: string; } +export type ThemedIconPath = { light: string | URI; dark: string | URI }; +export type IconPath = string | URI | ThemedIconPath; + export class SqlThemeIcon { static readonly Folder = new SqlThemeIcon('Folder'); diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts index 0b8ab25b02..127e21bbbc 100644 --- a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts +++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts @@ -23,6 +23,7 @@ import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browse import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType'; const DefaultConnectionIconClass = 'server-page'; @@ -168,6 +169,8 @@ class TreeNodeTemplate extends Disposable { let iconName: string | undefined = undefined; if (element.iconType) { iconName = (typeof element.iconType === 'string') ? element.iconType : element.iconType.id; + } else if (instanceOfSqlThemeIcon(element.icon)) { + iconName = element.icon.id; } else { iconName = element.nodeTypeId; if (element.nodeStatus) { @@ -185,10 +188,12 @@ class TreeNodeTemplate extends Disposable { this._icon.classList.remove(...tokens); this._icon.classList.add('icon'); let iconLowerCaseName = iconName.toLocaleLowerCase(); - this._icon.classList.add(iconLowerCaseName); + if (iconLowerCaseName) { + this._icon.classList.add(iconLowerCaseName); + } - if (element.iconPath) { - iconRenderer.putIcon(this._icon, element.iconPath); + if (element.icon && !instanceOfSqlThemeIcon(element.icon)) { + iconRenderer.putIcon(this._icon, element.icon); } this._label.textContent = element.label; diff --git a/src/sql/workbench/services/objectExplorer/browser/iconRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/iconRenderer.ts index 0eff1dd5fa..3c3055e1ed 100644 --- a/src/sql/workbench/services/objectExplorer/browser/iconRenderer.ts +++ b/src/sql/workbench/services/objectExplorer/browser/iconRenderer.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IconPath } from 'sql/workbench/api/common/sqlExtHostTypes'; import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; import { hash } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; @@ -10,10 +11,10 @@ import { URI } from 'vs/base/common/uri'; class IconRenderer { private iconRegistered: Set = new Set(); - public registerIcon(path: URI | IconPath | undefined): string | undefined { + public registerIcon(path: IconPath | undefined): string | undefined { if (!path) { return undefined; } - let iconPath: IconPath = this.toIconPath(path); - let iconUid: string | undefined = this.getIconUid(iconPath); + const iconPath: ThemedIconUri = this.toThemedIconUri(path); + const iconUid: string | undefined = this.getIconUid(iconPath); if (iconUid && !this.iconRegistered.has(iconUid)) { createCSSRule(`.icon#${iconUid}`, `background: ${asCSSUrl(iconPath.light || iconPath.dark)} center center no-repeat`); createCSSRule(`.vs-dark .icon#${iconUid}, .hc-black .icon#${iconUid}`, `background: ${asCSSUrl(iconPath.dark)} center center no-repeat`); @@ -22,22 +23,32 @@ class IconRenderer { return iconUid; } - public getIconUid(path: URI | IconPath): string | undefined { + public getIconUid(path: IconPath): string | undefined { if (!path) { return undefined; } - let iconPath: IconPath = this.toIconPath(path); + const iconPath: ThemedIconUri = this.toThemedIconUri(path); return `icon${hash(iconPath.light.toString() + iconPath.dark.toString())}`; } - private toIconPath(path: URI | IconPath): IconPath { - if (URI.isUri(path)) { - let singlePath = path; - return { light: singlePath, dark: singlePath }; + private toThemedIconUri(path: IconPath): ThemedIconUri { + let light, dark: string | URI; + + if (URI.isUri(path) || (typeof (path) === 'string')) { + light = dark = path; } else { - return path; + light = path.light; + dark = path.dark; } + return { + light: this.toUri(light), + dark: this.toUri(dark) + }; } - public putIcon(element: HTMLElement, path: URI | IconPath | undefined): void { + private toUri(path: string | URI): URI { + return URI.isUri(path) ? path : URI.file(path); + } + + public putIcon(element: HTMLElement, path: IconPath | undefined): void { let iconUid: string | undefined = this.registerIcon(path); element.id = iconUid ?? ''; } @@ -130,7 +141,7 @@ class BadgeRenderer { export const badgeRenderer: BadgeRenderer = new BadgeRenderer(); -interface IconPath { +interface ThemedIconUri { light: URI; dark: URI; } diff --git a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts index a225845590..492a3ce774 100644 --- a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts +++ b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts @@ -614,7 +614,7 @@ export class ObjectExplorerService implements IObjectExplorerService { } let node = new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath, - nodeInfo.nodeSubType!, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, { + nodeInfo.nodeSubType!, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, nodeInfo.icon, { getChildren: (treeNode?: TreeNode) => this.getChildren(treeNode), isExpanded: treeNode => this.isExpanded(treeNode), setNodeExpandedState: async (treeNode, expandedState) => await this.setNodeExpandedState(treeNode, expandedState), diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts index 69bff45141..93c0662724 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts @@ -17,6 +17,7 @@ import { badgeRenderer, iconRenderer } from 'sql/workbench/services/objectExplor import { URI } from 'vs/base/common/uri'; import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType'; export interface IConnectionTemplateData { root: HTMLElement; @@ -136,6 +137,8 @@ export class ServerTreeRenderer implements IRenderer { let iconName: string | undefined = undefined; if (treeNode.iconType) { iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id; + } else if (instanceOfSqlThemeIcon(treeNode.icon)) { + iconName = treeNode.icon.id; } else { iconName = treeNode.nodeTypeId; if (treeNode.nodeStatus) { @@ -153,10 +156,12 @@ export class ServerTreeRenderer implements IRenderer { templateData.icon.classList.remove(...tokens); templateData.icon.classList.add('icon'); let iconLowerCaseName = iconName.toLocaleLowerCase(); - templateData.icon.classList.add(iconLowerCaseName); + if (iconLowerCaseName) { + templateData.icon.classList.add(iconLowerCaseName); + } - if (treeNode.iconPath) { - iconRenderer.putIcon(templateData.icon, treeNode.iconPath); + if (treeNode.icon && !instanceOfSqlThemeIcon(treeNode.icon)) { + iconRenderer.putIcon(templateData.icon, treeNode.icon); } templateData.label.textContent = treeNode.label; diff --git a/src/sql/workbench/services/objectExplorer/common/nodeType.ts b/src/sql/workbench/services/objectExplorer/common/nodeType.ts index 29eb8cff5e..86d4e4aafb 100644 --- a/src/sql/workbench/services/objectExplorer/common/nodeType.ts +++ b/src/sql/workbench/services/objectExplorer/common/nodeType.ts @@ -103,3 +103,8 @@ export class NodeType { export interface SqlThemeIcon { readonly id: string; } + +export function instanceOfSqlThemeIcon(obj: any): obj is SqlThemeIcon { + const icon = obj as SqlThemeIcon; + return icon && icon.id !== undefined; +} diff --git a/src/sql/workbench/services/objectExplorer/common/treeNode.ts b/src/sql/workbench/services/objectExplorer/common/treeNode.ts index 67f9574e08..b88206724b 100644 --- a/src/sql/workbench/services/objectExplorer/common/treeNode.ts +++ b/src/sql/workbench/services/objectExplorer/common/treeNode.ts @@ -8,7 +8,7 @@ import { NodeType, SqlThemeIcon } from 'sql/workbench/services/objectExplorer/co import * as azdata from 'azdata'; import * as UUID from 'vs/base/common/uuid'; -import { URI } from 'vs/base/common/uri'; +import { IconPath } from 'sql/workbench/api/common/sqlExtHostTypes'; export enum TreeItemCollapsibleState { None = 0, @@ -92,11 +92,12 @@ export class TreeNode { public iconType?: string | SqlThemeIcon; - public iconPath?: URI | { light: URI, dark: URI }; + public icon?: IconPath | SqlThemeIcon; constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string, nodeSubType: string, nodeStatus?: string, parent?: TreeNode, metadata?: azdata.ObjectMetadata, iconType?: string | SqlThemeIcon, + icon?: IconPath | SqlThemeIcon, private _objectExplorerCallbacks?: ObjectExplorerCallbacks) { this.nodeTypeId = nodeTypeId; this.label = label; @@ -108,6 +109,7 @@ export class TreeNode { this.id = UUID.generateUuid(); this.nodeSubType = nodeSubType; this.nodeStatus = nodeStatus; + this.icon = icon; } public getConnectionProfile(): ConnectionProfile | undefined { let currentNode: TreeNode = this;