mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-13 17:22:15 -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> {
|
||||
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);
|
||||
|
||||
@@ -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<azdata.ObjectExplorerSession> = new vscode.EventEmitter<azdata.ObjectExplorerSession>();
|
||||
onSessionCreated: vscode.Event<azdata.ObjectExplorerSession> = 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
|
||||
}
|
||||
]
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 * 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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string> = new Set<string>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user