mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 01:25:37 -05:00
Table Designer - Save Changes feature and Editor related features (#17335)
* table designer add/remove row support * save changes and editor support * address comments * fix build error * including missing change * lower case request name
This commit is contained in:
18
src/sql/azdata.proposed.d.ts
vendored
18
src/sql/azdata.proposed.d.ts
vendored
@@ -1015,12 +1015,19 @@ declare module 'azdata' {
|
||||
*/
|
||||
getTableDesignerInfo(table: TableInfo): Thenable<TableDesignerInfo>;
|
||||
/**
|
||||
*
|
||||
* 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<DesignerEditResult>;
|
||||
|
||||
/**
|
||||
* 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<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Slick.SlickData> | 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<Slick.SlickData> | SelectBox): void {
|
||||
private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table<Slick.SlickData> | 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<void> {
|
||||
private async updatePropertiesPane(newContext: PropertiesPaneObjectContext): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
@@ -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<Slick.SlickData>;
|
||||
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<Slick.SlickData>;
|
||||
const tableDataView = table.getData() as TableDataView<Slick.SlickData>;
|
||||
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<Slick.SlickData>): 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;
|
||||
|
||||
@@ -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<Slick.SlickData>[] = [];
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DesignerState>;
|
||||
|
||||
/**
|
||||
* Gets the object type display name.
|
||||
*/
|
||||
@@ -24,6 +31,27 @@ export interface DesignerComponentInput {
|
||||
* @param edit the information about the edit.
|
||||
*/
|
||||
processEdit(edit: DesignerEdit): Promise<DesignerEditResult>;
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export interface ButtonColumnOptions extends IconColumnOptions {
|
||||
/**
|
||||
* Whether to show the text.
|
||||
*/
|
||||
showText?: boolean
|
||||
showText?: boolean;
|
||||
}
|
||||
|
||||
export class ButtonColumn<T extends Slick.SlickData> extends BaseClickableColumn<T> {
|
||||
@@ -33,7 +33,10 @@ export class ButtonColumn<T extends Slick.SlickData> extends BaseClickableColumn
|
||||
formatter: (row: number, cell: number, value: any, columnDef: Slick.Column<T>, 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 `<button tabindex=-1 class="${iconCssClasses} ${buttonTypeCssClass}" title="${escapedTitle}" aria-label="${escapedTitle}">${buttonText}</button>`;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -426,4 +426,8 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
|
||||
public set ariaLabel(value: string) {
|
||||
this._tableContainer.setAttribute('aria-label', value);
|
||||
}
|
||||
|
||||
public get container(): HTMLElement {
|
||||
return this._tableContainer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -512,11 +512,15 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
||||
$registerTableDesignerProvider(providerId: string, handle: number): Promise<any> {
|
||||
const self = this;
|
||||
this._tableDesignerService.registerProvider(providerId, <azdata.designers.TableDesignerProvider>{
|
||||
providerId: providerId,
|
||||
getTableDesignerInfo(tableInfo: azdata.designers.TableInfo): Thenable<azdata.designers.TableDesignerInfo> {
|
||||
return self._proxy.$getTableDesignerInfo(handle, tableInfo);
|
||||
},
|
||||
processTableEdit(table, data, edit): Thenable<azdata.designers.DesignerEditResult> {
|
||||
return self._proxy.$processTableDesignerEdit(handle, table, data, edit);
|
||||
},
|
||||
saveTable(tableInfo: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable<void> {
|
||||
return self._proxy.$saveTable(handle, tableInfo, data);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -900,6 +900,10 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
return this._resolveProvider<azdata.designers.TableDesignerProvider>(handle).processTableEdit(table, data, edit);
|
||||
}
|
||||
|
||||
public override $saveTable(handle, table: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable<void> {
|
||||
return this._resolveProvider<azdata.designers.TableDesignerProvider>(handle).saveTable(table, data);
|
||||
}
|
||||
|
||||
public override $openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo): Promise<void> {
|
||||
this._proxy.$openTableDesigner(providerId, tableInfo);
|
||||
return Promise.resolve();
|
||||
|
||||
@@ -537,6 +537,11 @@ export abstract class ExtHostDataProtocolShape {
|
||||
*/
|
||||
$processTableDesignerEdit(handle, table: azdata.designers.TableInfo, data: azdata.designers.DesignerData, edit: azdata.designers.DesignerEdit): Thenable<azdata.designers.DesignerEditResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* Process the table edit.
|
||||
*/
|
||||
$saveTable(handle, table: azdata.designers.TableInfo, data: azdata.designers.DesignerData): Thenable<void> { throw ni(); }
|
||||
|
||||
/**
|
||||
* Open a new instance of table designer.
|
||||
*/
|
||||
|
||||
@@ -921,7 +921,8 @@ export namespace designers {
|
||||
Type = 'type',
|
||||
AllowNulls = 'allowNulls',
|
||||
DefaultValue = 'defaultValue',
|
||||
Length = 'length'
|
||||
Length = 'length',
|
||||
IsPrimaryKey = 'isPrimaryKey'
|
||||
}
|
||||
|
||||
export enum DesignerEditType {
|
||||
|
||||
@@ -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<IEditorInput | undefined> {
|
||||
await this._designerComponentInput.save();
|
||||
return this;
|
||||
}
|
||||
|
||||
override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
44
src/sql/workbench/contrib/tableDesigner/browser/actions.ts
Normal file
44
src/sql/workbench/contrib/tableDesigner/browser/actions.ts
Normal file
@@ -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<void> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<void> {
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DesignerState>();
|
||||
|
||||
public readonly onStateChange: Event<DesignerState> = 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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")
|
||||
|
||||
@@ -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<string, TableDesignerProvider>();
|
||||
@@ -40,7 +42,7 @@ export class TableDesignerService implements ITableDesignerService {
|
||||
|
||||
public async openTableDesigner(providerId: string, tableInfo: azdata.designers.TableInfo): Promise<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user