mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
allow extension to provide custom icons to data explorer tree (#16868)
* expose icon path to extension for OE node * remove node type * pr comments
This commit is contained in:
@@ -42,7 +42,7 @@ export default class MainController implements vscode.Disposable {
|
|||||||
public activate(): Promise<boolean> {
|
public activate(): Promise<boolean> {
|
||||||
const connectionProvider = new ConnectionProvider();
|
const connectionProvider = new ConnectionProvider();
|
||||||
const iconProvider = new IconProvider();
|
const iconProvider = new IconProvider();
|
||||||
const objectExplorer = new ObjectExplorerProvider();
|
const objectExplorer = new ObjectExplorerProvider(this.context);
|
||||||
azdata.dataprotocol.registerConnectionProvider(connectionProvider);
|
azdata.dataprotocol.registerConnectionProvider(connectionProvider);
|
||||||
azdata.dataprotocol.registerIconProvider(iconProvider);
|
azdata.dataprotocol.registerIconProvider(iconProvider);
|
||||||
azdata.dataprotocol.registerObjectExplorerProvider(objectExplorer);
|
azdata.dataprotocol.registerObjectExplorerProvider(objectExplorer);
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import { ProviderId } from './connectionProvider';
|
|||||||
* This class implements the ObjectExplorerProvider interface that is responsible for providing the connection tree view content.
|
* This class implements the ObjectExplorerProvider interface that is responsible for providing the connection tree view content.
|
||||||
*/
|
*/
|
||||||
export class ObjectExplorerProvider implements azdata.ObjectExplorerProvider {
|
export class ObjectExplorerProvider implements azdata.ObjectExplorerProvider {
|
||||||
|
constructor(private context: vscode.ExtensionContext) {
|
||||||
|
}
|
||||||
|
|
||||||
onSessionCreatedEmitter: vscode.EventEmitter<azdata.ObjectExplorerSession> = new vscode.EventEmitter<azdata.ObjectExplorerSession>();
|
onSessionCreatedEmitter: vscode.EventEmitter<azdata.ObjectExplorerSession> = new vscode.EventEmitter<azdata.ObjectExplorerSession>();
|
||||||
onSessionCreated: vscode.Event<azdata.ObjectExplorerSession> = this.onSessionCreatedEmitter.event;
|
onSessionCreated: vscode.Event<azdata.ObjectExplorerSession> = this.onSessionCreatedEmitter.event;
|
||||||
|
|
||||||
@@ -60,13 +63,18 @@ export class ObjectExplorerProvider implements azdata.ObjectExplorerProvider {
|
|||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
nodePath: 'root/1',
|
nodePath: 'root/1',
|
||||||
nodeType: 'Database',
|
nodeType: '',
|
||||||
label: 'abc1',
|
icon: {
|
||||||
|
light: this.context.asAbsolutePath('images/group.svg'),
|
||||||
|
dark: this.context.asAbsolutePath('images/group_inverse.svg')
|
||||||
|
},
|
||||||
|
label: 'obj 1',
|
||||||
isLeaf: false
|
isLeaf: false
|
||||||
}, {
|
}, {
|
||||||
nodePath: 'root/2',
|
nodePath: 'root/2',
|
||||||
nodeType: 'Database',
|
nodeType: '',
|
||||||
label: 'abc2',
|
icon: azdata.SqlThemeIcon.Column,
|
||||||
|
label: 'obj 2',
|
||||||
isLeaf: false
|
isLeaf: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
7
src/sql/azdata.proposed.d.ts
vendored
7
src/sql/azdata.proposed.d.ts
vendored
@@ -921,4 +921,11 @@ declare module 'azdata' {
|
|||||||
*/
|
*/
|
||||||
physicalMemoryInMb?: number;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { nb, IConnectionProfile } from 'azdata';
|
import { nb, IConnectionProfile } from 'azdata';
|
||||||
import * as vsExtTypes from 'vs/workbench/api/common/extHostTypes';
|
import * as vsExtTypes from 'vs/workbench/api/common/extHostTypes';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
// SQL added extension host types
|
// SQL added extension host types
|
||||||
export enum ServiceOptionType {
|
export enum ServiceOptionType {
|
||||||
@@ -447,6 +448,9 @@ export class TreeItem extends vsExtTypes.TreeItem {
|
|||||||
providerHandle?: string;
|
providerHandle?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ThemedIconPath = { light: string | URI; dark: string | URI };
|
||||||
|
export type IconPath = string | URI | ThemedIconPath;
|
||||||
|
|
||||||
export class SqlThemeIcon {
|
export class SqlThemeIcon {
|
||||||
|
|
||||||
static readonly Folder = new SqlThemeIcon('Folder');
|
static readonly Folder = new SqlThemeIcon('Folder');
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browse
|
|||||||
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
|
import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
|
||||||
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
||||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||||
|
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||||
|
|
||||||
const DefaultConnectionIconClass = 'server-page';
|
const DefaultConnectionIconClass = 'server-page';
|
||||||
|
|
||||||
@@ -168,6 +169,8 @@ class TreeNodeTemplate extends Disposable {
|
|||||||
let iconName: string | undefined = undefined;
|
let iconName: string | undefined = undefined;
|
||||||
if (element.iconType) {
|
if (element.iconType) {
|
||||||
iconName = (typeof element.iconType === 'string') ? element.iconType : element.iconType.id;
|
iconName = (typeof element.iconType === 'string') ? element.iconType : element.iconType.id;
|
||||||
|
} else if (instanceOfSqlThemeIcon(element.icon)) {
|
||||||
|
iconName = element.icon.id;
|
||||||
} else {
|
} else {
|
||||||
iconName = element.nodeTypeId;
|
iconName = element.nodeTypeId;
|
||||||
if (element.nodeStatus) {
|
if (element.nodeStatus) {
|
||||||
@@ -185,10 +188,12 @@ class TreeNodeTemplate extends Disposable {
|
|||||||
this._icon.classList.remove(...tokens);
|
this._icon.classList.remove(...tokens);
|
||||||
this._icon.classList.add('icon');
|
this._icon.classList.add('icon');
|
||||||
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
||||||
this._icon.classList.add(iconLowerCaseName);
|
if (iconLowerCaseName) {
|
||||||
|
this._icon.classList.add(iconLowerCaseName);
|
||||||
|
}
|
||||||
|
|
||||||
if (element.iconPath) {
|
if (element.icon && !instanceOfSqlThemeIcon(element.icon)) {
|
||||||
iconRenderer.putIcon(this._icon, element.iconPath);
|
iconRenderer.putIcon(this._icon, element.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._label.textContent = element.label;
|
this._label.textContent = element.label;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* 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 { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
|
||||||
import { hash } from 'vs/base/common/hash';
|
import { hash } from 'vs/base/common/hash';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
@@ -10,10 +11,10 @@ import { URI } from 'vs/base/common/uri';
|
|||||||
class IconRenderer {
|
class IconRenderer {
|
||||||
private iconRegistered: Set<string> = new Set<string>();
|
private iconRegistered: Set<string> = new Set<string>();
|
||||||
|
|
||||||
public registerIcon(path: URI | IconPath | undefined): string | undefined {
|
public registerIcon(path: IconPath | undefined): string | undefined {
|
||||||
if (!path) { return undefined; }
|
if (!path) { return undefined; }
|
||||||
let iconPath: IconPath = this.toIconPath(path);
|
const iconPath: ThemedIconUri = this.toThemedIconUri(path);
|
||||||
let iconUid: string | undefined = this.getIconUid(iconPath);
|
const iconUid: string | undefined = this.getIconUid(iconPath);
|
||||||
if (iconUid && !this.iconRegistered.has(iconUid)) {
|
if (iconUid && !this.iconRegistered.has(iconUid)) {
|
||||||
createCSSRule(`.icon#${iconUid}`, `background: ${asCSSUrl(iconPath.light || iconPath.dark)} center center no-repeat`);
|
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`);
|
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;
|
return iconUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getIconUid(path: URI | IconPath): string | undefined {
|
public getIconUid(path: IconPath): string | undefined {
|
||||||
if (!path) { return 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())}`;
|
return `icon${hash(iconPath.light.toString() + iconPath.dark.toString())}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private toIconPath(path: URI | IconPath): IconPath {
|
private toThemedIconUri(path: IconPath): ThemedIconUri {
|
||||||
if (URI.isUri(path)) {
|
let light, dark: string | URI;
|
||||||
let singlePath = path;
|
|
||||||
return { light: singlePath, dark: singlePath };
|
if (URI.isUri(path) || (typeof (path) === 'string')) {
|
||||||
|
light = dark = path;
|
||||||
} else {
|
} 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);
|
let iconUid: string | undefined = this.registerIcon(path);
|
||||||
element.id = iconUid ?? '';
|
element.id = iconUid ?? '';
|
||||||
}
|
}
|
||||||
@@ -130,7 +141,7 @@ class BadgeRenderer {
|
|||||||
|
|
||||||
export const badgeRenderer: BadgeRenderer = new BadgeRenderer();
|
export const badgeRenderer: BadgeRenderer = new BadgeRenderer();
|
||||||
|
|
||||||
interface IconPath {
|
interface ThemedIconUri {
|
||||||
light: URI;
|
light: URI;
|
||||||
dark: URI;
|
dark: URI;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -614,7 +614,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let node = new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
|
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),
|
getChildren: (treeNode?: TreeNode) => this.getChildren(treeNode),
|
||||||
isExpanded: treeNode => this.isExpanded(treeNode),
|
isExpanded: treeNode => this.isExpanded(treeNode),
|
||||||
setNodeExpandedState: async (treeNode, expandedState) => await this.setNodeExpandedState(treeNode, expandedState),
|
setNodeExpandedState: async (treeNode, expandedState) => await this.setNodeExpandedState(treeNode, expandedState),
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { badgeRenderer, iconRenderer } from 'sql/workbench/services/objectExplor
|
|||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
|
||||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||||
|
import { instanceOfSqlThemeIcon } from 'sql/workbench/services/objectExplorer/common/nodeType';
|
||||||
|
|
||||||
export interface IConnectionTemplateData {
|
export interface IConnectionTemplateData {
|
||||||
root: HTMLElement;
|
root: HTMLElement;
|
||||||
@@ -136,6 +137,8 @@ export class ServerTreeRenderer implements IRenderer {
|
|||||||
let iconName: string | undefined = undefined;
|
let iconName: string | undefined = undefined;
|
||||||
if (treeNode.iconType) {
|
if (treeNode.iconType) {
|
||||||
iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id;
|
iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id;
|
||||||
|
} else if (instanceOfSqlThemeIcon(treeNode.icon)) {
|
||||||
|
iconName = treeNode.icon.id;
|
||||||
} else {
|
} else {
|
||||||
iconName = treeNode.nodeTypeId;
|
iconName = treeNode.nodeTypeId;
|
||||||
if (treeNode.nodeStatus) {
|
if (treeNode.nodeStatus) {
|
||||||
@@ -153,10 +156,12 @@ export class ServerTreeRenderer implements IRenderer {
|
|||||||
templateData.icon.classList.remove(...tokens);
|
templateData.icon.classList.remove(...tokens);
|
||||||
templateData.icon.classList.add('icon');
|
templateData.icon.classList.add('icon');
|
||||||
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
||||||
templateData.icon.classList.add(iconLowerCaseName);
|
if (iconLowerCaseName) {
|
||||||
|
templateData.icon.classList.add(iconLowerCaseName);
|
||||||
|
}
|
||||||
|
|
||||||
if (treeNode.iconPath) {
|
if (treeNode.icon && !instanceOfSqlThemeIcon(treeNode.icon)) {
|
||||||
iconRenderer.putIcon(templateData.icon, treeNode.iconPath);
|
iconRenderer.putIcon(templateData.icon, treeNode.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
templateData.label.textContent = treeNode.label;
|
templateData.label.textContent = treeNode.label;
|
||||||
|
|||||||
@@ -103,3 +103,8 @@ export class NodeType {
|
|||||||
export interface SqlThemeIcon {
|
export interface SqlThemeIcon {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function instanceOfSqlThemeIcon(obj: any): obj is SqlThemeIcon {
|
||||||
|
const icon = obj as SqlThemeIcon;
|
||||||
|
return icon && icon.id !== undefined;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { NodeType, SqlThemeIcon } from 'sql/workbench/services/objectExplorer/co
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
import * as UUID from 'vs/base/common/uuid';
|
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 {
|
export enum TreeItemCollapsibleState {
|
||||||
None = 0,
|
None = 0,
|
||||||
@@ -92,11 +92,12 @@ export class TreeNode {
|
|||||||
|
|
||||||
public iconType?: string | SqlThemeIcon;
|
public iconType?: string | SqlThemeIcon;
|
||||||
|
|
||||||
public iconPath?: URI | { light: URI, dark: URI };
|
public icon?: IconPath | SqlThemeIcon;
|
||||||
|
|
||||||
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
|
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
|
||||||
nodeSubType: string, nodeStatus?: string, parent?: TreeNode, metadata?: azdata.ObjectMetadata,
|
nodeSubType: string, nodeStatus?: string, parent?: TreeNode, metadata?: azdata.ObjectMetadata,
|
||||||
iconType?: string | SqlThemeIcon,
|
iconType?: string | SqlThemeIcon,
|
||||||
|
icon?: IconPath | SqlThemeIcon,
|
||||||
private _objectExplorerCallbacks?: ObjectExplorerCallbacks) {
|
private _objectExplorerCallbacks?: ObjectExplorerCallbacks) {
|
||||||
this.nodeTypeId = nodeTypeId;
|
this.nodeTypeId = nodeTypeId;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
@@ -108,6 +109,7 @@ export class TreeNode {
|
|||||||
this.id = UUID.generateUuid();
|
this.id = UUID.generateUuid();
|
||||||
this.nodeSubType = nodeSubType;
|
this.nodeSubType = nodeSubType;
|
||||||
this.nodeStatus = nodeStatus;
|
this.nodeStatus = nodeStatus;
|
||||||
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
public getConnectionProfile(): ConnectionProfile | undefined {
|
public getConnectionProfile(): ConnectionProfile | undefined {
|
||||||
let currentNode: TreeNode = this;
|
let currentNode: TreeNode = this;
|
||||||
|
|||||||
Reference in New Issue
Block a user