mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
make the designer event based (#17472)
* make the designer event based * pr comments
This commit is contained in:
@@ -3,12 +3,12 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerEditIdentifier, DesignerViewModel, DesignerDataPropertyInfo, DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties, DesignerComponentTypeName } from 'sql/base/browser/ui/designer/interfaces';
|
import { DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerEditIdentifier, DesignerViewModel, DesignerDataPropertyInfo, DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties, DesignerComponentTypeName, DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState } from 'sql/base/browser/ui/designer/interfaces';
|
||||||
import { IPanelTab, ITabbedPanelStyles, TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
import { IPanelTab, ITabbedPanelStyles, TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
|
import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||||
import 'vs/css!./media/designer';
|
import 'vs/css!./media/designer';
|
||||||
@@ -63,6 +63,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
private _tableCellEditorFactory: TableCellEditorFactory;
|
private _tableCellEditorFactory: TableCellEditorFactory;
|
||||||
private _propertiesPane: DesignerPropertiesPane;
|
private _propertiesPane: DesignerPropertiesPane;
|
||||||
private _buttons: Button[] = [];
|
private _buttons: Button[] = [];
|
||||||
|
private _inputDisposable: DisposableStore;
|
||||||
|
private _loadingTimeoutHandle: any;
|
||||||
|
|
||||||
constructor(private readonly _container: HTMLElement,
|
constructor(private readonly _container: HTMLElement,
|
||||||
private readonly _contextViewProvider: IContextViewProvider) {
|
private readonly _contextViewProvider: IContextViewProvider) {
|
||||||
@@ -72,8 +74,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
valueGetter: (item, column): string => {
|
valueGetter: (item, column): string => {
|
||||||
return item[column.field].value;
|
return item[column.field].value;
|
||||||
},
|
},
|
||||||
valueSetter: async (context: string, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): Promise<void> => {
|
valueSetter: (context: string, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||||
await this.handleEdit({
|
this.handleEdit({
|
||||||
type: DesignerEditType.Update,
|
type: DesignerEditType.Update,
|
||||||
property: {
|
property: {
|
||||||
parentProperty: context,
|
parentProperty: context,
|
||||||
@@ -196,37 +198,127 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async setInput(input: DesignerComponentInput): Promise<void> {
|
public setInput(input: DesignerComponentInput): void {
|
||||||
this._input = input;
|
// Save state
|
||||||
await this.initializeDesignerView();
|
if (this._input) {
|
||||||
|
this._input.designerUIState = this.getUIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeDesignerView(): Promise<void> {
|
// Clean up
|
||||||
this._propertiesPane.clear();
|
if (this._loadingTimeoutHandle) {
|
||||||
|
this.stopLoading();
|
||||||
|
}
|
||||||
|
this._buttons = [];
|
||||||
|
this._componentMap.clear();
|
||||||
DOM.clearNode(this._topContentContainer);
|
DOM.clearNode(this._topContentContainer);
|
||||||
// For initialization, we would want to show the loading indicator immediately.
|
this._tabbedPanel.clearTabs();
|
||||||
const handle = this.startLoading(localize('designer.loadingDesigner', "Loading designer..."), 0);
|
this._propertiesPane.clear();
|
||||||
const view = await this._input.getView();
|
this._inputDisposable?.dispose();
|
||||||
this.stopLoading(handle, localize('designer.loadingDesignerCompleted', "Loading designer completed"));
|
|
||||||
|
|
||||||
|
// Initialize with new input
|
||||||
|
this._input = input;
|
||||||
|
this._inputDisposable = new DisposableStore();
|
||||||
|
this._inputDisposable.add(this._input.onInitialized(() => {
|
||||||
|
this.initializeDesigner();
|
||||||
|
}));
|
||||||
|
this._inputDisposable.add(this._input.onEditProcessed((args) => {
|
||||||
|
this.handleEditProcessedEvent(args);
|
||||||
|
}));
|
||||||
|
this._inputDisposable.add(this._input.onStateChange((args) => {
|
||||||
|
this.handleInputStateChangedEvent(args);
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (this._input.view === undefined) {
|
||||||
|
this._input.initialize();
|
||||||
|
} else {
|
||||||
|
this.initializeDesigner();
|
||||||
|
}
|
||||||
|
if (this._input.pendingAction) {
|
||||||
|
this.updateLoadingStatus(this._input.pendingAction, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override dispose(): void {
|
||||||
|
super.dispose();
|
||||||
|
this._inputDisposable?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initializeDesigner(): void {
|
||||||
|
const view = this._input.view;
|
||||||
if (view.components) {
|
if (view.components) {
|
||||||
view.components.forEach(component => {
|
view.components.forEach(component => {
|
||||||
this.createComponent(this._topContentContainer, component, component.propertyName, true, true);
|
this.createComponent(this._topContentContainer, component, component.propertyName, true, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._tabbedPanel.clearTabs();
|
|
||||||
view.tabs.forEach(tab => {
|
view.tabs.forEach(tab => {
|
||||||
this._tabbedPanel.pushTab(this.createTabView(tab));
|
this._tabbedPanel.pushTab(this.createTabView(tab));
|
||||||
});
|
});
|
||||||
this.layoutTabbedPanel();
|
this.layoutTabbedPanel();
|
||||||
await this.updateComponentValues();
|
this.updateComponentValues();
|
||||||
|
this.restoreUIState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
||||||
|
const edit = args.edit;
|
||||||
|
const result = args.result;
|
||||||
|
if (result.isValid) {
|
||||||
|
this._supressEditProcessing = true;
|
||||||
|
this.updateComponentValues();
|
||||||
|
if (edit.type === DesignerEditType.Add) {
|
||||||
|
// Move focus to the first cell of the newly added row.
|
||||||
|
const propertyName = edit.property as string;
|
||||||
|
const tableData = this._input.viewModel[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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleInputStateChangedEvent(args: DesignerStateChangedEventArgs): void {
|
||||||
|
if (args.previousState.pendingAction !== args.currentState.pendingAction) {
|
||||||
|
const showLoading = args.currentState.pendingAction !== undefined;
|
||||||
|
const action = args.currentState.pendingAction || args.previousState.pendingAction;
|
||||||
|
this.updateLoadingStatus(action, showLoading, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateLoadingStatus(action: DesignerAction, showLoading: boolean, useDelay: boolean): void {
|
||||||
|
let message;
|
||||||
|
let timeout;
|
||||||
|
switch (action) {
|
||||||
|
case 'save':
|
||||||
|
message = showLoading ? localize('designer.savingChanges', "Saving changes...") : localize('designer.savingChangesCompleted', "Changes have been saved");
|
||||||
|
timeout = 0;
|
||||||
|
break;
|
||||||
|
case 'initialize':
|
||||||
|
message = showLoading ? localize('designer.loadingDesigner', "Loading designer...") : localize('designer.loadingDesignerCompleted', "Designer is loaded");
|
||||||
|
timeout = 0;
|
||||||
|
break;
|
||||||
|
case 'processEdit':
|
||||||
|
message = showLoading ? localize('designer.processingChanges', "Processing changes...") : localize('designer.processingChangesCompleted', "Changes have been processed");
|
||||||
|
// To make the edit experience smoother, only show the loading indicator if the request is not returning in 500ms.
|
||||||
|
timeout = 500;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (showLoading) {
|
||||||
|
this.startLoading(message, useDelay ? timeout : 0);
|
||||||
|
} else {
|
||||||
|
this.stopLoading(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private layoutTabbedPanel() {
|
private layoutTabbedPanel() {
|
||||||
this._tabbedPanel.layout(new DOM.Dimension(this._tabbedPanelContainer.clientWidth, this._tabbedPanelContainer.clientHeight));
|
this._tabbedPanel.layout(new DOM.Dimension(this._tabbedPanelContainer.clientWidth, this._tabbedPanelContainer.clientHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updatePropertiesPane(newContext: PropertiesPaneObjectContext): Promise<void> {
|
private updatePropertiesPane(newContext: PropertiesPaneObjectContext): void {
|
||||||
const viewModel = await this._input.getViewModel();
|
const viewModel = this._input.viewModel;
|
||||||
let type: string;
|
let type: string;
|
||||||
let components: DesignerDataPropertyInfo[];
|
let components: DesignerDataPropertyInfo[];
|
||||||
let inputViewModel: DesignerViewModel;
|
let inputViewModel: DesignerViewModel;
|
||||||
@@ -260,42 +352,25 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateComponentValues(): Promise<void> {
|
private updateComponentValues(): void {
|
||||||
const viewModel = await this._input.getViewModel();
|
const viewModel = this._input.viewModel;
|
||||||
// data[ScriptPropertyName] -- todo- set the script editor
|
// data[ScriptPropertyName] -- todo- set the script editor
|
||||||
this._componentMap.forEach((value) => {
|
this._componentMap.forEach((value) => {
|
||||||
this.setComponentValue(value.defintion, value.component, viewModel);
|
this.setComponentValue(value.defintion, value.component, viewModel);
|
||||||
});
|
});
|
||||||
await this.updatePropertiesPane(this._propertiesPane.context ?? 'root');
|
this.updatePropertiesPane(this._propertiesPane.context ?? 'root');
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleEdit(edit: DesignerEdit): Promise<void> {
|
private handleEdit(edit: DesignerEdit): void {
|
||||||
if (this._supressEditProcessing) {
|
if (this._supressEditProcessing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.applyEdit(edit);
|
this.applyEdit(edit);
|
||||||
const handle = this.startLoading(localize('designer.processingChanges', "Processing changes..."));
|
this._input.processEdit(edit);
|
||||||
const result = await this._input.processEdit(edit);
|
|
||||||
this.stopLoading(handle, localize('designer.processingChangesCompleted', "Processing changes completed"));
|
|
||||||
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.getViewModel();
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async applyEdit(edit: DesignerEdit): Promise<void> {
|
private applyEdit(edit: DesignerEdit): void {
|
||||||
const viewModel = await this._input.getViewModel();
|
const viewModel = this._input.viewModel;
|
||||||
switch (edit.type) {
|
switch (edit.type) {
|
||||||
case DesignerEditType.Update:
|
case DesignerEditType.Update:
|
||||||
if (typeof edit.property === 'string') {
|
if (typeof edit.property === 'string') {
|
||||||
@@ -423,9 +498,9 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
ariaLabel: inputProperties.title,
|
ariaLabel: inputProperties.title,
|
||||||
type: inputProperties.inputType,
|
type: inputProperties.inputType,
|
||||||
});
|
});
|
||||||
input.onLoseFocus(async (args) => {
|
input.onLoseFocus((args) => {
|
||||||
if (args.hasChanged) {
|
if (args.hasChanged) {
|
||||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: args.value });
|
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: args.value });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (setWidth && inputProperties.width !== undefined) {
|
if (setWidth && inputProperties.width !== undefined) {
|
||||||
@@ -440,8 +515,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
const dropdown = new SelectBox(dropdownProperties.values as string[], undefined, this._contextViewProvider, undefined);
|
const dropdown = new SelectBox(dropdownProperties.values as string[], undefined, this._contextViewProvider, undefined);
|
||||||
dropdown.render(dropdownContainer);
|
dropdown.render(dropdownContainer);
|
||||||
dropdown.selectElem.style.height = '25px';
|
dropdown.selectElem.style.height = '25px';
|
||||||
dropdown.onDidSelect(async (e) => {
|
dropdown.onDidSelect((e) => {
|
||||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected });
|
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected });
|
||||||
});
|
});
|
||||||
component = dropdown;
|
component = dropdown;
|
||||||
break;
|
break;
|
||||||
@@ -452,8 +527,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
const checkbox = new Checkbox(checkboxContainer, {
|
const checkbox = new Checkbox(checkboxContainer, {
|
||||||
label: checkboxProperties.title
|
label: checkboxProperties.title
|
||||||
});
|
});
|
||||||
checkbox.onChange(async (newValue) => {
|
checkbox.onChange((newValue) => {
|
||||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue });
|
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue });
|
||||||
});
|
});
|
||||||
component = checkbox;
|
component = checkbox;
|
||||||
break;
|
break;
|
||||||
@@ -465,8 +540,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
title: addNewText,
|
title: addNewText,
|
||||||
secondary: true
|
secondary: true
|
||||||
});
|
});
|
||||||
addRowButton.onDidClick(async () => {
|
addRowButton.onDidClick(() => {
|
||||||
await this.handleEdit({
|
this.handleEdit({
|
||||||
type: DesignerEditType.Add,
|
type: DesignerEditType.Add,
|
||||||
property: componentDefinition.propertyName,
|
property: componentDefinition.propertyName,
|
||||||
});
|
});
|
||||||
@@ -502,8 +577,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
width: propertyDefinition.componentProperties.width as number
|
width: propertyDefinition.componentProperties.width as number
|
||||||
});
|
});
|
||||||
table.registerPlugin(checkboxColumn);
|
table.registerPlugin(checkboxColumn);
|
||||||
checkboxColumn.onChange(async (e) => {
|
checkboxColumn.onChange((e) => {
|
||||||
await this.handleEdit({
|
this.handleEdit({
|
||||||
type: DesignerEditType.Update,
|
type: DesignerEditType.Update,
|
||||||
property: {
|
property: {
|
||||||
parentProperty: componentDefinition.propertyName,
|
parentProperty: componentDefinition.propertyName,
|
||||||
@@ -540,10 +615,9 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
resizable: false,
|
resizable: false,
|
||||||
isFontIcon: true
|
isFontIcon: true
|
||||||
});
|
});
|
||||||
deleteRowColumn.onClick(async (e) => {
|
deleteRowColumn.onClick((e) => {
|
||||||
const viewModel = await this._input.getViewModel();
|
(this._input.viewModel[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1);
|
||||||
(viewModel[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1);
|
this.handleEdit({
|
||||||
await this.handleEdit({
|
|
||||||
type: DesignerEditType.Remove,
|
type: DesignerEditType.Remove,
|
||||||
property: componentDefinition.propertyName,
|
property: componentDefinition.propertyName,
|
||||||
value: e.item
|
value: e.item
|
||||||
@@ -555,9 +629,9 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
table.grid.onBeforeEditCell.subscribe((e, data): boolean => {
|
table.grid.onBeforeEditCell.subscribe((e, data): boolean => {
|
||||||
return data.item[data.column.field].enabled !== false;
|
return data.item[data.column.field].enabled !== false;
|
||||||
});
|
});
|
||||||
table.grid.onActiveCellChanged.subscribe(async (e, data) => {
|
table.grid.onActiveCellChanged.subscribe((e, data) => {
|
||||||
if (data.row !== undefined) {
|
if (data.row !== undefined) {
|
||||||
await this.updatePropertiesPane({
|
this.updatePropertiesPane({
|
||||||
parentProperty: componentDefinition.propertyName,
|
parentProperty: componentDefinition.propertyName,
|
||||||
index: data.row
|
index: data.row
|
||||||
});
|
});
|
||||||
@@ -578,21 +652,37 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
|
|
||||||
private startLoading(message: string, timeout: number = 500): any {
|
private startLoading(message: string, timeout: number): void {
|
||||||
// To make the experience smoother, only show the loading indicator if the request is not returning in 500ms(default value).
|
this._loadingTimeoutHandle = setTimeout(() => {
|
||||||
return setTimeout(() => {
|
|
||||||
this._loadingSpinner.loadingMessage = message;
|
this._loadingSpinner.loadingMessage = message;
|
||||||
this._loadingSpinner.loading = true;
|
this._loadingSpinner.loading = true;
|
||||||
|
if (this._container.contains(this._verticalSplitViewContainer)) {
|
||||||
this._container.removeChild(this._verticalSplitViewContainer);
|
this._container.removeChild(this._verticalSplitViewContainer);
|
||||||
|
}
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private stopLoading(handle: any, message: string): void {
|
private stopLoading(message: string = ''): void {
|
||||||
clearTimeout(handle);
|
clearTimeout(this._loadingTimeoutHandle);
|
||||||
|
this._loadingTimeoutHandle = undefined;
|
||||||
if (this._loadingSpinner.loading) {
|
if (this._loadingSpinner.loading) {
|
||||||
this._loadingSpinner.loadingCompletedMessage = message;
|
this._loadingSpinner.loadingCompletedMessage = message;
|
||||||
this._loadingSpinner.loading = false;
|
this._loadingSpinner.loading = false;
|
||||||
|
if (!this._container.contains(this._verticalSplitViewContainer)) {
|
||||||
this._container.appendChild(this._verticalSplitViewContainer);
|
this._container.appendChild(this._verticalSplitViewContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUIState(): DesignerUIState {
|
||||||
|
return {
|
||||||
|
activeTabId: this._tabbedPanel.activeTabId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private restoreUIState(): void {
|
||||||
|
if (this._input.designerUIState) {
|
||||||
|
this._tabbedPanel.showTab(this._input.designerUIState.activeTabId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,34 +3,50 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
|
|
||||||
export interface DesignerComponentInput {
|
export interface DesignerComponentInput {
|
||||||
/**
|
/**
|
||||||
* The event that is triggerd when the designer state changes.
|
* The event that is triggerd when the designer state changes.
|
||||||
*/
|
*/
|
||||||
readonly onStateChange: Event<DesignerState>;
|
readonly onStateChange: Event<DesignerStateChangedEventArgs>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event that is triggerd when the designer information is loaded.
|
||||||
|
*/
|
||||||
|
readonly onInitialized: Event<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The event that is triggerd when an edit is processed.
|
||||||
|
*/
|
||||||
|
readonly onEditProcessed: Event<DesignerEditProcessedEventArgs>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the object type display name.
|
* Gets the object type display name.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
readonly objectTypeDisplayName: string;
|
readonly objectTypeDisplayName: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the designer view specification.
|
* Gets the designer view specification.
|
||||||
*/
|
*/
|
||||||
getView(): Promise<DesignerView>;
|
readonly view: DesignerView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the view model.
|
* Gets the view model.
|
||||||
*/
|
*/
|
||||||
getViewModel(): Promise<DesignerViewModel>;
|
readonly viewModel: DesignerViewModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the edit made in the designer.
|
* Start initilizing the designer input object.
|
||||||
|
*/
|
||||||
|
initialize(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start processing the edit made in the designer, the OnEditProcessed event will be fired when the processing is done.
|
||||||
* @param edit the information about the edit.
|
* @param edit the information about the edit.
|
||||||
*/
|
*/
|
||||||
processEdit(edit: DesignerEdit): Promise<DesignerEditResult>;
|
processEdit(edit: DesignerEdit): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A boolean value indicating whether the current state is valid.
|
* A boolean value indicating whether the current state is valid.
|
||||||
@@ -43,16 +59,35 @@ export interface DesignerComponentInput {
|
|||||||
readonly dirty: boolean;
|
readonly dirty: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A boolean value indicating whether the changes are being saved.
|
* Current in progress action.
|
||||||
*/
|
*/
|
||||||
readonly saving: boolean;
|
readonly pendingAction?: DesignerAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The UI state of the designer, used to restore the state.
|
||||||
|
*/
|
||||||
|
designerUIState?: DesignerUIState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DesignerUIState {
|
||||||
|
activeTabId: PanelTabIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DesignerAction = 'save' | 'initialize' | 'processEdit';
|
||||||
|
|
||||||
|
export interface DesignerEditProcessedEventArgs {
|
||||||
|
result: DesignerEditResult;
|
||||||
|
edit: DesignerEdit
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DesignerStateChangedEventArgs {
|
||||||
|
currentState: DesignerState,
|
||||||
|
previousState: DesignerState
|
||||||
|
}
|
||||||
export interface DesignerState {
|
export interface DesignerState {
|
||||||
valid: boolean;
|
valid: boolean;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
saving: boolean;
|
pendingAction?: DesignerAction
|
||||||
processing: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NameProperty = 'name';
|
export const NameProperty = 'name';
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ export class TabbedPanel extends Disposable {
|
|||||||
return this.parent;
|
return this.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get activeTabId(): string | undefined {
|
||||||
|
return this._shownTabId;
|
||||||
|
}
|
||||||
|
|
||||||
public override dispose() {
|
public override dispose() {
|
||||||
this.header.remove();
|
this.header.remove();
|
||||||
this.tabList.remove();
|
this.tabList.remove();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom';
|
|||||||
|
|
||||||
export interface ITableCellEditorOptions {
|
export interface ITableCellEditorOptions {
|
||||||
valueGetter?: (item: Slick.SlickData, column: Slick.Column<Slick.SlickData>) => string,
|
valueGetter?: (item: Slick.SlickData, column: Slick.Column<Slick.SlickData>) => string,
|
||||||
valueSetter?: (context: any, row: number, item: Slick.SlickData, column: Slick.Column<Slick.SlickData>, value: string) => Promise<void>,
|
valueSetter?: (context: any, row: number, item: Slick.SlickData, column: Slick.Column<Slick.SlickData>, value: string) => void,
|
||||||
optionsGetter?: (item: Slick.SlickData, column: Slick.Column<Slick.SlickData>) => string[],
|
optionsGetter?: (item: Slick.SlickData, column: Slick.Column<Slick.SlickData>) => string[],
|
||||||
editorStyler: (component: InputBox | SelectBox) => void
|
editorStyler: (component: InputBox | SelectBox) => void
|
||||||
}
|
}
|
||||||
@@ -72,9 +72,9 @@ export class TableCellEditorFactory {
|
|||||||
this._input.value = this._originalValue;
|
this._input.value = this._originalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async applyValue(item: Slick.SlickData, state: string): Promise<void> {
|
public applyValue(item: Slick.SlickData, state: string): void {
|
||||||
const activeCell = this._args.grid.getActiveCell();
|
const activeCell = this._args.grid.getActiveCell();
|
||||||
await self._options.valueSetter(context, activeCell.row, item, this._args.column, state);
|
self._options.valueSetter(context, activeCell.row, item, this._args.column, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isValueChanged(): boolean {
|
public isValueChanged(): boolean {
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ export class TableDesignerInput extends EditorInput {
|
|||||||
super();
|
super();
|
||||||
this._designerComponentInput = this._instantiationService.createInstance(TableDesignerComponentInput, this._provider, this._tableInfo);
|
this._designerComponentInput = this._instantiationService.createInstance(TableDesignerComponentInput, this._provider, this._tableInfo);
|
||||||
this._register(this._designerComponentInput.onStateChange((e) => {
|
this._register(this._designerComponentInput.onStateChange((e) => {
|
||||||
|
if (e.currentState.dirty !== e.previousState.dirty) {
|
||||||
this._onDidChangeDirty.fire();
|
this._onDidChangeDirty.fire();
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
const existingNames = editorService.editors.map(editor => editor.getName());
|
const existingNames = editorService.editors.map(editor => editor.getName());
|
||||||
|
|
||||||
@@ -66,7 +68,7 @@ export class TableDesignerInput extends EditorInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override isSaving(): boolean {
|
override isSaving(): boolean {
|
||||||
return this._designerComponentInput.saving;
|
return this._designerComponentInput.pendingAction === 'save';
|
||||||
}
|
}
|
||||||
|
|
||||||
override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
|
override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<IEditorInput | undefined> {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class SaveTableChangesAction extends Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateState(): void {
|
private updateState(): void {
|
||||||
this.enabled = this._input.dirty && this._input.valid && !this._input.processing;
|
this.enabled = this._input.dirty && this._input.valid && this._input.pendingAction === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
override dispose() {
|
override dispose() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { DesignerViewModel, DesignerEdit, DesignerEditResult, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerState } from 'sql/base/browser/ui/designer/interfaces';
|
import { DesignerViewModel, DesignerEdit, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerEditProcessedEventArgs, DesignerAction, DesignerStateChangedEventArgs } from 'sql/base/browser/ui/designer/interfaces';
|
||||||
import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface';
|
import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { designers } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { designers } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
@@ -17,11 +17,14 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
|||||||
private _view: DesignerView;
|
private _view: DesignerView;
|
||||||
private _valid: boolean = true;
|
private _valid: boolean = true;
|
||||||
private _dirty: boolean = false;
|
private _dirty: boolean = false;
|
||||||
private _saving: boolean = false;
|
private _pendingAction?: DesignerAction = undefined;
|
||||||
private _processing: boolean = false;
|
private _onStateChange = new Emitter<DesignerStateChangedEventArgs>();
|
||||||
private _onStateChange = new Emitter<DesignerState>();
|
private _onInitialized = new Emitter<void>();
|
||||||
|
private _onEditProcessed = new Emitter<DesignerEditProcessedEventArgs>();
|
||||||
|
|
||||||
public readonly onStateChange: Event<DesignerState> = this._onStateChange.event;
|
public readonly onInitialized: Event<void> = this._onInitialized.event;
|
||||||
|
public readonly onEditProcessed: Event<DesignerEditProcessedEventArgs> = this._onEditProcessed.event;
|
||||||
|
public readonly onStateChange: Event<DesignerStateChangedEventArgs> = this._onStateChange.event;
|
||||||
|
|
||||||
constructor(private readonly _provider: TableDesignerProvider,
|
constructor(private readonly _provider: TableDesignerProvider,
|
||||||
private _tableInfo: azdata.designers.TableInfo,
|
private _tableInfo: azdata.designers.TableInfo,
|
||||||
@@ -36,43 +39,44 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
|||||||
return this._dirty;
|
return this._dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
get saving(): boolean {
|
get pendingAction(): DesignerAction | undefined {
|
||||||
return this._saving;
|
return this._pendingAction;
|
||||||
}
|
|
||||||
|
|
||||||
get processing(): boolean {
|
|
||||||
return this._processing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get objectTypeDisplayName(): string {
|
get objectTypeDisplayName(): string {
|
||||||
return localize('tableDesigner.tableObjectType', "Table");
|
return localize('tableDesigner.tableObjectType', "Table");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getView(): Promise<DesignerView> {
|
get view(): DesignerView {
|
||||||
if (!this._view) {
|
|
||||||
await this.initialize();
|
|
||||||
}
|
|
||||||
return this._view;
|
return this._view;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getViewModel(): Promise<DesignerViewModel> {
|
get viewModel(): DesignerViewModel {
|
||||||
if (!this._viewModel) {
|
|
||||||
await this.initialize();
|
|
||||||
}
|
|
||||||
return this._viewModel;
|
return this._viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async processEdit(edit: DesignerEdit): Promise<DesignerEditResult> {
|
processEdit(edit: DesignerEdit): void {
|
||||||
this.updateState(this.valid, this.dirty, this.saving, true);
|
this.updateState(this.valid, this.dirty, 'processEdit');
|
||||||
const result = await this._provider.processTableEdit(this._tableInfo, this._viewModel!, edit);
|
this._provider.processTableEdit(this._tableInfo, this._viewModel!, edit).then(
|
||||||
|
result => {
|
||||||
if (result.isValid) {
|
if (result.isValid) {
|
||||||
this._viewModel = result.viewModel;
|
this._viewModel = result.viewModel;
|
||||||
}
|
}
|
||||||
this.updateState(result.isValid, true, this.saving, false);
|
this.updateState(result.isValid, true, undefined);
|
||||||
return {
|
|
||||||
|
this._onEditProcessed.fire({
|
||||||
|
edit: edit,
|
||||||
|
result: {
|
||||||
isValid: result.isValid,
|
isValid: result.isValid,
|
||||||
errors: result.errors
|
errors: result.errors
|
||||||
};
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this._notificationService.error(localize('tableDesigner.errorProcessingEdit', "An error occured while processing the change: {0}", error?.message ?? error));
|
||||||
|
this.updateState(this.valid, this.dirty);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save(): Promise<void> {
|
async save(): Promise<void> {
|
||||||
@@ -81,40 +85,61 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
|||||||
message: localize('tableDesigner.savingChanges', "Saving table designer changes...")
|
message: localize('tableDesigner.savingChanges', "Saving table designer changes...")
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
this.updateState(this.valid, this.dirty, true, true);
|
this.updateState(this.valid, this.dirty, 'save');
|
||||||
await this._provider.saveTable(this._tableInfo, this._viewModel);
|
await this._provider.saveTable(this._tableInfo, this._viewModel);
|
||||||
this.updateState(true, false, false, false);
|
this.updateState(true, false);
|
||||||
notificationHandle.updateMessage(localize('tableDesigner.savedChangeSuccess', "The changes have been successfully saved."));
|
notificationHandle.updateMessage(localize('tableDesigner.savedChangeSuccess', "The changes have been successfully saved."));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notificationHandle.updateSeverity(Severity.Error);
|
notificationHandle.updateSeverity(Severity.Error);
|
||||||
notificationHandle.updateMessage(localize('tableDesigner.saveChangeError', "An error occured while saving changes: {0}", error?.message ?? error));
|
notificationHandle.updateMessage(localize('tableDesigner.saveChangeError', "An error occured while saving changes: {0}", error?.message ?? error));
|
||||||
this.updateState(this.valid, this.dirty, false, false);
|
this.updateState(this.valid, this.dirty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async revert(): Promise<void> {
|
async revert(): Promise<void> {
|
||||||
this.updateState(true, false, false, false);
|
this.updateState(true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateState(valid: boolean, dirty: boolean, saving: boolean, processing: boolean): void {
|
private updateState(valid: boolean, dirty: boolean, pendingAction?: DesignerAction): void {
|
||||||
if (this._dirty !== dirty || this._valid !== valid || this._saving !== saving || this._processing !== processing) {
|
if (this._dirty !== dirty || this._valid !== valid || this._pendingAction !== pendingAction) {
|
||||||
this._dirty = dirty;
|
const previousState = {
|
||||||
this._valid = valid;
|
|
||||||
this._saving = saving;
|
|
||||||
this._processing = processing;
|
|
||||||
this._onStateChange.fire({
|
|
||||||
valid: this._valid,
|
valid: this._valid,
|
||||||
dirty: this._dirty,
|
dirty: this._dirty,
|
||||||
saving: this._saving,
|
pendingAction: this._pendingAction
|
||||||
processing: this._processing
|
};
|
||||||
|
|
||||||
|
this._dirty = dirty;
|
||||||
|
this._valid = valid;
|
||||||
|
this._pendingAction = pendingAction;
|
||||||
|
|
||||||
|
const currentState = {
|
||||||
|
valid: this._valid,
|
||||||
|
dirty: this._dirty,
|
||||||
|
pendingAction: this._pendingAction
|
||||||
|
};
|
||||||
|
this._onStateChange.fire({
|
||||||
|
currentState,
|
||||||
|
previousState,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initialize(): Promise<void> {
|
initialize(): void {
|
||||||
this.updateState(this.valid, this.dirty, this.saving, true);
|
if (this._view !== undefined || this.pendingAction === 'initialize') {
|
||||||
const designerInfo = await this._provider.getTableDesignerInfo(this._tableInfo);
|
return;
|
||||||
this.updateState(this.valid, this.dirty, this.saving, false);
|
}
|
||||||
|
|
||||||
|
this.updateState(this.valid, this.dirty, 'initialize');
|
||||||
|
this._provider.getTableDesignerInfo(this._tableInfo).then(result => {
|
||||||
|
this.doInitialization(result);
|
||||||
|
this._onInitialized.fire();
|
||||||
|
}, error => {
|
||||||
|
this._notificationService.error(localize('tableDesigner.errorInitializingTableDesigner', "An error occured while initializing the table designer: {0}", error?.message ?? error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private doInitialization(designerInfo: azdata.designers.TableDesignerInfo): void {
|
||||||
|
this.updateState(true, false);
|
||||||
this._viewModel = designerInfo.viewModel;
|
this._viewModel = designerInfo.viewModel;
|
||||||
this.setDefaultData();
|
this.setDefaultData();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user