diff --git a/src/sql/parts/modelComponents/button.component.ts b/src/sql/parts/modelComponents/button.component.ts index 4b0bd413f9..cecb533862 100644 --- a/src/sql/parts/modelComponents/button.component.ts +++ b/src/sql/parts/modelComponents/button.component.ts @@ -10,21 +10,16 @@ import { import * as sqlops from 'sqlops'; -import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { ComponentWithIconBase } from 'sql/parts/modelComponents/componentWithIconBase'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; import { attachButtonStyler } from 'sql/common/theme/styler'; import { Button } from 'sql/base/browser/ui/button/button'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_TITLE_FOREGROUND } 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'; -type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI }; @Component({ selector: 'modelview-button', @@ -32,12 +27,10 @@ type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | UR
` }) -export default class ButtonComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit { +export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit { @Input() descriptor: IComponentDescriptor; @Input() modelStore: IModelStore; private _button: Button; - private _iconClass: string; - private _iconPath: IUserFriendlyIcon; @ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef; constructor( @@ -71,9 +64,6 @@ export default class ButtonComponent extends ComponentBase implements IComponent } ngOnDestroy(): void { - if (this._iconClass) { - removeCSSRulesContainingSelector(this._iconClass); - } this.baseDestroy(); } @@ -101,14 +91,11 @@ export default class ButtonComponent extends ComponentBase implements IComponent this.updateIcon(); } - private updateIcon() { - if (this.iconPath && this.iconPath !== this._iconPath) { - this._iconPath = this.iconPath; + protected updateIcon() { + if (this.iconPath) { if (!this._iconClass) { - const ids = new IdGenerator('button-component-icon-' + Math.round(Math.random() * 1000)); - this._iconClass = ids.nextId(); + super.updateIcon(); this._button.icon = this._iconClass + ' icon'; - // Styling for icon button this._register(attachButtonStyler(this._button, this.themeService, { buttonBackground: Color.transparent.toString(), @@ -117,36 +104,6 @@ export default class ButtonComponent extends ComponentBase implements IComponent 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: IUserFriendlyIcon): string { - if (iconPath && iconPath['light']) { - return this.getIconPath(iconPath['light']); - } else { - return this.getIconPath(iconPath); - } - } - - private getDarkIconPath(iconPath: IUserFriendlyIcon): 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(); } } @@ -160,13 +117,7 @@ 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/card.component.html b/src/sql/parts/modelComponents/card.component.html index 48e5153d95..ec1ac8c7f0 100644 --- a/src/sql/parts/modelComponents/card.component.html +++ b/src/sql/parts/modelComponents/card.component.html @@ -1,19 +1,32 @@ -
+
-
-

{{label}}

-

{{value}}

- - - - - - -
{{action.label}} - {{action.actionTitle}} -
-
-
+ + +
+
+
+

{{label}}

+
+
+
+ + +
+

{{label}}

+

{{value}}

+ + + + + + +
{{action.label}} + {{action.actionTitle}} +
+
+
+
+
\ No newline at end of file diff --git a/src/sql/parts/modelComponents/card.component.ts b/src/sql/parts/modelComponents/card.component.ts index 6ee4ca2a95..ba5c74a171 100644 --- a/src/sql/parts/modelComponents/card.component.ts +++ b/src/sql/parts/modelComponents/card.component.ts @@ -15,14 +15,14 @@ import * as colors from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service'; -import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; +import { ComponentWithIconBase } from 'sql/parts/modelComponents/componentWithIconBase'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; import { StatusIndicator, CardProperties, ActionDescriptor } from 'sql/workbench/api/common/sqlExtHostTypes'; @Component({ templateUrl: decodeURI(require.toUrl('sql/parts/modelComponents/card.component.html')) }) -export default class CardComponent extends ComponentBase implements IComponent, OnDestroy { +export default class CardComponent extends ComponentWithIconBase implements IComponent, OnDestroy { @Input() descriptor: IComponentDescriptor; @Input() modelStore: IModelStore; @@ -30,7 +30,7 @@ export default class CardComponent extends ComponentBase implements IComponent, constructor(@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ElementRef)) private _el: ElementRef, - @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService + @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, ) { super(changeRef); } @@ -46,6 +46,39 @@ export default class CardComponent extends ComponentBase implements IComponent, this.baseDestroy(); } + private _defaultBorderColor = 'rgb(214, 214, 214)'; + private _hasFocus: boolean; + + public onCardClick() { + if (this.selectable) { + this.selected = !this.selected; + this._changeRef.detectChanges(); + this._onEventEmitter.fire({ + eventType: ComponentEventType.onDidClick, + args: this.selected + }); + } + } + + public getBorderColor() { + if (this.selectable && this.selected || this._hasFocus) { + return 'Blue'; + } else { + return this._defaultBorderColor; + } + } + + public getClass(): string { + return (this.selectable && this.selected || this._hasFocus) ? 'model-card selected' : + 'model-card unselected'; + } + + public onCardHoverChanged(event: any) { + if (this.selectable) { + this._hasFocus = event.type === 'mouseover'; + this._changeRef.detectChanges(); + } + } /// IComponent implementation public layout(): void { @@ -57,6 +90,19 @@ export default class CardComponent extends ComponentBase implements IComponent, this.layout(); } + public setProperties(properties: { [key: string]: any; }): void { + super.setProperties(properties); + this.updateIcon(); + } + + public get iconClass(): string { + return this._iconClass + ' icon' + ' cardIcon'; + } + + private get selectable(): boolean { + return this.cardType === 'VerticalButton'; + } + // CSS-bound properties public get label(): string { @@ -67,6 +113,27 @@ export default class CardComponent extends ComponentBase implements IComponent, return this.getPropertyOrDefault((props) => props.value, ''); } + public get cardType(): string { + return this.getPropertyOrDefault((props) => props.cardType, 'Details'); + } + + public get selected(): boolean { + return this.getPropertyOrDefault((props) => props.selected, false); + } + + public set selected(newValue: boolean) { + this.setPropertyFromUI((props, value) => props.selected = value, newValue); + } + + public get isDetailsCard(): boolean { + return !this.cardType || this.cardType === 'Details'; + } + + public get isVerticalButton(): boolean { + return this.cardType === 'VerticalButton'; + } + + public get actions(): ActionDescriptor[] { return this.getPropertyOrDefault((props) => props.actions, []); } diff --git a/src/sql/parts/modelComponents/card.css b/src/sql/parts/modelComponents/card.css index 49aecafa0f..3875663d90 100644 --- a/src/sql/parts/modelComponents/card.css +++ b/src/sql/parts/modelComponents/card.css @@ -7,12 +7,26 @@ margin: 15px; border-width: 1px; border-style: solid; - border-color: rgb(214, 214, 214); + text-align: left; vertical-align: top; box-shadow: rgba(120, 120, 120, 0.75) 0px 0px 6px; } +.model-card.selected { + border-color: darkblue +} + +.vs-dark .monaco-workbench .model-card.selected, +.hc-black .monaco-workbench .model-card.selected { + border-color: darkblue +} + +.model-card.unselected { + border-color: rgb(214, 214, 214); +} + + .model-card .card-content { position: relative; display: inline-block; @@ -23,6 +37,16 @@ min-width: 30px; } +.model-card .card-vertical-button { + position: relative; + display: inline-block; + height: auto; + width: auto; + padding: 5px 5px 5px 5px; + min-height: 130px; + min-width: 130px; +} + .model-card .card-label { font-size: 12px; font-weight: bold; @@ -33,6 +57,19 @@ line-height: 18px; } +.model-card .iconContainer { + width: 100%; + height: 50px; + text-align: center; + padding: 10px 0px 10px 0px; +} + +.model-card .cardIcon { + display: inline-block; + width: 40px; + height: 40px; +} + .model-card .card-status { position: absolute; top: 7px; diff --git a/src/sql/parts/modelComponents/componentBase.ts b/src/sql/parts/modelComponents/componentBase.ts index c51ebc5254..c25c5377ae 100644 --- a/src/sql/parts/modelComponents/componentBase.ts +++ b/src/sql/parts/modelComponents/componentBase.ts @@ -18,6 +18,12 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component'; +import URI from 'vs/base/common/uri'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; + + +export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI }; export class ItemDescriptor { constructor(public descriptor: IComponentDescriptor, public config: T) { } diff --git a/src/sql/parts/modelComponents/componentWithIconBase.ts b/src/sql/parts/modelComponents/componentWithIconBase.ts new file mode 100644 index 0000000000..8dc3611d5b --- /dev/null +++ b/src/sql/parts/modelComponents/componentWithIconBase.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, + ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, OnInit, QueryList +} from '@angular/core'; + +import { IComponent, IComponentDescriptor, IModelStore, IComponentEventArgs, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; +import * as sqlops from 'sqlops'; +import URI from 'vs/base/common/uri'; +import { IdGenerator } from 'vs/base/common/idGenerator'; +import { createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom'; +import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; + + +export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI }; + +export class ItemDescriptor { + constructor(public descriptor: IComponentDescriptor, public config: T) { } +} + +export abstract class ComponentWithIconBase extends ComponentBase { + + protected _iconClass: string; + protected _iconPath: IUserFriendlyIcon; + constructor( + changeRef: ChangeDetectorRef) { + super(changeRef); + } + + /// IComponent implementation + + public get iconClass(): string { + return this._iconClass + ' icon'; + } + + protected updateIcon() { + if (this.iconPath && this.iconPath !== this._iconPath) { + this._iconPath = this.iconPath; + if (!this._iconClass) { + const ids = new IdGenerator('model-view-component-icon-' + Math.round(Math.random() * 1000)); + this._iconClass = ids.nextId(); + } + + 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: IUserFriendlyIcon): string { + if (iconPath && iconPath['light']) { + return this.getIconPath(iconPath['light']); + } else { + return this.getIconPath(iconPath); + } + } + + private getDarkIconPath(iconPath: IUserFriendlyIcon): 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(); + } + } + + public getIconWidth(): string { + return this.convertSize(this.iconWidth, '40px'); + } + + public getIconHeight(): string { + return this.convertSize(this.iconHeight, '40px'); + } + + public get iconPath(): string | URI | { light: string | URI; dark: string | URI } { + return this.getPropertyOrDefault((props) => props.iconPath, undefined); + } + + public get iconHeight(): number | string { + return this.getPropertyOrDefault((props) => props.iconHeight, '40px'); + } + + public get iconWidth(): number | string { + return this.getPropertyOrDefault((props) => props.iconWidth, '40px'); + } + + + ngOnDestroy(): void { + if (this._iconClass) { + removeCSSRulesContainingSelector(this._iconClass); + } + super.ngOnDestroy(); + } +} diff --git a/src/sql/parts/modelComponents/formContainer.component.ts b/src/sql/parts/modelComponents/formContainer.component.ts index 45cbf91128..303b113a1f 100644 --- a/src/sql/parts/modelComponents/formContainer.component.ts +++ b/src/sql/parts/modelComponents/formContainer.component.ts @@ -26,7 +26,7 @@ export interface TitledFormItemLayout { horizontal: boolean; componentWidth?: number | string; componentHeight?: number | string; - titleFontSize?: number; + titleFontSize?: number | string; required?: boolean; info?: string; } @@ -48,7 +48,7 @@ class FormItem {
{{getItemTitle(item)}}* - +
@@ -68,7 +68,7 @@ class FormItem {
{{getItemTitle(item)}}* - +
diff --git a/src/sql/parts/modelComponents/formLayout.css b/src/sql/parts/modelComponents/formLayout.css index 60442271b8..ed5323224c 100644 --- a/src/sql/parts/modelComponents/formLayout.css +++ b/src/sql/parts/modelComponents/formLayout.css @@ -42,6 +42,11 @@ padding-left: 5px; } +.form-info { + width: 15px; + height: 15px; +} + .form-component-actions { padding-left: 5px; } diff --git a/src/sql/sqlops.proposed.d.ts b/src/sql/sqlops.proposed.d.ts index 517d7cba0c..998cd17713 100644 --- a/src/sql/sqlops.proposed.d.ts +++ b/src/sql/sqlops.proposed.d.ts @@ -239,7 +239,7 @@ declare module 'sqlops' { horizontal?: boolean; componentWidth?: number | string; componentHeight?: number | string; - titleFontSize?: number; + titleFontSize?: number | string; required?: boolean; info?: string; } @@ -300,15 +300,22 @@ declare module 'sqlops' { Error = 3 } + export enum CardType { + VerticalButton = 'VerticalButton', + Details = 'Details' + } + /** * Properties representing the card component, can be used * when using ModelBuilder to create the component */ - export interface CardProperties { + export interface CardProperties extends ComponentWithIcon { label: string; value?: string; actions?: ActionDescriptor[]; status?: StatusIndicator; + selected?: boolean; + cardType: CardType; } export type InputBoxInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'range' | 'search' | 'text' | 'time' | 'url' | 'week'; @@ -318,6 +325,12 @@ declare module 'sqlops' { width?: number | string; } + export interface ComponentWithIcon { + iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; + iconHeight?: number | string; + iconWidth?: number | string; + } + export interface InputBoxProperties extends ComponentProperties { value?: string; ariaLabel?: string; @@ -393,20 +406,17 @@ declare module 'sqlops' { html?: string; } - export interface ButtonProperties extends ComponentProperties { + export interface ButtonProperties extends ComponentProperties, ComponentWithIcon { label?: string; - iconPath?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }; } export interface LoadingComponentProperties { loading?: boolean; } - export interface CardComponent extends Component { - label: string; - value: string; - actions?: ActionDescriptor[]; + export interface CardComponent extends Component, CardProperties { onDidActionClick: vscode.Event; + onCardSelectedChanged: vscode.Event; } export interface TextComponent extends Component { diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index f8249168b7..f8dd4e5940 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -208,6 +208,8 @@ export interface CardProperties { value?: string; actions?: ActionDescriptor[]; status?: StatusIndicator; + selected?: boolean; + cardType: CardType; } export interface ActionDescriptor { @@ -236,4 +238,9 @@ export enum DeclarativeDataType { string = 'string', category = 'category', boolean = 'boolean' +} + +export enum CardType { + VerticalButton = 'VerticalButton', + Details = 'Details' } \ No newline at end of file diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index 80f9aad992..e1879abb58 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -15,7 +15,7 @@ import * as vscode from 'vscode'; 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 { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType, CardType } from 'sql/workbench/api/common/sqlExtHostTypes'; class ModelBuilderImpl implements sqlops.ModelBuilder { private nextComponentId: number; @@ -523,6 +523,7 @@ class CardWrapper extends ComponentWrapper implements sqlops.CardComponent { super(proxy, handle, ModelComponentTypes.Card, id); this.properties = {}; this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); + this._emitterMap.set(ComponentEventType.onDidClick, new Emitter()); } public get label(): string { @@ -537,17 +538,53 @@ class CardWrapper extends ComponentWrapper implements sqlops.CardComponent { public set value(v: string) { this.setProperty('value', v); } + public get selected(): boolean { + return this.properties['selected']; + } + public set selected(v: boolean) { + this.setProperty('selected', v); + } + public get cardType(): sqlops.CardType { + return this.properties['cardType']; + } + public set cardType(v: sqlops.CardType) { + this.setProperty('cardType', v); + } public get actions(): sqlops.ActionDescriptor[] { return this.properties['actions']; } public set actions(a: sqlops.ActionDescriptor[]) { this.setProperty('actions', a); } + 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 iconHeight(): number | string { + return this.properties['iconHeight']; + } + public set iconHeight(v: number | string) { + this.setProperty('iconHeight', v); + } + public get iconWidth(): number | string { + return this.properties['iconWidth']; + } + public set iconWidth(v: number | string) { + this.setProperty('iconWidth', v); + } public get onDidActionClick(): vscode.Event { let emitter = this._emitterMap.get(ComponentEventType.onDidClick); return emitter && emitter.event; } + + public get onCardSelectedChanged(): vscode.Event { + let emitter = this._emitterMap.get(ComponentEventType.onDidClick); + return emitter && emitter.event; + } } class InputBoxWrapper extends ComponentWrapper implements sqlops.InputBoxComponent { diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index c0153d8cfc..194586331c 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -391,7 +391,8 @@ export function createApiFactory( workspace, queryeditor: queryEditor, ui: ui, - StatusIndicator: sqlExtHostTypes.StatusIndicator + StatusIndicator: sqlExtHostTypes.StatusIndicator, + CardType: sqlExtHostTypes.CardType }; } };