diff --git a/extensions/machine-learning/src/common/constants.ts b/extensions/machine-learning/src/common/constants.ts index 9541dfe561..7af943d5a5 100644 --- a/extensions/machine-learning/src/common/constants.ts +++ b/extensions/machine-learning/src/common/constants.ts @@ -224,7 +224,7 @@ export function invalidImportTableSchemaError(databaseName: string | undefined, export const loadModelParameterFailedError = localize('models.loadModelParameterFailedError', "Failed to load model parameters'"); export const unsupportedModelParameterType = localize('models.unsupportedModelParameterType', "unsupported"); export const dashboardTitle = localize('dashboardTitle', "Machine Learning"); -export const dashboardDesc = localize('dashboardDesc', "Machine Learning for SQL Databases"); +export const dashboardDesc = localize('dashboardDesc', "Machine Learning for SQL databases"); export const dashboardLinksTitle = localize('dashboardLinksTitle', "Useful links"); export const dashboardVideoLinksTitle = localize('dashboardVideoLinksTitle', "Video tutorials"); export const showMoreTitle = localize('showMoreTitle', "Show more"); diff --git a/extensions/machine-learning/src/views/widgets/dashboardWidget.ts b/extensions/machine-learning/src/views/widgets/dashboardWidget.ts index 90b5de1495..03cf9ea2bc 100644 --- a/extensions/machine-learning/src/views/widgets/dashboardWidget.ts +++ b/extensions/machine-learning/src/views/widgets/dashboardWidget.ts @@ -15,11 +15,11 @@ interface IActionMetadata { description?: string, link?: string, iconPath?: { light: string | vscode.Uri; dark: string | vscode.Uri }, - command?: string; + command?: string } -const maxWidth = 800; -const headerMaxHeight = 300; +const maxWidth = 810; +const headerMaxHeight = 234; export class DashboardWidget { /** @@ -33,33 +33,30 @@ export class DashboardWidget { this._apiWrapper.registerWidget('mls.dashboard', async (view) => { const container = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', - width: '100%', + width: 'auto', height: '100%' }).component(); - const header = this.createHeader(view); - const tasksContainer = await this.createTasks(view); + const header = await this.createHeader(view); const footerContainer = this.createFooter(view); container.addItem(header, { CSSStyles: { - 'background-image': `url(${vscode.Uri.file(this.asAbsolutePath('images/background.svg'))})`, + 'background-image': ` + url(${vscode.Uri.file(this.asAbsolutePath('images/background.svg'))}), + linear-gradient(0deg, rgba(0,0,0,0.09) 0%, rgba(0,0,0,0) 100%) + `, 'background-repeat': 'no-repeat', - 'background-position': 'bottom', + 'background-position': 'left 32px', + 'background-size': '107%', + 'border': 'none', 'width': `${maxWidth}px`, - 'height': '330px', - 'background-size': `${maxWidth}px ${headerMaxHeight}px`, - 'margin-bottom': '-60px' - } - }); - container.addItem(tasksContainer, { - CSSStyles: { - 'width': `${maxWidth}px`, - 'height': '150px', + 'height': `${headerMaxHeight}px` } }); container.addItem(footerContainer, { CSSStyles: { - 'width': `${maxWidth}px`, 'height': '500px', + 'width': `${maxWidth}px`, + 'margin-top': '16px' } }); const mainContainer = view.modelBuilder.flexContainer() @@ -70,7 +67,7 @@ export class DashboardWidget { position: 'absolute' }).component(); mainContainer.addItem(container, { - CSSStyles: { 'padding-top': '25px', 'padding-left': '5px' } + CSSStyles: { 'padding-top': '12px' } }); await view.initializeModel(mainContainer); resolve(); @@ -78,7 +75,7 @@ export class DashboardWidget { }); } - private createHeader(view: azdata.ModelView): azdata.Component { + private async createHeader(view: azdata.ModelView): Promise { const header = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: maxWidth, @@ -88,7 +85,8 @@ export class DashboardWidget { value: constants.dashboardTitle, CSSStyles: { 'font-size': '36px', - 'font-weight': 'bold', + 'font-weight': '300', + 'line-height': '48px', 'margin': '0px' } }).component(); @@ -96,14 +94,22 @@ export class DashboardWidget { value: constants.dashboardDesc, CSSStyles: { 'font-size': '14px', - 'font-weight': 'bold', + 'font-weight': '300', + 'line-height': '20px', 'margin': '0px' } }).component(); header.addItems([titleComponent, descComponent], { CSSStyles: { - 'width': `${maxWidth}px`, - 'padding': '5px' + 'padding-left': '26px' + } + }); + const tasksContainer = this.createTasks(view); + header.addItem(await tasksContainer, { + CSSStyles: { + 'height': 'auto', + 'margin-top': '67px', + 'width': `${maxWidth}px` } }); @@ -134,7 +140,6 @@ export class DashboardWidget { const videosContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', width: maxWidth, - height: '300px', }).component(); links.forEach(link => { @@ -163,10 +168,7 @@ export class DashboardWidget { } }).component(); const viewPanelStyle = { - 'padding': '0px', - 'padding-right': '5px', - 'padding-top': '20px', - 'height': '200px', + 'padding': '10px 5px 10px 0', 'margin': '0px' }; @@ -204,15 +206,14 @@ export class DashboardWidget { } ]); - this.addShowMorePanel(view, linksContainer, moreVideosContainer, { 'padding-left': '5px' }, viewPanelStyle); + this.addShowMorePanel(view, linksContainer, moreVideosContainer, { 'padding-top': '10px' }, viewPanelStyle); return linksContainer; } private addShowMorePanel(view: azdata.ModelView, parentPanel: azdata.FlexContainer, morePanel: azdata.Component, moreButtonStyle: { [key: string]: string }, morePanelStyle: { [key: string]: string }): azdata.Component { - const maxWidth = 100; const linkContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', - width: maxWidth + 10, + width: 'auto', justifyContent: 'flex-start' }).component(); const showMoreComponent = view.modelBuilder.hyperlink().withProperties({ @@ -253,29 +254,18 @@ export class DashboardWidget { CSSStyles: Object.assign({}, moreButtonStyle, { 'font-size': '12px', 'margin': '0px', - 'color': '#006ab1', - 'padding-right': '5px' - } - ) + }) }); linkContainer.addItem(image, { CSSStyles: { - 'padding': '0px', - 'padding-right': '5px', - 'padding-top': '5px', - 'height': '10px', + 'padding-left': '5px', + 'padding-top': '15px', 'margin': '0px' } }); parentPanel.addItem(linkContainer, { - CSSStyles: { - 'padding': '0px', - 'padding-right': '5px', - 'padding-top': '10px', - 'height': '10px', - 'margin': '0px' - } + CSSStyles: {} }); return showMoreComponent; @@ -286,7 +276,6 @@ export class DashboardWidget { const videosContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: maxWidth, - height: maxWidth, justifyContent: 'flex-start' }).component(); const video1Container = view.modelBuilder.divContainer().withProperties({ @@ -299,7 +288,7 @@ export class DashboardWidget { width: maxWidth, height: '50px', CSSStyles: { - 'font-size': '12px', + 'font-size': '13px', 'margin': '0px' } }).component(); @@ -310,11 +299,11 @@ export class DashboardWidget { }); videosContainer.addItem(video1Container, { CSSStyles: { - 'background-image': `url(${vscode.Uri.file(this.asAbsolutePath(linkMetaData.iconPath?.light || ''))})`, + 'background-image': `url(${vscode.Uri.file(this.asAbsolutePath(linkMetaData.iconPath?.light as string || ''))})`, 'background-repeat': 'no-repeat', 'background-position': 'top', 'width': `${maxWidth}px`, - 'height': '110px', + 'height': '104px', 'background-size': `${maxWidth}px 120px` } }); @@ -384,20 +373,20 @@ export class DashboardWidget { linksContainer.addItems(links.map(l => this.createLink(view, l)), { CSSStyles: styles }); - moreLinksContainer.addItems(moreLinks.map(l => this.createLink(view, l))); + moreLinksContainer.addItems(moreLinks.map(l => this.createLink(view, l)), { + CSSStyles: styles + }); - this.addShowMorePanel(view, linksContainer, moreLinksContainer, { 'padding-left': '10px' }, styles); + this.addShowMorePanel(view, linksContainer, moreLinksContainer, { 'padding-left': '10px', 'padding-top': '10px' }, {}); return linksContainer; } private createLink(view: azdata.ModelView, linkMetaData: IActionMetadata): azdata.Component { - const maxHeight = 80; const maxWidth = 400; const labelsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: maxWidth, - height: maxHeight, justifyContent: 'flex-start' }).component(); const descriptionComponent = view.modelBuilder.text().withProperties({ @@ -405,6 +394,7 @@ export class DashboardWidget { width: maxWidth, CSSStyles: { 'font-size': '12px', + 'line-height': '16px', 'margin': '0px' } }).component(); @@ -433,26 +423,20 @@ export class DashboardWidget { }).component(); linkContainer.addItem(linkComponent, { CSSStyles: { - 'padding': '0px', - 'padding-right': '5px', - 'margin': '0px', - 'color': '#006ab1' + 'font-size': '14px', + 'line-height': '18px', + 'padding': '0 5px 0 0', } }); linkContainer.addItem(image, { CSSStyles: { - 'padding': '0px', - 'padding-right': '5px', - 'padding-top': '5px', + 'padding': '5px 5px 0 0', 'height': '10px', - 'margin': '0px' } }); labelsContainer.addItems([linkContainer, descriptionComponent], { CSSStyles: { - 'padding': '0px', - 'padding-top': '5px', - 'margin': '0px' + 'padding': '5px 0 0 0', } }); @@ -466,8 +450,8 @@ export class DashboardWidget { private async createTasks(view: azdata.ModelView): Promise { const tasksContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', + height: '84px', width: '100%', - height: '50px', }).component(); const predictionMetadata: IActionMetadata = { title: constants.makePredictionTitle, @@ -502,11 +486,8 @@ export class DashboardWidget { command: constants.notebookCommandNew }; const notebookModelsButton = this.createTaskButton(view, notebookMetadata); - tasksContainer.addItems([predictionButton, importModelsButton, notebookModelsButton], { - CSSStyles: { - 'padding': '10px' - } - }); + tasksContainer.addItems([predictionButton, importModelsButton, notebookModelsButton]); + if (!await this._predictService.serverSupportOnnxModel()) { console.log(constants.onnxNotSupportedError); } @@ -515,87 +496,24 @@ export class DashboardWidget { } private createTaskButton(view: azdata.ModelView, taskMetaData: IActionMetadata): azdata.Component { - const maxHeight = 116; - const maxWidth = 250; - const mainContainer = view.modelBuilder.divContainer().withLayout({ - width: maxWidth, - height: maxHeight - }).withProperties({ - clickable: true, - ariaRole: taskMetaData.title - }).component(); - const iconContainer = view.modelBuilder.flexContainer().withLayout({ - flexFlow: 'row', - width: maxWidth, - height: maxHeight - 23, - alignItems: 'flex-start' - }).component(); - const labelsContainer = view.modelBuilder.flexContainer().withLayout({ - flexFlow: 'column', - width: maxWidth - 50, - height: maxHeight - 20, - justifyContent: 'space-between' - }).component(); - const titleComponent = view.modelBuilder.text().withProperties({ - value: taskMetaData.title, - CSSStyles: { - 'font-size': '14px', - 'font-weight': 'bold', - 'margin': '0px' - } - }).component(); - const descriptionComponent = view.modelBuilder.text().withProperties({ - value: taskMetaData.description, - CSSStyles: { - 'font-size': '13px', - 'margin': '0px' - } - }).component(); - const linkComponent = view.modelBuilder.hyperlink().withProperties({ - label: constants.learnMoreTitle, - url: taskMetaData.link - }).component(); - const image = view.modelBuilder.image().withProperties({ - width: '20px', - height: '20px', + const maxHeight: number = 84; + const maxWidth: number = 236; + const buttonContainer = view.modelBuilder.button().withProperties({ + buttonType: azdata.ButtonType.Informational, + description: taskMetaData.description, + height: maxHeight, + iconHeight: 32, iconPath: taskMetaData.iconPath, - iconHeight: '20px', - iconWidth: '20px' + iconWidth: 32, + label: taskMetaData.title, + title: taskMetaData.title, + width: maxWidth, }).component(); - labelsContainer.addItems([titleComponent, descriptionComponent, linkComponent], { - CSSStyles: { - 'padding': '0px', - 'padding-bottom': '5px', - 'width': '200px', - 'margin': '0px', - 'color': '#006ab1' - } - }); - iconContainer.addItem(image, { - CSSStyles: { - 'padding-top': '10px', - 'padding-right': '10px' - } - }); - iconContainer.addItem(labelsContainer, { - CSSStyles: { - 'padding-top': '5px', - 'padding-right': '10px' - } - }); - mainContainer.addItems([iconContainer], { - CSSStyles: { - 'padding': '10px', - 'border-radius': '5px', - 'border-color': '#f2f2f2', - 'border': '1px solid' - } - }); - mainContainer.onDidClick(async () => { - if (mainContainer.enabled && taskMetaData.command) { + buttonContainer.onDidClick(async () => { + if (buttonContainer.enabled && taskMetaData.command) { await this._apiWrapper.executeCommand(taskMetaData.command); } }); - return mainContainer; + return view.modelBuilder.divContainer().withItems([buttonContainer]).component(); } } diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index 25f1aeb49e..b1841d7539 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -691,6 +691,24 @@ declare module 'azdata' { */ delete?: boolean; } + + export interface ButtonProperties { + /** + * Specifies whether to use expanded layout or not. + */ + buttonType?: ButtonType; + /** + * Description text to display inside button element. + */ + description?: string; + } + + export enum ButtonType { + File = 'File', + Normal = 'Normal', + Informational = 'Informational' + } + export interface DiffEditorComponent { /** * Title of editor diff --git a/src/sql/base/browser/ui/button/button.ts b/src/sql/base/browser/ui/button/button.ts index 0bcf60797e..2dc1a43e57 100644 --- a/src/sql/base/browser/ui/button/button.ts +++ b/src/sql/base/browser/ui/button/button.ts @@ -12,7 +12,7 @@ export interface IButtonStyles extends vsIButtonStyles { } export class Button extends vsButton { - private buttonFocusOutline?: Color; + protected buttonFocusOutline?: Color; constructor(container: HTMLElement, options?: IButtonOptions) { super(container, options); diff --git a/src/sql/base/browser/ui/infoButton/infoButton.css b/src/sql/base/browser/ui/infoButton/infoButton.css new file mode 100644 index 0000000000..a47ed12d62 --- /dev/null +++ b/src/sql/base/browser/ui/infoButton/infoButton.css @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.info-button-container { + display: flex; + justify-content: space-around; +} +.info-button .info-main { + cursor: pointer; + padding: 10px; +} +.info-button .info-icon { + align-items: flex-start; + display: flex; + flex-flow: column; + padding-right: 10px; +} +.info-button .info-text { + display: flex; + flex-flow: column; + justify-content: space-between; + padding: 0 0 0 10px; + margin: 0px; +} +.info-button .info-text p.info-title { + font-size: 14px; + font-weight: bold; + line-height: 20px; + margin: 0px; +} +.info-button .info-text p.info-desc { + font-size: 12px; + line-height: 16px; + margin: 0px; +} + +.info-button { + background-color: #fff; + border-radius: 2px; + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.13); + display: inline-block; +} +.info-button[tabindex="0"]:active { + background-color: #fff; + box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.13); +} +.vs-dark .info-button { + background-color: #1b1a19; + box-shadow: 0px 1px 4px rgba(255, 255, 255, 0.13); +} +.vs-dark .info-button[tabindex="0"]:active { + background-color: #1b1a19; + box-shadow: 0px 3px 8px rgba(255, 255, 255, 0.13); +} +.hc-black .info-button { + background-color: #000; + box-shadow: none; +} +.hc-black .info-button[tabindex="0"]:active { + background-color: #000; +} +.info-button:hover { + box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.13); +} +.vs-dark .info-button:hover { + box-shadow: 0px 3px 8px rgba(255, 255, 255, 0.13); +} +.hc-black .info-button[tabindex="0"]:hover { + border-style: dashed !important; + border-color: rgb(243, 133, 24) !important; +} diff --git a/src/sql/base/browser/ui/infoButton/infoButton.ts b/src/sql/base/browser/ui/infoButton/infoButton.ts new file mode 100644 index 0000000000..6ea1e35955 --- /dev/null +++ b/src/sql/base/browser/ui/infoButton/infoButton.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./infoButton'; +import { Button as sqlButton } from 'sql/base/browser/ui/button/button'; +import * as DOM from 'vs/base/browser/dom'; +import { IButtonOptions } from 'vs/base/browser/ui/button/button'; + +export interface IInfoButtonOptions extends IButtonOptions { + buttonMaxHeight: number, + buttonMaxWidth: number, + description: string, + iconClass: string, + iconHeight: number, + iconWidth: number, + title: string, +} + +export class InfoButton extends sqlButton { + private _container: HTMLElement; + private _main: HTMLElement; + private _iconContainer: HTMLElement; + private _iconElement: HTMLElement; + private _textContainer: HTMLElement; + private _pTitle: HTMLElement; + private _pDesc: HTMLElement; + + private _buttonMaxHeight?: number; + private _buttonMaxWidth?: number; + private _description?: string; + private _iconClass?: string; + private _iconHeight?: number; + private _iconWidth?: number; + private _title?: string; + + constructor(container: HTMLElement, options?: IInfoButtonOptions) { + super(container, options); + this._container = container; + + DOM.addClass(this._container, 'info-button-container'); + + this._main = document.createElement('div'); + DOM.addClass(this._main, 'flexContainer'); + DOM.addClass(this._main, 'info-main'); + + this._iconContainer = document.createElement('div'); + DOM.addClass(this._iconContainer, 'info-icon'); + this._iconContainer.style.alignItems = 'flex-start'; + + this._iconElement = document.createElement('div'); + DOM.addClass(this._iconElement, 'icon'); + + this._textContainer = document.createElement('div'); + DOM.addClass(this._textContainer, 'info-text'); + + this._pTitle = document.createElement('p'); + DOM.addClass(this._pTitle, 'info-title'); + this._pTitle.setAttribute('aria-hidden', 'false'); + + this._pDesc = document.createElement('p'); + DOM.addClass(this._pDesc, 'info-desc'); + this._pDesc.setAttribute('aria-hidden', 'false'); + + this._textContainer.appendChild(this._pTitle); + this._textContainer.appendChild(this._pDesc); + + this._iconContainer.appendChild(this._iconElement); + + this._main.appendChild(this._iconContainer); + this._main.appendChild(this._textContainer); + + DOM.addClass(this.element, 'info-button'); + this.element.appendChild(this._main); + this.element.style.background = 'none'; + + this.infoButtonOptions = options; + } + + public get title(): string { + return this._title!; + } + public set title(value: string) { + this._title! = value; + this._pTitle.innerText = this.title; + } + + public get description(): string { + return this._description!; + } + public set description(value: string) { + this._description! = value; + this._pDesc.innerText = this.description; + } + + public get buttonMaxHeight(): number { + return this._buttonMaxHeight!; + } + public set buttonMaxHeight(value: number) { + this._buttonMaxHeight! = value; + this._main.style.maxHeight = `${this._buttonMaxHeight!}px`; + this._iconContainer.style.height = `${this._buttonMaxHeight! - 20}px`; + this._textContainer.style.height = `${this._buttonMaxHeight! - 20}px`; + } + + public get buttonMaxWidth(): number { + return this._buttonMaxWidth!; + } + public set buttonMaxWidth(value: number) { + this._buttonMaxWidth! = value; + this._main.style.width = `${this._buttonMaxWidth!}px`; + this._textContainer.style.width = `${this._buttonMaxWidth! - this._iconWidth!}px`; + } + + public get iconHeight(): number { + return this._iconHeight!; + } + public set iconHeight(value: number) { + this._iconHeight! = value; + this._iconElement.style.height = `${this._iconHeight!}px`; + } + + public get iconWidth(): number { + return this._iconWidth!; + } + public set iconWidth(value: number) { + this._iconWidth! = value; + this._iconContainer.style.width = `${this._iconWidth!}px`; + this._iconElement.style.width = `${this._iconWidth!}px`; + } + + public get iconClass(): string { + return this._iconClass!; + } + public set iconClass(value: string) { + this._iconClass! = value; + DOM.addClass(this._iconElement, this._iconClass!); + } + + public set infoButtonOptions(options: IInfoButtonOptions | undefined) { + if (!options) { + return; + } + this.buttonMaxHeight = options.buttonMaxHeight; + this.buttonMaxWidth = options.buttonMaxWidth; + this.description = options.description; + this.iconHeight = options.iconHeight; + this.iconWidth = options.iconWidth; + this.iconClass = options.iconClass; + this.title = options.title; + } +} diff --git a/src/sql/base/browser/ui/panel/media/panel.css b/src/sql/base/browser/ui/panel/media/panel.css index d56a37c745..57e7b02c9d 100644 --- a/src/sql/base/browser/ui/panel/media/panel.css +++ b/src/sql/base/browser/ui/panel/media/panel.css @@ -58,7 +58,7 @@ panel { max-width: 100px; } -.tabbedPanel.vertical >.title .tabList .tab .tabLabel { +.tabbedPanel.vertical > .title .tabList .tab .tabLabel { font-size: 13px; padding-bottom: 0px; max-width: 200px; @@ -70,6 +70,20 @@ panel { font-weight: 600; } +.tabbedPanel .tabList .tab-header:hover:not(.active) { + background-color: #dcdcdc; + outline: none; +} +.vs-dark .tabbedPanel .tabList .tab-header:hover:not(.active) { + background-color: #2a2d2e; + outline: none; +} +.hc-black .tabbedPanel .tabList .tab-header:hover:not(.active) { + background-color: initial; + outline: 1px dashed #f38518; + outline-offset: -3px; +} + .tabbedPanel.horizontal > .title .tabList .tab .tabLabel, .tabbedPanel.vertical > .title .tabList .tab .tabLabel { text-overflow: ellipsis; diff --git a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts index 429bf9d876..c16567ec6d 100644 --- a/src/sql/workbench/api/common/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/common/sqlExtHost.api.impl.ts @@ -539,6 +539,7 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp return { accounts, + ButtonType: sqlExtHostTypes.ButtonType, connection, credentials, objectexplorer: objectExplorer, diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 4598e4fb79..15a0815c2f 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -862,3 +862,9 @@ export enum SqlAssessmentResultItemKind { Warning = 1, Error = 2 } + +export enum ButtonType { + File = 'File', + Normal = 'Normal', + Informational = 'Informational' +} diff --git a/src/sql/workbench/browser/modelComponents/button.component.ts b/src/sql/workbench/browser/modelComponents/button.component.ts index b723f70ab1..76cf403695 100644 --- a/src/sql/workbench/browser/modelComponents/button.component.ts +++ b/src/sql/workbench/browser/modelComponents/button.component.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/button'; import { Component, Input, Inject, ChangeDetectorRef, forwardRef, - ViewChild, ElementRef, OnDestroy, AfterViewInit + ViewChild, ElementRef, OnDestroy } from '@angular/core'; import * as azdata from 'azdata'; @@ -17,30 +17,40 @@ import { SIDE_BAR_BACKGROUND, SIDE_BAR_TITLE_FOREGROUND } from 'vs/workbench/com import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; import { Button } from 'sql/base/browser/ui/button/button'; +import { InfoButton } from 'sql/base/browser/ui/infoButton/infoButton'; import { Color } from 'vs/base/common/color'; import { IComponentDescriptor, IComponent, IModelStore, ComponentEventType } from 'sql/platform/dashboard/browser/interfaces'; import { convertSize } from 'sql/base/browser/dom'; +import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils'; @Component({ selector: 'modelview-button', template: ` -
+
+ -
+ + +
+
` }) -export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit { + +export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy { @Input() descriptor: IComponentDescriptor; @Input() modelStore: IModelStore; - private _button: Button; + private _button: Button | InfoButton; public fileType: string = '.sql'; + private _currentButtonType?: azdata.ButtonType = undefined; @ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef; @ViewChild('fileInput', { read: ElementRef }) private _fileInputContainer: ElementRef; + @ViewChild('infoButton', { read: ElementRef }) private _infoButtonContainer: ElementRef; + constructor( @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @@ -53,75 +63,97 @@ export default class ButtonComponent extends ComponentWithIconBase { - if (this._fileInputContainer) { - const self = this; - this._fileInputContainer.nativeElement.onchange = () => { - let file = self._fileInputContainer.nativeElement.files[0]; - let reader = new FileReader(); - reader.onload = (e) => { - let text = (e.target).result; - self.fileContent = text.toString(); - self.fireEvent({ - eventType: ComponentEventType.onDidClick, - args: { - filePath: file.path, - fileContent: self.fileContent - } - }); - }; - reader.readAsText(file); - }; - } else { - this.fireEvent({ - eventType: ComponentEventType.onDidClick, - args: e - }); - } - })); - } - } - ngOnDestroy(): void { this.baseDestroy(); } - /// IComponent implementation - public setLayout(layout: any): void { - // TODO allow configuring the look and feel this.layout(); } + private initButton(): void { + this._currentButtonType = this.buttonType; + const elementToRemove = this._button?.element; + if (this._inputContainer) { + this._button = new Button(this._inputContainer.nativeElement); + } else if (this._infoButtonContainer) { + this._button = new InfoButton(this._infoButtonContainer.nativeElement); + } + + // remove the previously created element if any. + if (elementToRemove) { + const container = this._inputContainer || this._infoButtonContainer; + (container.nativeElement as HTMLElement)?.removeChild(elementToRemove); + } + + this._register(this._button); + this._register(attachButtonStyler(this._button, this.themeService, { + buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND, buttonForeground: SIDE_BAR_TITLE_FOREGROUND + })); + this._register(this._button.onDidClick(e => { + if (this._fileInputContainer) { + const self = this; + this._fileInputContainer.nativeElement.onchange = () => { + let file = self._fileInputContainer.nativeElement.files[0]; + let reader = new FileReader(); + reader.onload = (e) => { + let text = (e.target as FileReader).result; + self.fileContent = text.toString(); + self.fireEvent({ + eventType: ComponentEventType.onDidClick, + args: { + filePath: file.path, + fileContent: self.fileContent + } + }); + }; + reader.readAsText(file); + }; + } else { + this.fireEvent({ + eventType: ComponentEventType.onDidClick, + args: e + }); + } + })); + } public setProperties(properties: { [key: string]: any; }): void { super.setProperties(properties); - this._button.enabled = this.enabled; - this._button.label = this.label; - if (this.properties.fileType) { - this.fileType = properties.fileType; + if (this._currentButtonType !== this.buttonType) { + this.initButton(); } - this._button.title = this.title; + if (this._infoButtonContainer) { + let button = this._button as InfoButton; + button.buttonMaxHeight = this.properties.height; + button.buttonMaxWidth = this.properties.width; + button.description = this.properties.description; + button.iconClass = createIconCssClass(this.properties.iconPath); + button.iconHeight = this.properties.iconHeight; + button.iconWidth = this.properties.iconWidth; + button.title = this.properties.title; + } else { + this._button.enabled = this.enabled; + this._button.label = this.label; - // Button's ariaLabel gets set to the label by default. - // We only want to override that if buttonComponent's ariaLabel is set explicitly. - if (this.ariaLabel) { - this._button.ariaLabel = this.ariaLabel; + if (this.properties.fileType) { + this.fileType = properties.fileType; + } + this._button.title = this.title; + + // Button's ariaLabel gets set to the label by default. + // We only want to override that if buttonComponent's ariaLabel is set explicitly. + if (this.ariaLabel) { + this._button.ariaLabel = this.ariaLabel; + } + + if (this.width) { + this._button.setWidth(convertSize(this.width.toString())); + } + if (this.height) { + this._button.setHeight(convertSize(this.height.toString())); + } } - if (this.width) { - this._button.setWidth(convertSize(this.width.toString())); - } - if (this.height) { - this._button.setHeight(convertSize(this.height.toString())); - } this.updateIcon(); this._changeRef.detectChanges(); } @@ -158,6 +190,18 @@ export default class ButtonComponent extends ComponentWithIconBase(this.setValueProperties, newValue); } + public get buttonType(): azdata.ButtonType { + if (this.isFile === true) { + return 'File' as azdata.ButtonType; + } else { + return this.getPropertyOrDefault((props) => props.buttonType, 'Normal' as azdata.ButtonType); + } + } + + public get description(): string { + return this.getPropertyOrDefault((props) => props.description, ''); + } + public get isFile(): boolean { return this.getPropertyOrDefault((props) => props.isFile, false); }