mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-21 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:
@@ -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