diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index 8448166aa2..fb7ca064b0 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -1033,6 +1033,11 @@ export interface TableDesignerEditRequestParams { data: azdata.designers.DesignerData } +export interface SaveTableDesignerChangesRequestParams { + tableInfo: azdata.designers.TableInfo, + data: azdata.designers.DesignerData +} + export namespace GetTableDesignerInfoRequest { export const type = new RequestType('tabledesigner/gettabledesignerinfo'); } @@ -1041,4 +1046,8 @@ export namespace ProcessTableDesignerEditRequest { export const type = new RequestType('tabledesigner/processedit'); } +export namespace SaveTableDesignerChangesRequest { + export const type = new RequestType('tabledesigner/savechanges'); +} + // ------------------------------- < Table Designer > ------------------------------------ diff --git a/extensions/mssql/src/features.ts b/extensions/mssql/src/features.ts index e4a41c6e0e..7352159778 100644 --- a/extensions/mssql/src/features.ts +++ b/extensions/mssql/src/features.ts @@ -1108,7 +1108,7 @@ export class TableDesignerFeature extends SqlOpsFeature { protected registerProvider(options: undefined): Disposable { const client = this._client; - const getTableDesignerInfo = async (tableInfo: azdata.designers.TableInfo): Promise => { + const getTableDesignerInfo = (tableInfo: azdata.designers.TableInfo): Thenable => { try { return client.sendRequest(contracts.GetTableDesignerInfoRequest.type, tableInfo); } @@ -1117,7 +1117,7 @@ export class TableDesignerFeature extends SqlOpsFeature { return Promise.reject(e); } }; - const processTableEdit = async (tableInfo: azdata.designers.TableInfo, data: azdata.designers.DesignerData, tableChangeInfo: azdata.designers.DesignerEdit): Promise => { + const processTableEdit = (tableInfo: azdata.designers.TableInfo, data: azdata.designers.DesignerData, tableChangeInfo: azdata.designers.DesignerEdit): Thenable => { let params: contracts.TableDesignerEditRequestParams = { tableInfo: tableInfo, data: data, @@ -1132,10 +1132,25 @@ export class TableDesignerFeature extends SqlOpsFeature { } }; + const saveTable = (tableInfo: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable => { + let params: contracts.SaveTableDesignerChangesRequestParams = { + tableInfo: tableInfo, + data: data + }; + try { + return client.sendRequest(contracts.SaveTableDesignerChangesRequest.type, params); + } + catch (e) { + client.logFailedRequest(contracts.SaveTableDesignerChangesRequest.type, e); + return Promise.reject(e); + } + }; + return azdata.dataprotocol.registerTableDesignerProvider({ providerId: client.providerId, getTableDesignerInfo, - processTableEdit + processTableEdit, + saveTable }); } } diff --git a/extensions/mssql/src/tableDesigner/tableDesigner.ts b/extensions/mssql/src/tableDesigner/tableDesigner.ts index e2708b2a4e..a63bb37af9 100644 --- a/extensions/mssql/src/tableDesigner/tableDesigner.ts +++ b/extensions/mssql/src/tableDesigner/tableDesigner.ts @@ -18,12 +18,17 @@ export function registerTableDesignerCommands(appContext: AppContext) { })); appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.designTable', async (context: azdata.ObjectExplorerContext) => { + const server = context.connectionProfile.serverName; + const database = context.connectionProfile.databaseName; + const schema = context.nodeInfo.metadata.schema; + const name = context.nodeInfo.metadata.name; await azdata.designers.openTableDesigner(sqlProviderName, { - server: context.connectionProfile.serverName, - database: context.connectionProfile.databaseName, + server: server, + database: database, isNewTable: false, - name: context.nodeInfo.metadata.name, - schema: context.nodeInfo.metadata.schema + name: name, + schema: schema, + id: `${server}|${database}|${schema}|${name}` }); })); diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index b0cde5618e..b83355cdd6 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -1015,12 +1015,19 @@ declare module 'azdata' { */ getTableDesignerInfo(table: TableInfo): Thenable; /** - * + * Process the table change. * @param table the table information * @param data the object contains the state of the table designer * @param tableChangeInfo the information about the change user made through the UI. */ processTableEdit(table: TableInfo, data: DesignerData, tableChangeInfo: DesignerEdit): Thenable; + + /** + * Save the table + * @param table the table information + * @param data the object contains the state of the table designer + */ + saveTable(table: TableInfo, data: DesignerData): Thenable; } /** @@ -1047,6 +1054,10 @@ declare module 'azdata' { * A boolean value indicates whether a new table is being designed. */ isNewTable: boolean; + /** + * If this is not a new table, the id will be set to uniquely identify a table. + */ + id?: string; /** * Extension can store additional information that the provider needs to uniquely identify a table. */ @@ -1095,7 +1106,8 @@ declare module 'azdata' { DefaultValue = 'defaultValue', Length = 'length', Name = 'name', - Type = 'type' + Type = 'type', + IsPrimaryKey = 'isPrimaryKey' } /** @@ -1229,7 +1241,7 @@ declare module 'azdata' { /** * the new value */ - value: any; + value?: any; } /** diff --git a/src/sql/base/browser/ui/designer/designer.ts b/src/sql/base/browser/ui/designer/designer.ts index 40793a2521..8a0164e967 100644 --- a/src/sql/base/browser/ui/designer/designer.ts +++ b/src/sql/base/browser/ui/designer/designer.ts @@ -23,6 +23,9 @@ import { TableCellEditorFactory } from 'sql/base/browser/ui/table/tableCellEdito import { CheckBoxColumn } from 'sql/base/browser/ui/table/plugins/checkboxColumn.plugin'; import { DesignerTabPanelView } from 'sql/base/browser/ui/designer/designerTabPanelView'; import { DesignerPropertiesPane, PropertiesPaneObjectContext } from 'sql/base/browser/ui/designer/designerPropertiesPane'; +import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button'; +import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin'; +import { Codicon } from 'vs/base/common/codicons'; export interface IDesignerStyle { tabbedPanelStyles?: ITabbedPanelStyles; @@ -30,6 +33,7 @@ export interface IDesignerStyle { tableStyles?: ITableStyles; selectBoxStyles?: ISelectBoxStyles; checkboxStyles?: ICheckboxStyles; + buttonStyles?: IButtonStyles; } export type DesignerUIComponent = InputBox | Checkbox | Table | SelectBox; @@ -38,7 +42,6 @@ export type CreateComponentFunc = (container: HTMLElement, component: DesignerDa export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerData) => void; export class Designer extends Disposable implements IThemable { - private _horizontalSplitViewContainer: HTMLElement; private _verticalSplitViewContainer: HTMLElement; private _tabbedPanelContainer: HTMLElement; @@ -55,6 +58,7 @@ export class Designer extends Disposable implements IThemable { private _input: DesignerComponentInput; private _tableCellEditorFactory: TableCellEditorFactory; private _propertiesPane: DesignerPropertiesPane; + private _buttons: Button[] = []; constructor(private readonly _container: HTMLElement, private readonly _contextViewProvider: IContextViewProvider) { @@ -144,7 +148,7 @@ export class Designer extends Disposable implements IThemable { this._editorContainer.appendChild(editor); } - private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table | SelectBox): void { + private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table | SelectBox | Button): void { if (component instanceof InputBox) { component.style(this._styles.inputBoxStyles); } else if (component instanceof Checkbox) { @@ -153,10 +157,13 @@ export class Designer extends Disposable implements IThemable { component.style(this._styles.tabbedPanelStyles); } else if (component instanceof Table) { component.style(this._styles.tableStyles); + } else if (component instanceof Button) { + component.style(this._styles.buttonStyles); } else { component.style(this._styles.selectBoxStyles); } } + public style(styles: IDesignerStyle): void { this._styles = styles; this._componentMap.forEach((value, key, map) => { @@ -172,6 +179,10 @@ export class Designer extends Disposable implements IThemable { this._horizontalSplitView.style({ separatorBorder: styles.selectBoxStyles.selectBorder }); + + this._buttons.forEach((button) => { + this.styleComponent(button); + }); } public layout(dimension: DOM.Dimension) { @@ -206,19 +217,22 @@ export class Designer extends Disposable implements IThemable { this._tabbedPanel.layout(new DOM.Dimension(this._tabbedPanelContainer.clientWidth, this._tabbedPanelContainer.clientHeight)); } - private async updateComponentValues(): Promise { + private async updatePropertiesPane(newContext: PropertiesPaneObjectContext): Promise { const data = await this._input.getData(); - // data[ScriptPropertyName] -- todo- set the script editor - this._componentMap.forEach((value) => { - this.setComponentValue(value.defintion, value.component, data); - }); - let type: string; let components: DesignerDataPropertyInfo[]; let inputData: DesignerData; let context: PropertiesPaneObjectContext; - const currentContext = this._propertiesPane.context; - if (currentContext === 'root' || currentContext === undefined) { + if (newContext !== 'root') { + context = newContext; + const tableData = data[newContext.parentProperty] as DesignerTableProperties; + const tableProperties = this._componentMap.get(newContext.parentProperty).defintion.componentProperties as DesignerTableProperties; + inputData = tableData.data[newContext.index] as DesignerData; + components = tableProperties.itemProperties; + type = tableProperties.objectTypeDisplayName; + } + + if (!inputData) { context = 'root'; components = []; this._componentMap.forEach(value => { @@ -226,20 +240,25 @@ export class Designer extends Disposable implements IThemable { }); type = this._input.objectTypeDisplayName; inputData = data; - } else { - context = currentContext; - const tableData = data[currentContext.parentProperty] as DesignerTableProperties; - const tableProperties = this._componentMap.get(currentContext.parentProperty).defintion.componentProperties as DesignerTableProperties; - inputData = tableData.data[currentContext.index] as DesignerData; - components = tableProperties.itemProperties; - type = tableProperties.objectTypeDisplayName; } - this._propertiesPane.show({ - context: context, - type: type, - components: components, - data: inputData + + if (inputData) { + this._propertiesPane.show({ + context: context, + type: type, + components: components, + data: inputData + }); + } + } + + private async updateComponentValues(): Promise { + const data = await this._input.getData(); + // data[ScriptPropertyName] -- todo- set the script editor + this._componentMap.forEach((value) => { + this.setComponentValue(value.defintion, value.component, data); }); + await this.updatePropertiesPane(this._propertiesPane.context ?? 'root'); } private async handleEdit(edit: DesignerEdit): Promise { @@ -251,6 +270,14 @@ export class Designer extends Disposable implements IThemable { if (result.isValid) { this._supressEditProcessing = true; await this.updateComponentValues(); + if (edit.type === DesignerEditType.Add) { + // Move focus to the first cell of the newly added row. + const data = await this._input.getData(); + const propertyName = edit.property as string; + const tableData = data[propertyName] as DesignerTableProperties; + const table = this._componentMap.get(propertyName).component as Table; + table.setActiveCell(tableData.data.length - 1, 0); + } this._supressEditProcessing = false; } else { //TODO: add error notification @@ -319,9 +346,19 @@ export class Designer extends Disposable implements IThemable { case 'table': const table = component as Table; const tableDataView = table.getData() as TableDataView; + const newData = (data[definition.propertyName] as DesignerTableProperties).data; + let activeCell: Slick.Cell; + if (table.container.contains(document.activeElement)) { + // Note down the current active cell if the focus is currently in the table + // After the table is refreshed, the focus will be restored. + activeCell = Object.assign({}, table.activeCell); + } tableDataView.clear(); - tableDataView.push((data[definition.propertyName] as DesignerTableProperties).data); + tableDataView.push(newData); table.rerenderGrid(); + if (activeCell && newData.length > activeCell.row) { + table.setActiveCell(activeCell.row, activeCell.cell); + } break; case 'checkbox': const checkbox = component as Checkbox; @@ -356,15 +393,13 @@ export class Designer extends Disposable implements IThemable { } private createComponent(container: HTMLElement, componentDefinition: DesignerDataPropertyInfo, editIdentifier: DesignerEditIdentifier, addToComponentMap: boolean, setWidth: boolean): DesignerUIComponent { - const componentContainerClass = componentDefinition.componentType === 'table' ? '.full-row' : ''; - const labelContainer = container.appendChild(DOM.$(componentContainerClass)); - labelContainer.appendChild(DOM.$('span.component-label')).innerText = (componentDefinition.componentType === 'checkbox' || componentDefinition.componentProperties?.title === undefined) ? '' : componentDefinition.componentProperties.title; - const componentDiv = container.appendChild(DOM.$(componentContainerClass)); let component: DesignerUIComponent; switch (componentDefinition.componentType) { case 'input': + container.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? ''; + const inputContainer = container.appendChild(DOM.$('')); const inputProperties = componentDefinition.componentProperties as InputBoxProperties; - const input = new InputBox(componentDiv, this._contextViewProvider, { + const input = new InputBox(inputContainer, this._contextViewProvider, { ariaLabel: inputProperties.title, type: inputProperties.inputType, }); @@ -377,9 +412,11 @@ export class Designer extends Disposable implements IThemable { component = input; break; case 'dropdown': + container.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? ''; + const dropdownContainer = container.appendChild(DOM.$('')); const dropdownProperties = componentDefinition.componentProperties as DropDownProperties; const dropdown = new SelectBox(dropdownProperties.values as string[], undefined, this._contextViewProvider, undefined); - dropdown.render(componentDiv); + dropdown.render(dropdownContainer); dropdown.selectElem.style.height = '25px'; dropdown.onDidSelect(async (e) => { await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected }); @@ -387,8 +424,10 @@ export class Designer extends Disposable implements IThemable { component = dropdown; break; case 'checkbox': + container.appendChild(DOM.$('')); // label container place holder + const checkboxContainer = container.appendChild(DOM.$('')); const checkboxProperties = componentDefinition.componentProperties as CheckBoxProperties; - const checkbox = new Checkbox(componentDiv, { + const checkbox = new Checkbox(checkboxContainer, { label: checkboxProperties.title }); checkbox.onChange(async (newValue) => { @@ -398,17 +437,40 @@ export class Designer extends Disposable implements IThemable { break; case 'table': const tableProperties = componentDefinition.componentProperties as DesignerTableProperties; - const table = new Table(componentDiv, { + const buttonContainer = container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('.add-row-button-container')); + const addNewText = localize('designer.newRowText', "Add New"); + const addRowButton = new Button(buttonContainer, { + title: addNewText, + secondary: true + }); + addRowButton.onDidClick(async () => { + await this.handleEdit({ + type: DesignerEditType.Add, + property: componentDefinition.propertyName, + }); + }); + this.styleComponent(addRowButton); + addRowButton.label = addNewText; + addRowButton.icon = { + id: `add-row-button new codicon` + }; + this._buttons.push(addRowButton); + const tableContainer = container.appendChild(DOM.$('.full-row')); + const table = new Table(tableContainer, { dataProvider: new TableDataView() }, { editable: true, autoEdit: true, dataItemColumnValueExtractor: (data: any, column: Slick.Column): string => { - return data[column.field].value; + if (column.field) { + return data[column.field].value; + } else { + return undefined; + } } - } - ); - table.columns = tableProperties.columns.map(propName => { + }); + table.ariaLabel = tableProperties.ariaLabel; + const columns = tableProperties.columns.map(propName => { const propertyDefinition = tableProperties.itemProperties.find(item => item.propertyName === propName); switch (propertyDefinition.componentType) { case 'checkbox': @@ -448,20 +510,36 @@ export class Designer extends Disposable implements IThemable { }; } }); - table.layout(new DOM.Dimension(container.clientWidth, container.clientHeight)); + const deleteRowColumn = new ButtonColumn({ + id: 'deleteRow', + iconCssClass: Codicon.trash.classNames, + title: localize('designer.removeRowText', "Remove"), + width: 20, + resizable: false, + isFontIcon: true + }); + deleteRowColumn.onClick(async (e) => { + const data = await this._input.getData(); + (data[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1); + await this.handleEdit({ + type: DesignerEditType.Remove, + property: componentDefinition.propertyName, + value: e.item + }); + }); + table.registerPlugin(deleteRowColumn); + columns.push(deleteRowColumn.definition); + table.columns = columns; table.grid.onBeforeEditCell.subscribe((e, data): boolean => { return data.item[data.column.field].enabled !== false; }); - table.grid.onActiveCellChanged.subscribe((e, data) => { - this._propertiesPane.show({ - context: { + table.grid.onActiveCellChanged.subscribe(async (e, data) => { + if (data.row !== undefined) { + await this.updatePropertiesPane({ parentProperty: componentDefinition.propertyName, index: data.row - }, - type: tableProperties.objectTypeDisplayName, - components: tableProperties.itemProperties, - data: table.getData().getItem(data.row) - }); + }); + } }); component = table; break; diff --git a/src/sql/base/browser/ui/designer/designerTabPanelView.ts b/src/sql/base/browser/ui/designer/designerTabPanelView.ts index f675eafaef..04051c1e2f 100644 --- a/src/sql/base/browser/ui/designer/designerTabPanelView.ts +++ b/src/sql/base/browser/ui/designer/designerTabPanelView.ts @@ -10,6 +10,10 @@ import { Disposable } from 'vs/base/common/lifecycle'; import * as DOM from 'vs/base/browser/dom'; import { CreateComponentFunc } from 'sql/base/browser/ui/designer/designer'; +const ButtonHeight = 30; +const HorizontalPadding = 10; +const VerticalPadding = 20; + export class DesignerTabPanelView extends Disposable implements IPanelView { private _componentsContainer: HTMLElement; private _tables: Table[] = []; @@ -30,7 +34,7 @@ export class DesignerTabPanelView extends Disposable implements IPanelView { layout(dimension: DOM.Dimension): void { this._tables.forEach(table => { - table.layout(new DOM.Dimension(dimension.width - 10, dimension.height - 20)); + table.layout(new DOM.Dimension(dimension.width - HorizontalPadding, dimension.height - VerticalPadding - ButtonHeight)); }); } } diff --git a/src/sql/base/browser/ui/designer/interfaces.ts b/src/sql/base/browser/ui/designer/interfaces.ts index b164d4d4a0..7e58d7f5e6 100644 --- a/src/sql/base/browser/ui/designer/interfaces.ts +++ b/src/sql/base/browser/ui/designer/interfaces.ts @@ -3,7 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Event } from 'vs/base/common/event'; + export interface DesignerComponentInput { + /** + * The event that is triggerd when the designer state changes. + */ + readonly onStateChange: Event; + /** * Gets the object type display name. */ @@ -24,6 +31,27 @@ export interface DesignerComponentInput { * @param edit the information about the edit. */ processEdit(edit: DesignerEdit): Promise; + + /** + * A boolean value indicating whether the current state is valid. + */ + readonly valid: boolean; + + /** + * A boolean value indicating whether the current state is dirty. + */ + readonly dirty: boolean; + + /** + * A boolean value indicating whether the changes are being saved. + */ + readonly saving: boolean; +} + +export interface DesignerState { + valid: boolean; + dirty: boolean; + saving: boolean; } export const NameProperty = 'name'; @@ -115,7 +143,7 @@ export enum DesignerEditType { export interface DesignerEdit { type: DesignerEditType; property: DesignerEditIdentifier; - value: any; + value?: any; } export type DesignerEditIdentifier = string | { parentProperty: string, index: number, property: string }; diff --git a/src/sql/base/browser/ui/designer/media/designer.css b/src/sql/base/browser/ui/designer/media/designer.css index 7644b7d26b..6b437fcede 100644 --- a/src/sql/base/browser/ui/designer/media/designer.css +++ b/src/sql/base/browser/ui/designer/media/designer.css @@ -59,7 +59,8 @@ .designer-component .components-grid { display: grid; - grid-template-columns: max-content auto; /* label, component*/ + /* grid-template-columns: column 1 is for label, column 2 is for component.*/ + grid-template-columns: max-content auto; grid-template-rows: max-content; grid-gap: 5px; padding: 5px; @@ -68,10 +69,23 @@ } .designer-component .components-grid .full-row { - grid-area: span 1 / span 2; /* spans 1 row and 2 columns*/ + grid-area: span 1 / span 2; } .designer-component .monaco-table .slick-cell.editable { padding: 0px; border-width: 0px; } + +.designer-component .add-row-button-container { + display: flex; + flex-flow: row-reverse; +} + +.designer-component .add-row-button-container .codicon.add-row-button { + width: fit-content; + background-repeat: no-repeat; + background-size: 13px; + padding-left: 17px; + background-position: 2px center; +} diff --git a/src/sql/base/browser/ui/table/plugins/buttonColumn.plugin.ts b/src/sql/base/browser/ui/table/plugins/buttonColumn.plugin.ts index ca89a6cc40..2455945361 100644 --- a/src/sql/base/browser/ui/table/plugins/buttonColumn.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/buttonColumn.plugin.ts @@ -17,7 +17,7 @@ export interface ButtonColumnOptions extends IconColumnOptions { /** * Whether to show the text. */ - showText?: boolean + showText?: boolean; } export class ButtonColumn extends BaseClickableColumn { @@ -33,7 +33,10 @@ export class ButtonColumn extends BaseClickableColumn formatter: (row: number, cell: number, value: any, columnDef: Slick.Column, dataContext: T): string => { const iconValue = getIconCellValue(this.options, dataContext); const escapedTitle = escape(iconValue.title ?? ''); - const iconCssClasses = iconValue.iconCssClass ? `codicon icon slick-plugin-icon ${iconValue.iconCssClass}` : ''; + let iconCssClasses = ''; + if (iconValue.iconCssClass) { + iconCssClasses = this.options.isFontIcon ? iconValue.iconCssClass : `codicon icon slick-plugin-icon ${iconValue.iconCssClass}`; + } const buttonTypeCssClass = this.options.showText ? 'slick-plugin-button slick-plugin-text-button' : 'slick-plugin-button slick-plugin-image-only-button'; const buttonText = this.options.showText ? escapedTitle : ''; return ``; diff --git a/src/sql/base/browser/ui/table/plugins/tableColumn.ts b/src/sql/base/browser/ui/table/plugins/tableColumn.ts index bcf3a3eb70..c56097989b 100644 --- a/src/sql/base/browser/ui/table/plugins/tableColumn.ts +++ b/src/sql/base/browser/ui/table/plugins/tableColumn.ts @@ -140,6 +140,11 @@ export interface IconColumnOptions extends BaseTableColumnOptions { * The title for all the cells. If the 'field' is provided, the cell values will overwrite this value. */ title?: string + + /** + * Whether the icon is font icon. If true, no other class names will be auto appended. + */ + isFontIcon?: boolean; } export interface IconCellValue { diff --git a/src/sql/base/browser/ui/table/table.ts b/src/sql/base/browser/ui/table/table.ts index a78ccaf7a4..ea24ac7f9b 100644 --- a/src/sql/base/browser/ui/table/table.ts +++ b/src/sql/base/browser/ui/table/table.ts @@ -426,4 +426,8 @@ export class Table extends Widget implements IDisposa public set ariaLabel(value: string) { this._tableContainer.setAttribute('aria-label', value); } + + public get container(): HTMLElement { + return this._tableContainer; + } } diff --git a/src/sql/platform/theme/common/styler.ts b/src/sql/platform/theme/common/styler.ts index 78c59e943b..6657fc5905 100644 --- a/src/sql/platform/theme/common/styler.ts +++ b/src/sql/platform/theme/common/styler.ts @@ -8,7 +8,7 @@ import * as colors from './colors'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as cr from 'vs/platform/theme/common/colorRegistry'; import * as sqlcr from 'sql/platform/theme/common/colorRegistry'; -import { attachStyler, computeStyles, defaultListStyles, IColorMapping, IStyleOverrides } from 'vs/platform/theme/common/styler'; +import { attachStyler, computeStyles, defaultButtonStyles, defaultListStyles, IColorMapping, IStyleOverrides } from 'vs/platform/theme/common/styler'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IThemable } from 'vs/base/common/styler'; @@ -52,7 +52,7 @@ export interface IInputBoxStyleOverrides extends IStyleOverrides { inputValidationErrorBackground?: cr.ColorIdentifier } -export const defaultInputBoxStyleOverrides: IInputBoxStyleOverrides = { +export const defaultInputBoxStyles: IInputBoxStyleOverrides = { inputBackground: cr.inputBackground, inputForeground: cr.inputForeground, disabledInputBackground: colors.disabledInputBackground, @@ -67,7 +67,7 @@ export const defaultInputBoxStyleOverrides: IInputBoxStyleOverrides = { }; export function attachInputBoxStyler(widget: IThemable, themeService: IThemeService, style?: IInputBoxStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultInputBoxStyleOverrides, ...(style || {}) }, widget); + return attachStyler(themeService, { ...defaultInputBoxStyles, ...(style || {}) }, widget); } export interface ISelectBoxStyleOverrides extends IStyleOverrides { @@ -91,7 +91,7 @@ export interface ISelectBoxStyleOverrides extends IStyleOverrides { listHoverForeground?: cr.ColorIdentifier } -export const defaultSelectBoxStyleOverrides: ISelectBoxStyleOverrides = { +export const defaultSelectBoxStyles: ISelectBoxStyleOverrides = { selectBackground: cr.selectBackground, selectListBackground: cr.selectListBackground, selectForeground: cr.selectForeground, @@ -114,7 +114,7 @@ export const defaultSelectBoxStyleOverrides: ISelectBoxStyleOverrides = { }; export function attachSelectBoxStyler(widget: IThemable, themeService: IThemeService, style?: ISelectBoxStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultSelectBoxStyleOverrides, ...(style || {}) }, widget); + return attachStyler(themeService, { ...defaultSelectBoxStyles, ...(style || {}) }, widget); } export function attachListBoxStyler(widget: IThemable, themeService: IThemeService, style?: @@ -163,7 +163,7 @@ export interface ITableStyleOverrides extends IStyleOverrides { tableHeaderForeground?: cr.ColorIdentifier, } -export const defaultTableStyleOverrides: ITableStyleOverrides = { +export const defaultTableStyles: ITableStyleOverrides = { listFocusBackground: cr.listFocusBackground, listFocusForeground: cr.listFocusForeground, listActiveSelectionBackground: cr.listActiveSelectionBackground, @@ -185,7 +185,7 @@ export const defaultTableStyleOverrides: ITableStyleOverrides = { }; export function attachTableStyler(widget: IThemable, themeService: IThemeService, style?: ITableStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultTableStyleOverrides, ...(style || {}) }, widget); + return attachStyler(themeService, { ...defaultTableStyles, ...(style || {}) }, widget); } export interface IHighPerfTableStyleOverrides extends IStyleOverrides { @@ -267,7 +267,7 @@ export interface IEditableDropdownStyleOverrides extends IStyleOverrides { contextBorder?: cr.ColorIdentifier } -export const defaultEditableDropdownStyleOverrides: IEditableDropdownStyleOverrides = { +export const defaultEditableDropdownStyle: IEditableDropdownStyleOverrides = { listFocusBackground: cr.listFocusBackground, listFocusForeground: cr.listFocusForeground, listActiveSelectionBackground: cr.listActiveSelectionBackground, @@ -299,14 +299,14 @@ export const defaultEditableDropdownStyleOverrides: IEditableDropdownStyleOverri export function attachEditableDropdownStyler(widget: IThemable, themeService: IThemeService, style?: IEditableDropdownStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultEditableDropdownStyleOverrides, ...(style || {}) }, widget); + return attachStyler(themeService, { ...defaultEditableDropdownStyle, ...(style || {}) }, widget); } export interface ICheckboxStyleOverrides extends IStyleOverrides { disabledCheckboxForeground?: cr.ColorIdentifier } -export const defaultCheckboxStyleOverrides: ICheckboxStyleOverrides = { +export const defaultCheckboxStyles: ICheckboxStyleOverrides = { disabledCheckboxForeground: colors.disabledCheckboxForeground }; @@ -352,7 +352,7 @@ export function attachInfoButtonStyler(widget: IThemable, themeService: IThemeSe export function attachTableFilterStyler(widget: IThemable, themeService: IThemeService): IDisposable { return attachStyler(themeService, { - ...defaultInputBoxStyleOverrides, + ...defaultInputBoxStyles, buttonForeground: cr.buttonForeground, buttonBackground: cr.buttonBackground, buttonHoverBackground: cr.buttonHoverBackground, @@ -374,15 +374,17 @@ export function attachTableFilterStyler(widget: IThemable, themeService: IThemeS export function attachDesignerStyler(widget: any, themeService: IThemeService): IDisposable { function applyStyles(): void { const colorTheme = themeService.getColorTheme(); - const inputStyles = computeStyles(colorTheme, defaultInputBoxStyleOverrides); - const selectBoxStyles = computeStyles(colorTheme, defaultSelectBoxStyleOverrides); - const tableStyles = computeStyles(colorTheme, defaultTableStyleOverrides); - const checkboxStyles = computeStyles(colorTheme, defaultCheckboxStyleOverrides); + const inputStyles = computeStyles(colorTheme, defaultInputBoxStyles); + const selectBoxStyles = computeStyles(colorTheme, defaultSelectBoxStyles); + const tableStyles = computeStyles(colorTheme, defaultTableStyles); + const checkboxStyles = computeStyles(colorTheme, defaultCheckboxStyles); + const buttonStyles = computeStyles(colorTheme, defaultButtonStyles); widget.style({ inputBoxStyles: inputStyles, selectBoxStyles: selectBoxStyles, tableStyles: tableStyles, - checkboxStyles: checkboxStyles + checkboxStyles: checkboxStyles, + buttonStyles: buttonStyles }); } diff --git a/src/sql/workbench/api/browser/mainThreadDataProtocol.ts b/src/sql/workbench/api/browser/mainThreadDataProtocol.ts index f2b59de010..a7bff20580 100644 --- a/src/sql/workbench/api/browser/mainThreadDataProtocol.ts +++ b/src/sql/workbench/api/browser/mainThreadDataProtocol.ts @@ -512,11 +512,15 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData $registerTableDesignerProvider(providerId: string, handle: number): Promise { const self = this; this._tableDesignerService.registerProvider(providerId, { + providerId: providerId, getTableDesignerInfo(tableInfo: azdata.designers.TableInfo): Thenable { return self._proxy.$getTableDesignerInfo(handle, tableInfo); }, processTableEdit(table, data, edit): Thenable { return self._proxy.$processTableDesignerEdit(handle, table, data, edit); + }, + saveTable(tableInfo: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable { + return self._proxy.$saveTable(handle, tableInfo, data); } }); diff --git a/src/sql/workbench/api/common/extHostDataProtocol.ts b/src/sql/workbench/api/common/extHostDataProtocol.ts index 4fbe6da9c4..46f709d2c7 100644 --- a/src/sql/workbench/api/common/extHostDataProtocol.ts +++ b/src/sql/workbench/api/common/extHostDataProtocol.ts @@ -900,6 +900,10 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape { return this._resolveProvider(handle).processTableEdit(table, data, edit); } + public override $saveTable(handle, table: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable { + return this._resolveProvider(handle).saveTable(table, data); + } + public override $openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo): Promise { this._proxy.$openTableDesigner(providerId, tableInfo); return Promise.resolve(); diff --git a/src/sql/workbench/api/common/sqlExtHost.protocol.ts b/src/sql/workbench/api/common/sqlExtHost.protocol.ts index da1c28b6bb..2f50b76bd2 100644 --- a/src/sql/workbench/api/common/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/common/sqlExtHost.protocol.ts @@ -537,6 +537,11 @@ export abstract class ExtHostDataProtocolShape { */ $processTableDesignerEdit(handle, table: azdata.designers.TableInfo, data: azdata.designers.DesignerData, edit: azdata.designers.DesignerEdit): Thenable { throw ni(); } + /** + * Process the table edit. + */ + $saveTable(handle, table: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable { throw ni(); } + /** * Open a new instance of table designer. */ diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 1169a54785..d60b544512 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -921,7 +921,8 @@ export namespace designers { Type = 'type', AllowNulls = 'allowNulls', DefaultValue = 'defaultValue', - Length = 'length' + Length = 'length', + IsPrimaryKey = 'isPrimaryKey' } export enum DesignerEditType { diff --git a/src/sql/workbench/browser/editor/tableDesigner/tableDesignerInput.ts b/src/sql/workbench/browser/editor/tableDesigner/tableDesignerInput.ts index e6b2230578..ca7f24df91 100644 --- a/src/sql/workbench/browser/editor/tableDesigner/tableDesignerInput.ts +++ b/src/sql/workbench/browser/editor/tableDesigner/tableDesignerInput.ts @@ -9,16 +9,39 @@ import { URI } from 'vs/workbench/workbench.web.api'; import { TableDesignerComponentInput } from 'sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput'; import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface'; import * as azdata from 'azdata'; +import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; const NewTable: string = localize('tableDesigner.newTable', "New Table"); export class TableDesignerInput extends EditorInput { public static ID: string = 'workbench.editorinputs.tableDesignerInput'; private _designerComponentInput: TableDesignerComponentInput; - constructor(provider: TableDesignerProvider, - private _tableInfo: azdata.designers.TableInfo) { + private _name: string; + + constructor( + private _provider: TableDesignerProvider, + private _tableInfo: azdata.designers.TableInfo, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IEditorService editorService: IEditorService) { super(); - this._designerComponentInput = new TableDesignerComponentInput(provider, this._tableInfo); + this._designerComponentInput = this._instantiationService.createInstance(TableDesignerComponentInput, this._provider, this._tableInfo); + this._register(this._designerComponentInput.onStateChange((e) => { + this._onDidChangeDirty.fire(); + })); + const existingNames = editorService.editors.map(editor => editor.getName()); + + if (this._tableInfo.isNewTable) { + // Find the next available unique name for the new table designer + let idx = 1; + do { + this._name = `${this._tableInfo.server}.${this._tableInfo.database} - ${NewTable} ${idx}`; + idx++; + } while (existingNames.indexOf(this._name) !== -1); + } else { + this._name = `${this._tableInfo.server}.${this._tableInfo.database} - ${this._tableInfo.schema}.${this._tableInfo.name}`; + } } get typeId(): string { @@ -34,7 +57,33 @@ export class TableDesignerInput extends EditorInput { } override getName(): string { - const tableName = this._tableInfo.isNewTable ? NewTable : `${this._tableInfo.schema}.${this._tableInfo.name}`; - return `${this._tableInfo.server}.${this._tableInfo.database} - ${tableName}`; + return this._name; + } + + override isDirty(): boolean { + return this._designerComponentInput.dirty; + } + + override isSaving(): boolean { + return this._designerComponentInput.saving; + } + + override async save(group: GroupIdentifier, options?: ISaveOptions): Promise { + await this._designerComponentInput.save(); + return this; + } + + override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + await this._designerComponentInput.revert(); + } + + override matches(otherInput: any): boolean { + // For existing tables, the table designer provider will give us unique id, we can use it to do the match. + // For new tables, we can do the match using their names. + return otherInput instanceof TableDesignerInput + && this._provider.providerId === otherInput._provider.providerId + && this._tableInfo.isNewTable === otherInput._tableInfo.isNewTable + && (!this._tableInfo.isNewTable || this.getName() === otherInput.getName()) + && (this._tableInfo.isNewTable || this._tableInfo.id === otherInput._tableInfo.id); } } diff --git a/src/sql/workbench/contrib/tableDesigner/browser/actions.ts b/src/sql/workbench/contrib/tableDesigner/browser/actions.ts new file mode 100644 index 0000000000..bda7934e11 --- /dev/null +++ b/src/sql/workbench/contrib/tableDesigner/browser/actions.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TableDesignerComponentInput } from 'sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput'; +import { Action } from 'vs/base/common/actions'; +import { Codicon } from 'vs/base/common/codicons'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; + +export class SaveTableChangesAction extends Action { + public static ID = 'tableDesigner.saveTableChanges'; + public static LABEL = localize('tableDesigner.saveTableChanges', "Save Changes"); + private _input: TableDesignerComponentInput; + private _onStateChangeDisposable: IDisposable; + + constructor( + ) { + super(SaveTableChangesAction.ID, SaveTableChangesAction.LABEL, Codicon.save.classNames); + } + + public setContext(input: TableDesignerComponentInput): void { + this._input = input; + this.updateState(); + this._onStateChangeDisposable?.dispose(); + this._onStateChangeDisposable = input.onStateChange((e) => { + this.updateState(); + }); + } + + public override async run(): Promise { + await this._input.save(); + } + + private updateState(): void { + this.enabled = this._input.dirty && this._input.valid && !this._input.saving; + } + + override dispose() { + super.dispose(); + this._onStateChangeDisposable?.dispose(); + } +} diff --git a/src/sql/workbench/contrib/tableDesigner/browser/media/tableDesignerEditor.css b/src/sql/workbench/contrib/tableDesigner/browser/media/tableDesignerEditor.css new file mode 100644 index 0000000000..4d6a2c09c6 --- /dev/null +++ b/src/sql/workbench/contrib/tableDesigner/browser/media/tableDesignerEditor.css @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.table-designer-main-container { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +} + +.table-designer-main-container .actionbar-container { + flex: 0 0 auto; + padding: 5px; + border-width: 0 0 1px 0; + border-style: solid; + border-bottom-color: rgba(128, 128, 128, 0.35); +} + +.table-designer-main-container .designer-container { + flex: 1 1 auto; +} diff --git a/src/sql/workbench/contrib/tableDesigner/browser/tableDesignerEditor.ts b/src/sql/workbench/contrib/tableDesigner/browser/tableDesignerEditor.ts index 93e266070e..cf0ad43540 100644 --- a/src/sql/workbench/contrib/tableDesigner/browser/tableDesignerEditor.ts +++ b/src/sql/workbench/contrib/tableDesigner/browser/tableDesignerEditor.ts @@ -3,10 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/tableDesignerEditor'; import { Designer } from 'sql/base/browser/ui/designer/designer'; import { attachDesignerStyler } from 'sql/platform/theme/common/styler'; import { TableDesignerInput } from 'sql/workbench/browser/editor/tableDesigner/tableDesignerInput'; import * as DOM from 'vs/base/browser/dom'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; @@ -15,17 +17,21 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { SaveTableChangesAction } from 'sql/workbench/contrib/tableDesigner/browser/actions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class TableDesignerEditor extends EditorPane { public static readonly ID: string = 'workbench.editor.tableDesigner'; private _designer: Designer; + private _saveChangesAction: SaveTableChangesAction; constructor( @ITelemetryService telemetryService: ITelemetryService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @IStorageService storageService: IStorageService, - @IContextViewService private _contextViewService: IContextViewService + @IContextViewService private _contextViewService: IContextViewService, + @IInstantiationService private _instantiationService: IInstantiationService ) { super(TableDesignerEditor.ID, telemetryService, themeService, storageService); } @@ -36,12 +42,22 @@ export class TableDesignerEditor extends EditorPane { override async setInput(input: TableDesignerInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); - this._designer.setInput(input.getComponentInput()); + const designerInput = input.getComponentInput(); + this._designer.setInput(designerInput); + this._saveChangesAction.setContext(designerInput); } protected createEditor(parent: HTMLElement): void { // The editor is only created once per editor group. - this._designer = new Designer(parent, this._contextViewService); + const container = parent.appendChild(DOM.$('.table-designer-main-container')); + const actionbarContainer = container.appendChild(DOM.$('.actionbar-container')); + const designerContainer = container.appendChild(DOM.$('.designer-container')); + const actionbar = new ActionBar(actionbarContainer); + this._register(actionbar); + this._saveChangesAction = this._instantiationService.createInstance(SaveTableChangesAction); + this._saveChangesAction.enabled = false; + actionbar.push(this._saveChangesAction, { icon: true, label: false }); + this._designer = new Designer(designerContainer, this._contextViewService); this._register(attachDesignerStyler(this._designer, this.themeService)); } diff --git a/src/sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput.ts b/src/sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput.ts index 9128235688..8f114543db 100644 --- a/src/sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput.ts +++ b/src/sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput.ts @@ -4,18 +4,39 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; -import { DesignerData, DesignerEdit, DesignerEditResult, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties } from 'sql/base/browser/ui/designer/interfaces'; +import { DesignerData, DesignerEdit, DesignerEditResult, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerState } from 'sql/base/browser/ui/designer/interfaces'; import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface'; import { localize } from 'vs/nls'; import { designers } from 'sql/workbench/api/common/sqlExtHostTypes'; +import { Emitter, Event } from 'vs/base/common/event'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; export class TableDesignerComponentInput implements DesignerComponentInput { private _data: DesignerData; private _view: DesignerView; + private _valid: boolean = true; + private _dirty: boolean = false; + private _saving: boolean = false; + private _onStateChange = new Emitter(); + + public readonly onStateChange: Event = this._onStateChange.event; constructor(private readonly _provider: TableDesignerProvider, - private _tableInfo: azdata.designers.TableInfo) { + private _tableInfo: azdata.designers.TableInfo, + @INotificationService private readonly _notificationService: INotificationService) { + } + + get valid(): boolean { + return this._valid; + } + + get dirty(): boolean { + return this._dirty; + } + + get saving(): boolean { + return this._saving; } get objectTypeDisplayName(): string { @@ -41,12 +62,47 @@ export class TableDesignerComponentInput implements DesignerComponentInput { if (result.isValid) { this._data = result.data; } + this.updateState(result.isValid, true, this.saving); return { isValid: result.isValid, errors: result.errors }; } + async save(): Promise { + const notificationHandle = this._notificationService.notify({ + severity: Severity.Info, + message: localize('tableDesigner.savingChanges', "Saving table designer changes...") + }); + try { + this.updateState(this.valid, this.dirty, true); + await this._provider.saveTable(this._tableInfo, this._data); + this.updateState(true, false, false); + notificationHandle.updateMessage(localize('tableDesigner.savedChangeSuccess', "The changes have been successfully saved.")); + } catch (error) { + notificationHandle.updateSeverity(Severity.Error); + notificationHandle.updateMessage(localize('tableDesigner.saveChangeError', "An error occured while saving changes: {0}", error?.message ?? error)); + this.updateState(this.valid, this.dirty, false); + } + } + + async revert(): Promise { + this.updateState(true, false, false); + } + + private updateState(valid: boolean, dirty: boolean, saving: boolean): void { + if (this._dirty !== dirty || this._valid !== valid || this._saving !== saving) { + this._dirty = dirty; + this._valid = valid; + this._saving = saving; + this._onStateChange.fire({ + valid: this._valid, + dirty: this._dirty, + saving: this._saving + }); + } + } + private async initialize(): Promise { const designerInfo = await this._provider.getTableDesignerInfo(this._tableInfo); @@ -115,6 +171,12 @@ export class TableDesignerComponentInput implements DesignerComponentInput { componentProperties: { title: localize('tableDesigner.columnAllowNullTitle', "Allow Nulls"), } + }, { + componentType: 'checkbox', + propertyName: designers.TableColumnProperty.IsPrimaryKey, + componentProperties: { + title: localize('tableDesigner.columnIsPrimaryKeyTitle', "Primary Key"), + } } ]; @@ -135,7 +197,8 @@ export class TableDesignerComponentInput implements DesignerComponentInput { designers.TableColumnProperty.Type, designers.TableColumnProperty.Length, designers.TableColumnProperty.DefaultValue, - designers.TableColumnProperty.AllowNulls + designers.TableColumnProperty.AllowNulls, + designers.TableColumnProperty.IsPrimaryKey ], itemProperties: columnProperties, objectTypeDisplayName: localize('tableDesigner.columnTypeName', "Column") diff --git a/src/sql/workbench/services/tableDesigner/browser/tableDesignerService.ts b/src/sql/workbench/services/tableDesigner/browser/tableDesignerService.ts index 5b760b34be..bbb647deaf 100644 --- a/src/sql/workbench/services/tableDesigner/browser/tableDesignerService.ts +++ b/src/sql/workbench/services/tableDesigner/browser/tableDesignerService.ts @@ -8,10 +8,12 @@ import { invalidProvider } from 'sql/base/common/errors'; import * as azdata from 'azdata'; import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { TableDesignerInput } from 'sql/workbench/browser/editor/tableDesigner/tableDesignerInput'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class TableDesignerService implements ITableDesignerService { - constructor(@IEditorService private _editorService: IEditorService) { } + constructor(@IEditorService private _editorService: IEditorService, + @IInstantiationService private _instantiationService: IInstantiationService) { } public _serviceBrand: undefined; private _providers = new Map(); @@ -40,7 +42,7 @@ export class TableDesignerService implements ITableDesignerService { public async openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo): Promise { const provider = this.getProvider(providerId); - const tableDesignerInput = new TableDesignerInput(provider, tableInfo); + const tableDesignerInput = this._instantiationService.createInstance(TableDesignerInput, provider, tableInfo); await this._editorService.openEditor(tableDesignerInput, { pinned: true }, ACTIVE_GROUP); } } diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 63c9dbbc38..f0fa8fa083 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -229,6 +229,21 @@ export interface IButtonStyleOverrides extends IStyleOverrides { buttonDisabledBorder?: ColorIdentifier; } +// {{SQL CARBON EDIT}} +export const defaultButtonStyles: IButtonStyleOverrides = { + buttonForeground: buttonForeground, + buttonBackground: buttonBackground, + buttonHoverBackground: buttonHoverBackground, + buttonSecondaryForeground: buttonSecondaryForeground, + buttonSecondaryBackground: buttonSecondaryBackground, + buttonSecondaryHoverBackground: buttonSecondaryHoverBackground, + buttonBorder: buttonBorder, + buttonSecondaryBorder: buttonSecondaryBorder, + buttonDisabledBorder: buttonDisabledBorder, + buttonDisabledBackground: buttonDisabledBackground, + buttonDisabledForeground: buttonDisabledForeground +}; + export function attachButtonStyler(widget: IThemable, themeService: IThemeService, style?: IButtonStyleOverrides): IDisposable { return attachStyler(themeService, { buttonForeground: style?.buttonForeground || buttonForeground,