mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Differentiated server icons by server type: box, big data cluster, cloud... (#5241)
This commit is contained in:
@@ -11,6 +11,8 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as path from 'path';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export interface ConnectionProviderProperties {
|
||||
providerId: string;
|
||||
@@ -66,6 +68,47 @@ const ConnectionProviderContrib: IJSONSchema = {
|
||||
type: 'string',
|
||||
description: localize('schema.displayName', "Display Name for the provider")
|
||||
},
|
||||
iconPath: {
|
||||
description: localize('schema.iconPath', 'Icon path for the server type'),
|
||||
oneOf: [
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
path: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
light: {
|
||||
type: 'string',
|
||||
},
|
||||
dark: {
|
||||
type: 'string',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
light: {
|
||||
type: 'string',
|
||||
},
|
||||
dark: {
|
||||
type: 'string',
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'string'
|
||||
}
|
||||
]
|
||||
},
|
||||
connectionOptions: {
|
||||
type: 'array',
|
||||
description: localize('schema.connectionOptions', "Options for connection"),
|
||||
@@ -123,6 +166,7 @@ ExtensionsRegistry.registerExtensionPoint<ConnectionProviderProperties | Connect
|
||||
|
||||
for (let extension of extensions) {
|
||||
const { value } = extension;
|
||||
resolveIconPath(extension);
|
||||
if (Array.isArray<ConnectionProviderProperties>(value)) {
|
||||
for (let command of value) {
|
||||
handleCommand(command, extension);
|
||||
@@ -132,3 +176,39 @@ ExtensionsRegistry.registerExtensionPoint<ConnectionProviderProperties | Connect
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function resolveIconPath(extension: IExtensionPointUser<any>): void {
|
||||
if (!extension || !extension.value) { return undefined; }
|
||||
|
||||
let toAbsolutePath = (iconPath: any, baseDir: string) => {
|
||||
if (!iconPath || !baseDir) { return; }
|
||||
if (Array.isArray(iconPath)) {
|
||||
for (let e of iconPath) {
|
||||
e.path = {
|
||||
light: URI.file(path.join(baseDir, e.path.light)),
|
||||
dark: URI.file(path.join(baseDir, e.path.dark))
|
||||
};
|
||||
}
|
||||
} else if (typeof iconPath === 'string') {
|
||||
iconPath = {
|
||||
light: URI.file(path.join(baseDir, iconPath)),
|
||||
dark: URI.file(path.join(baseDir, iconPath))
|
||||
};
|
||||
} else {
|
||||
iconPath = {
|
||||
light: URI.file(path.join(baseDir, iconPath.light)),
|
||||
dark: URI.file(path.join(baseDir, iconPath.dark))
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let baseDir = extension.description.extensionLocation.fsPath;
|
||||
let properties: ConnectionProviderProperties = extension.value;
|
||||
if (Array.isArray<ConnectionProviderProperties>(properties)) {
|
||||
for (let p of properties) {
|
||||
toAbsolutePath(p['iconPath'], baseDir);
|
||||
}
|
||||
} else {
|
||||
toAbsolutePath(properties['iconPath'], baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,25 +10,35 @@ export class ServerInfoContextKey implements IContextKey<ServerInfo> {
|
||||
|
||||
static ServerInfo = new RawContextKey<ServerInfo>('serverInfo', undefined);
|
||||
static ServerMajorVersion = new RawContextKey<string>('serverMajorVersion', undefined);
|
||||
static IsCloud = new RawContextKey<boolean>('isCloud', undefined);
|
||||
static IsBigDataCluster = new RawContextKey<boolean>('isBigDataCluster', undefined);
|
||||
|
||||
private _serverInfo: IContextKey<ServerInfo>;
|
||||
private _serverMajorVersion: IContextKey<string>;
|
||||
private _isCloud: IContextKey<boolean>;
|
||||
private _isBigDataCluster: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
this._serverInfo = ServerInfoContextKey.ServerInfo.bindTo(contextKeyService);
|
||||
this._serverMajorVersion = ServerInfoContextKey.ServerMajorVersion.bindTo(contextKeyService);
|
||||
this._isCloud = ServerInfoContextKey.IsCloud.bindTo(contextKeyService);
|
||||
this._isBigDataCluster = ServerInfoContextKey.IsBigDataCluster.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
set(value: ServerInfo) {
|
||||
this._serverInfo.set(value);
|
||||
let majorVersion = value && value.serverMajorVersion;
|
||||
this._serverMajorVersion.set(majorVersion && `${majorVersion}`);
|
||||
this._isCloud.set(value && value.isCloud);
|
||||
this._isBigDataCluster.set(value && value.options && value.options['isBigDataCluster']);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._serverMajorVersion.reset();
|
||||
this._isCloud.reset();
|
||||
this._isBigDataCluster.reset();
|
||||
}
|
||||
|
||||
public get(): ServerInfo {
|
||||
|
||||
@@ -98,22 +98,13 @@ margin-bottom: 2px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected {
|
||||
background: url('connected_active_server.svg') center center no-repeat;
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page {
|
||||
background: url('default_server.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected,
|
||||
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected{
|
||||
background: url('connected_active_server_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected {
|
||||
background: url('disconnected_server.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected,
|
||||
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected{
|
||||
background: url('disconnected_server_inverse.svg') center center no-repeat;
|
||||
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page,
|
||||
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page{
|
||||
background: url('default_server_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* loading for OE node */
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
viewBox="0 0 16 16"
|
||||
data-name="Layer 1"
|
||||
id="Layer_1">
|
||||
<metadata
|
||||
id="metadata15">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>server_16x16</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
|
||||
</defs>
|
||||
<title
|
||||
id="title6">server_16x16</title>
|
||||
<path
|
||||
style="fill:#212121"
|
||||
id="path8"
|
||||
d="m 2.735,0 v 16 h 10.53 V 0 Z m 1,1 h 8.53 v 9 h -8.53 z m 8.53,14 h -8.53 v -4 h 8.53 z"
|
||||
class="cls-1" />
|
||||
<path
|
||||
style="fill:#231f20"
|
||||
id="path10"
|
||||
d="M 7.125,4.23 H 4.675 V 1.77 h 2.45 z m -2,-0.5 h 1.5 V 2.27 h -1.45 z"
|
||||
class="cls-2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
viewBox="0 0 16 16"
|
||||
data-name="Layer 1"
|
||||
id="Layer_1">
|
||||
<metadata
|
||||
id="metadata15">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>server_16x16</dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4">
|
||||
<style
|
||||
id="style2">.cls-1{fill:#212121;}.cls-2{fill:#231f20;}</style>
|
||||
</defs>
|
||||
<title
|
||||
id="title6">server_16x16</title>
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
id="path8"
|
||||
d="m 2.735,0 v 16 h 10.53 V 0 Z m 1,1 h 8.53 v 9 h -8.53 z m 8.53,14 h -8.53 v -4 h 8.53 z"
|
||||
class="cls-1" />
|
||||
<path
|
||||
style="fill:#ffffff"
|
||||
id="path10"
|
||||
d="M 7.125,4.23 H 4.675 V 1.77 h 2.45 z m -2,-0.5 h 1.5 V 2.27 h -1.45 z"
|
||||
class="cls-2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
137
src/sql/workbench/parts/objectExplorer/browser/iconRenderer.ts
Normal file
137
src/sql/workbench/parts/objectExplorer/browser/iconRenderer.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
class IconRenderer {
|
||||
private iconRegistered: Set<string> = new Set<string>();
|
||||
|
||||
public registerIcon(path: URI | IconPath): string {
|
||||
if (!path) { return undefined; }
|
||||
let iconPath: IconPath = this.toIconPath(path);
|
||||
let iconUid: string = this.getIconUid(iconPath);
|
||||
if (!this.iconRegistered.has(iconUid)) {
|
||||
createCSSRule(`.icon#${iconUid}`, `background: url("${iconPath.light.toString()}") center center no-repeat`);
|
||||
createCSSRule(`.vs-dark .icon#${iconUid}, .hc-black .icon#${iconUid}`, `background: url("${iconPath.dark.toString()}") center center no-repeat`);
|
||||
this.iconRegistered.add(iconUid);
|
||||
}
|
||||
return iconUid;
|
||||
}
|
||||
|
||||
public getIconUid(path: URI | IconPath): string {
|
||||
if (!path) { return undefined; }
|
||||
let iconPath: IconPath = this.toIconPath(path);
|
||||
return `icon${hash(iconPath.light.toString() + iconPath.dark.toString())}`;
|
||||
}
|
||||
|
||||
private toIconPath(path: URI | IconPath): IconPath {
|
||||
if (path['light']) {
|
||||
return path as IconPath;
|
||||
} else {
|
||||
let singlePath = path as URI;
|
||||
return { light: singlePath, dark: singlePath };
|
||||
}
|
||||
}
|
||||
|
||||
public putIcon(element: HTMLElement, path: URI | IconPath): void {
|
||||
if (!element || !path) { return undefined; }
|
||||
let iconUid: string = this.registerIcon(path);
|
||||
element.id = iconUid;
|
||||
}
|
||||
|
||||
public removeIcon(element: HTMLElement): void {
|
||||
if (!element) { return undefined; }
|
||||
element.id = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export const iconRenderer: IconRenderer = new IconRenderer();
|
||||
|
||||
class BadgeRenderer {
|
||||
public readonly serverConnected: string = 'serverConnected';
|
||||
public readonly serverDisconnected: string = 'serverDisconnected';
|
||||
public readonly newTag: string = 'newTag';
|
||||
|
||||
private badgeCreated: Set<string> = new Set<string>();
|
||||
|
||||
constructor() {
|
||||
this.createBadge(this.serverConnected, this.getConnectionStatusBadge(true));
|
||||
this.createBadge(this.serverDisconnected, this.getConnectionStatusBadge(false));
|
||||
this.createBadge(this.newTag, this.getNewTagBadge());
|
||||
}
|
||||
|
||||
private getConnectionStatusBadge(isConnected: boolean) {
|
||||
let circleColor: string = isConnected ? 'rgba(59, 180, 74, 100%)' : 'rgba(208, 46, 0, 100%)';
|
||||
let bgColor: string = isConnected ? 'rgba(59, 180, 74, 100%)' : 'rgba(255, 255, 255, 80%)';
|
||||
return `position: absolute;
|
||||
height: 0.25rem;
|
||||
width: 0.25rem;
|
||||
top: 14px;
|
||||
left: 19px;
|
||||
border: 0.12rem solid ${circleColor};
|
||||
border-radius: 100%;
|
||||
background: ${bgColor};
|
||||
content:"";
|
||||
font-size: 100%;
|
||||
line-height: 100%;
|
||||
color:white;
|
||||
text-align:center;
|
||||
vertical-align:middle;`
|
||||
.replace(/\t/g, ' ').replace(/\r?\n/g, ' ').replace(/ +/g, ' ');
|
||||
}
|
||||
|
||||
private getNewTagBadge(): string {
|
||||
return `position: absolute;
|
||||
height: 0.4rem;
|
||||
width: 0.4rem;
|
||||
top: 3px;
|
||||
left: 5px;
|
||||
border: 1px solid green;
|
||||
border-radius: 15%;
|
||||
background: green;
|
||||
content:"N";
|
||||
font-size: 0.3rem;
|
||||
font-weight: bold;
|
||||
line-height: 0.4rem;
|
||||
color: white;
|
||||
text-align:center;
|
||||
vertical-align:middle;`
|
||||
.replace(/\t/g, ' ').replace(/\r?\n/g, ' ').replace(/ +/g, ' ');
|
||||
}
|
||||
|
||||
private createBadge(badgeClass: string, badge: string): void {
|
||||
if (!this.badgeCreated.has(badgeClass)) {
|
||||
createCSSRule(`.${badgeClass}:after`, badge);
|
||||
this.badgeCreated.add(badgeClass);
|
||||
}
|
||||
}
|
||||
|
||||
public addBadge(element: HTMLElement, badgeClass: string): void {
|
||||
element.innerHTML = (element.innerHTML || '') +
|
||||
`<div class="${badgeClass}" style="width: 0px; height: 0px;"><div>`;
|
||||
}
|
||||
|
||||
public removeBadge(element: HTMLElement, badgeClass: string): void {
|
||||
let children: HTMLCollection = element.children;
|
||||
let current = children[0];
|
||||
while (current) {
|
||||
let next = current.nextElementSibling;
|
||||
if (current.classList.contains(badgeClass)) {
|
||||
current.remove();
|
||||
break;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const badgeRenderer: BadgeRenderer = new BadgeRenderer();
|
||||
|
||||
interface IconPath {
|
||||
light: URI;
|
||||
dark: URI;
|
||||
}
|
||||
@@ -14,6 +14,9 @@ import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { badgeRenderer, iconRenderer } from 'sql/workbench/parts/objectExplorer/browser/iconRenderer';
|
||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export interface IConnectionTemplateData {
|
||||
root: HTMLElement;
|
||||
@@ -56,7 +59,8 @@ export class ServerTreeRenderer implements IRenderer {
|
||||
|
||||
constructor(
|
||||
isCompact: boolean,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService
|
||||
) {
|
||||
// isCompact defaults to false unless explicitly set by instantiation call.
|
||||
if (isCompact) {
|
||||
@@ -152,19 +156,70 @@ export class ServerTreeRenderer implements IRenderer {
|
||||
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
||||
templateData.icon.classList.add(iconLowerCaseName);
|
||||
|
||||
if (treeNode.iconPath) {
|
||||
iconRenderer.putIcon(templateData.icon, treeNode.iconPath);
|
||||
}
|
||||
|
||||
templateData.label.textContent = treeNode.label;
|
||||
templateData.root.title = treeNode.label;
|
||||
}
|
||||
|
||||
private getIconPath(connection: ConnectionProfile): IconPath {
|
||||
if (!connection) { return undefined; }
|
||||
|
||||
if (connection['iconPath']) {
|
||||
return connection['iconPath'];
|
||||
}
|
||||
|
||||
let iconId = this._connectionManagementService.getConnectionIconId(connection.id);
|
||||
if (!iconId) { return undefined; }
|
||||
|
||||
let providerProperties = this._connectionManagementService.getProviderProperties(connection.providerName);
|
||||
if (!providerProperties) { return undefined; }
|
||||
|
||||
let iconPath: IconPath = undefined;
|
||||
let pathConfig: URI | IconPath | { id: string, path: IconPath }[] = providerProperties['iconPath'];
|
||||
if (Array.isArray(pathConfig)) {
|
||||
for (const e of pathConfig) {
|
||||
if (!e.id || e.id === iconId) {
|
||||
iconPath = e.path;
|
||||
connection['iconPath'] = iconPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (pathConfig['light']) {
|
||||
iconPath = pathConfig as IconPath;
|
||||
connection['iconPath'] = iconPath;
|
||||
} else {
|
||||
let singlePath = pathConfig as URI;
|
||||
iconPath = { light: singlePath, dark: singlePath };
|
||||
connection['iconPath'] = iconPath;
|
||||
}
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
private renderServerIcon(element: HTMLElement, iconPath: IconPath, isConnected: boolean): void {
|
||||
if (!element) { return; }
|
||||
if (iconPath) {
|
||||
iconRenderer.putIcon(element, iconPath);
|
||||
}
|
||||
let badgeToRemove: string = isConnected ? badgeRenderer.serverDisconnected : badgeRenderer.serverConnected;
|
||||
let badgeToAdd: string = isConnected ? badgeRenderer.serverConnected : badgeRenderer.serverDisconnected;
|
||||
badgeRenderer.removeBadge(element, badgeToRemove);
|
||||
badgeRenderer.addBadge(element, badgeToAdd);
|
||||
}
|
||||
|
||||
private renderConnection(connection: ConnectionProfile, templateData: IConnectionTemplateData): void {
|
||||
if (!this._isCompact) {
|
||||
let iconPath: IconPath = this.getIconPath(connection);
|
||||
if (this._connectionManagementService.isConnected(undefined, connection)) {
|
||||
templateData.icon.classList.remove('disconnected');
|
||||
templateData.icon.classList.add('connected');
|
||||
this.renderServerIcon(templateData.icon, iconPath, true);
|
||||
} else {
|
||||
templateData.icon.classList.remove('connected');
|
||||
templateData.icon.classList.add('disconnected');
|
||||
this.renderServerIcon(templateData.icon, iconPath, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,3 +272,7 @@ export class ServerTreeRenderer implements IRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
interface IconPath {
|
||||
light: URI;
|
||||
dark: URI;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { NodeType, SqlThemeIcon } from 'sql/workbench/parts/objectExplorer/commo
|
||||
import * as azdata from 'sqlops';
|
||||
|
||||
import * as UUID from 'vs/base/common/uuid';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export enum TreeItemCollapsibleState {
|
||||
None = 0,
|
||||
@@ -91,6 +92,8 @@ export class TreeNode {
|
||||
|
||||
public iconType: string | SqlThemeIcon;
|
||||
|
||||
public iconPath: URI | { light: URI, dark: URI };
|
||||
|
||||
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
|
||||
nodeSubType: string, nodeStatus: string, parent: TreeNode, metadata: azdata.ObjectMetadata,
|
||||
iconType: string | SqlThemeIcon,
|
||||
|
||||
Reference in New Issue
Block a user