diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index 2001bb4567..7b949a762a 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -266,10 +266,23 @@ export default class MainController implements vscode.Disposable { values: ['aa', 'bb', 'cc'] }) .component(); - let button = view.modelBuilder.button() + let runIcon = path.join(__dirname, '..', 'media', 'start.svg'); + let runButton = view.modelBuilder.button() .withProperties({ - label: 'Run' + label: 'Run', + iconPath: runIcon }).component(); + + let monitoLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg')); + let monitorIcon = { + light: monitoLightPath, + dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') }; + + let monitorButton = view.modelBuilder.button() + .withProperties({ + label: 'Monitor', + iconPath: monitorIcon + }).component(); let toolbarModel = view.modelBuilder.toolbarContainer() .withToolbarItems([{ component: inputBox, @@ -278,7 +291,9 @@ export default class MainController implements vscode.Disposable { component: dropdown, title: 'favorite:' }, { - component: button + component: runButton + }, { + component: monitorButton }]).component(); diff --git a/samples/sqlservices/src/media/monitor.svg b/samples/sqlservices/src/media/monitor.svg new file mode 100644 index 0000000000..642d6a5782 --- /dev/null +++ b/samples/sqlservices/src/media/monitor.svg @@ -0,0 +1 @@ +usage \ No newline at end of file diff --git a/samples/sqlservices/src/media/monitor_inverse.svg b/samples/sqlservices/src/media/monitor_inverse.svg new file mode 100644 index 0000000000..5e56c7cabc --- /dev/null +++ b/samples/sqlservices/src/media/monitor_inverse.svg @@ -0,0 +1 @@ +usage_inverse \ No newline at end of file diff --git a/samples/sqlservices/src/media/start.svg b/samples/sqlservices/src/media/start.svg new file mode 100644 index 0000000000..2ceb9e2292 --- /dev/null +++ b/samples/sqlservices/src/media/start.svg @@ -0,0 +1 @@ +run \ No newline at end of file diff --git a/src/sql/parts/modelComponents/button.component.ts b/src/sql/parts/modelComponents/button.component.ts index bf7c4c5468..4ba87046cd 100644 --- a/src/sql/parts/modelComponents/button.component.ts +++ b/src/sql/parts/modelComponents/button.component.ts @@ -2,14 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - +import 'vs/css!./button'; import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit } from '@angular/core'; import * as sqlops from 'sqlops'; -import { Event, Emitter } from 'vs/base/common/event'; import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; @@ -19,9 +18,13 @@ import { Button } from 'sql/base/browser/ui/button/button'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { attachListStyler } from 'vs/platform/theme/common/styler'; - +import URI from 'vs/base/common/uri'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import { focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; +import { Color } from 'vs/base/common/color'; @Component({ - selector: 'button', + selector: 'modelview-button', template: `
` @@ -30,6 +33,8 @@ export default class ButtonComponent extends ComponentBase implements IComponent @Input() descriptor: IComponentDescriptor; @Input() modelStore: IModelStore; private _button: Button; + private _iconClass: string; + private _iconPath: string | URI | { light: string | URI; dark: string | URI }; @ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef; constructor( @@ -37,6 +42,7 @@ export default class ButtonComponent extends ComponentBase implements IComponent @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService ) { super(changeRef); + } ngOnInit(): void { @@ -64,6 +70,9 @@ export default class ButtonComponent extends ComponentBase implements IComponent } ngOnDestroy(): void { + if (this._iconClass) { + removeCSSRulesContainingSelector(this._iconClass); + } this.baseDestroy(); } @@ -80,7 +89,58 @@ export default class ButtonComponent extends ComponentBase implements IComponent public setProperties(properties: { [key: string]: any; }): void { super.setProperties(properties); + this._button.enabled = this.enabled; this._button.label = this.label; + this.updateIcon(); + } + + private updateIcon() { + if (this.iconPath && this.iconPath !== this._iconPath) { + this._iconPath = this.iconPath; + if (!this._iconClass) { + const ids = new IdGenerator('button-component-icon-' + Math.round(Math.random() * 1000)); + this._iconClass = ids.nextId(); + this._button.icon = this._iconClass + ' icon'; + + // Styling for icon button + this._register(attachButtonStyler(this._button, this.themeService, { + buttonBackground: Color.transparent.toString(), + buttonHoverBackground: Color.transparent.toString(), + buttonFocusOutline: focusBorder, + buttonForeground: foreground + })); + } + + removeCSSRulesContainingSelector(this._iconClass); + const icon = this.getLightIconPath(this.iconPath); + const iconDark = this.getDarkIconPath(this.iconPath) || icon; + createCSSRule(`.icon.${this._iconClass}`, `background-image: url("${icon}")`); + createCSSRule(`.vs-dark .icon.${this._iconClass}, .hc-black .icon.${this._iconClass}`, `background-image: url("${iconDark}")`); + } + } + + private getLightIconPath(iconPath: string | URI | { light: string | URI; dark: string | URI }): string { + if (iconPath && iconPath['light']) { + return this.getIconPath(iconPath['light']); + } else { + return this.getIconPath(iconPath); + } + } + + private getDarkIconPath(iconPath: string | URI | { light: string | URI; dark: string | URI }): string { + if (iconPath && iconPath['dark']) { + return this.getIconPath(iconPath['dark']); + } + return null; + } + + private getIconPath(iconPath: string | URI): string { + if (typeof iconPath === 'string') { + return URI.file(iconPath).toString(); + } else { + let uri = URI.revive(iconPath); + return uri.toString(); + } } // CSS-bound properties @@ -93,6 +153,14 @@ export default class ButtonComponent extends ComponentBase implements IComponent this.setPropertyFromUI(this.setValueProperties, newValue); } + public get iconPath(): string | URI | { light: string | URI; dark: string | URI } { + return this.getPropertyOrDefault((props) => props.iconPath, undefined); + } + + public set iconPath(newValue: string | URI | { light: string | URI; dark: string | URI }) { + this.setPropertyFromUI((properties, iconPath) => { properties.iconPath = iconPath; }, newValue); + } + private setValueProperties(properties: sqlops.ButtonProperties, label: string): void { properties.label = label; } diff --git a/src/sql/parts/modelComponents/button.css b/src/sql/parts/modelComponents/button.css new file mode 100644 index 0000000000..5e7752ad5a --- /dev/null +++ b/src/sql/parts/modelComponents/button.css @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +modelview-button a.monaco-button.monaco-text-button.icon { + background-repeat: no-repeat; + background-position: 0% 50%; +} \ No newline at end of file diff --git a/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts b/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts index 596966832f..c1193e8bad 100644 --- a/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts +++ b/src/sql/parts/modelComponents/modelEditor/modelViewEditor.ts @@ -21,6 +21,7 @@ export class ModelViewEditor extends BaseEditor { public static ID: string = 'workbench.editor.modelViewEditor'; private _editorFrame: HTMLElement; + private _content: HTMLElement; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -34,6 +35,8 @@ export class ModelViewEditor extends BaseEditor { */ public createEditor(parent: HTMLElement): void { this._editorFrame = parent; + this._content = document.createElement('div'); + parent.appendChild(this._content); } /** @@ -69,6 +72,7 @@ export class ModelViewEditor extends BaseEditor { input.appendModelViewContainer(); input.container.style.visibility = 'visible'; + this._content.setAttribute('aria-flowto', input.container.id); await super.setInput(input, options); this.doUpdateContainer(); diff --git a/src/sql/parts/modelComponents/toolbarLayout.css b/src/sql/parts/modelComponents/toolbarLayout.css index ad6819e858..15e07fa246 100644 --- a/src/sql/parts/modelComponents/toolbarLayout.css +++ b/src/sql/parts/modelComponents/toolbarLayout.css @@ -30,16 +30,14 @@ margin: auto; } -.modelview-toolbar-container .modelview-toolbar-component .select-box, +.modelview-toolbar-container .modelview-toolbar-component select, .modelview-toolbar-container .modelview-toolbar-component .monaco-inputbox { width: 200px; height: 25px; } -.modelview-toolbar-container .modelview-toolbar-component button { - height: 25px; -} - -.modelview-toolbar-container .modelview-toolbar-component button .monaco-text-button { - padding: 0px +.modelview-toolbar-container .modelview-toolbar-component modelview-button .monaco-text-button.icon { + padding-left: 15px; + background-size: 11px; + margin-right: 0.3em; } \ No newline at end of file diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 41a7cf5687..a4ccb8917d 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -332,6 +332,7 @@ declare module 'sqlops' { export interface ButtonProperties { label?: string; + iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; } export interface CardComponent extends Component { @@ -377,6 +378,7 @@ declare module 'sqlops' { export interface ButtonComponent extends Component { label: string; + iconPath: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; onDidClick: vscode.Event; } diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index 168836ee16..e29e788906 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -7,6 +7,8 @@ import { IMainContext } from 'vs/workbench/api/node/extHost.protocol'; import { Emitter } from 'vs/base/common/event'; import { deepClone } from 'vs/base/common/objects'; +import { IActionDescriptor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; +import URI from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import * as vscode from 'vscode'; @@ -14,7 +16,6 @@ import * as sqlops from 'sqlops'; import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { IActionDescriptor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; class ModelBuilderImpl implements sqlops.ModelBuilder { private nextComponentId: number; @@ -747,6 +748,13 @@ class ButtonWrapper extends ComponentWrapper implements sqlops.ButtonComponent { this.setProperty('label', v); } + public get iconPath(): string | URI | { light: string | URI; dark: string | URI } { + return this.properties['iconPath']; + } + public set iconPath(v: string | URI | { light: string | URI; dark: string | URI }) { + this.setProperty('iconPath', v); + } + public get onDidClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event;