mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 * as DOM from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
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 { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import 'vs/css!./media/designer';
|
||||
@@ -63,6 +63,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
private _tableCellEditorFactory: TableCellEditorFactory;
|
||||
private _propertiesPane: DesignerPropertiesPane;
|
||||
private _buttons: Button[] = [];
|
||||
private _inputDisposable: DisposableStore;
|
||||
private _loadingTimeoutHandle: any;
|
||||
|
||||
constructor(private readonly _container: HTMLElement,
|
||||
private readonly _contextViewProvider: IContextViewProvider) {
|
||||
@@ -72,8 +74,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
valueGetter: (item, column): string => {
|
||||
return item[column.field].value;
|
||||
},
|
||||
valueSetter: async (context: string, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): Promise<void> => {
|
||||
await this.handleEdit({
|
||||
valueSetter: (context: string, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Update,
|
||||
property: {
|
||||
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 {
|
||||
// Save state
|
||||
if (this._input) {
|
||||
this._input.designerUIState = this.getUIState();
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (this._loadingTimeoutHandle) {
|
||||
this.stopLoading();
|
||||
}
|
||||
this._buttons = [];
|
||||
this._componentMap.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
this._tabbedPanel.clearTabs();
|
||||
this._propertiesPane.clear();
|
||||
this._inputDisposable?.dispose();
|
||||
|
||||
|
||||
// Initialize with new input
|
||||
this._input = input;
|
||||
await this.initializeDesignerView();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeDesignerView(): Promise<void> {
|
||||
this._propertiesPane.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
// For initialization, we would want to show the loading indicator immediately.
|
||||
const handle = this.startLoading(localize('designer.loadingDesigner', "Loading designer..."), 0);
|
||||
const view = await this._input.getView();
|
||||
this.stopLoading(handle, localize('designer.loadingDesignerCompleted', "Loading designer completed"));
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
this._inputDisposable?.dispose();
|
||||
}
|
||||
|
||||
private initializeDesigner(): void {
|
||||
const view = this._input.view;
|
||||
if (view.components) {
|
||||
view.components.forEach(component => {
|
||||
this.createComponent(this._topContentContainer, component, component.propertyName, true, true);
|
||||
});
|
||||
}
|
||||
this._tabbedPanel.clearTabs();
|
||||
view.tabs.forEach(tab => {
|
||||
this._tabbedPanel.pushTab(this.createTabView(tab));
|
||||
});
|
||||
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() {
|
||||
this._tabbedPanel.layout(new DOM.Dimension(this._tabbedPanelContainer.clientWidth, this._tabbedPanelContainer.clientHeight));
|
||||
}
|
||||
|
||||
private async updatePropertiesPane(newContext: PropertiesPaneObjectContext): Promise<void> {
|
||||
const viewModel = await this._input.getViewModel();
|
||||
private updatePropertiesPane(newContext: PropertiesPaneObjectContext): void {
|
||||
const viewModel = this._input.viewModel;
|
||||
let type: string;
|
||||
let components: DesignerDataPropertyInfo[];
|
||||
let inputViewModel: DesignerViewModel;
|
||||
@@ -260,42 +352,25 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
}
|
||||
|
||||
private async updateComponentValues(): Promise<void> {
|
||||
const viewModel = await this._input.getViewModel();
|
||||
private updateComponentValues(): void {
|
||||
const viewModel = this._input.viewModel;
|
||||
// data[ScriptPropertyName] -- todo- set the script editor
|
||||
this._componentMap.forEach((value) => {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
await this.applyEdit(edit);
|
||||
const handle = this.startLoading(localize('designer.processingChanges', "Processing changes..."));
|
||||
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
|
||||
}
|
||||
this.applyEdit(edit);
|
||||
this._input.processEdit(edit);
|
||||
}
|
||||
|
||||
private async applyEdit(edit: DesignerEdit): Promise<void> {
|
||||
const viewModel = await this._input.getViewModel();
|
||||
private applyEdit(edit: DesignerEdit): void {
|
||||
const viewModel = this._input.viewModel;
|
||||
switch (edit.type) {
|
||||
case DesignerEditType.Update:
|
||||
if (typeof edit.property === 'string') {
|
||||
@@ -423,9 +498,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
ariaLabel: inputProperties.title,
|
||||
type: inputProperties.inputType,
|
||||
});
|
||||
input.onLoseFocus(async (args) => {
|
||||
input.onLoseFocus((args) => {
|
||||
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) {
|
||||
@@ -440,8 +515,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
const dropdown = new SelectBox(dropdownProperties.values as string[], undefined, this._contextViewProvider, undefined);
|
||||
dropdown.render(dropdownContainer);
|
||||
dropdown.selectElem.style.height = '25px';
|
||||
dropdown.onDidSelect(async (e) => {
|
||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected });
|
||||
dropdown.onDidSelect((e) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected });
|
||||
});
|
||||
component = dropdown;
|
||||
break;
|
||||
@@ -452,8 +527,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
const checkbox = new Checkbox(checkboxContainer, {
|
||||
label: checkboxProperties.title
|
||||
});
|
||||
checkbox.onChange(async (newValue) => {
|
||||
await this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue });
|
||||
checkbox.onChange((newValue) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue });
|
||||
});
|
||||
component = checkbox;
|
||||
break;
|
||||
@@ -465,8 +540,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
title: addNewText,
|
||||
secondary: true
|
||||
});
|
||||
addRowButton.onDidClick(async () => {
|
||||
await this.handleEdit({
|
||||
addRowButton.onDidClick(() => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Add,
|
||||
property: componentDefinition.propertyName,
|
||||
});
|
||||
@@ -502,8 +577,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
width: propertyDefinition.componentProperties.width as number
|
||||
});
|
||||
table.registerPlugin(checkboxColumn);
|
||||
checkboxColumn.onChange(async (e) => {
|
||||
await this.handleEdit({
|
||||
checkboxColumn.onChange((e) => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Update,
|
||||
property: {
|
||||
parentProperty: componentDefinition.propertyName,
|
||||
@@ -540,10 +615,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
resizable: false,
|
||||
isFontIcon: true
|
||||
});
|
||||
deleteRowColumn.onClick(async (e) => {
|
||||
const viewModel = await this._input.getViewModel();
|
||||
(viewModel[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1);
|
||||
await this.handleEdit({
|
||||
deleteRowColumn.onClick((e) => {
|
||||
(this._input.viewModel[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1);
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Remove,
|
||||
property: componentDefinition.propertyName,
|
||||
value: e.item
|
||||
@@ -555,9 +629,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
table.grid.onBeforeEditCell.subscribe((e, data): boolean => {
|
||||
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) {
|
||||
await this.updatePropertiesPane({
|
||||
this.updatePropertiesPane({
|
||||
parentProperty: componentDefinition.propertyName,
|
||||
index: data.row
|
||||
});
|
||||
@@ -578,21 +652,37 @@ export class Designer extends Disposable implements IThemable {
|
||||
return component;
|
||||
}
|
||||
|
||||
private startLoading(message: string, timeout: number = 500): any {
|
||||
// To make the experience smoother, only show the loading indicator if the request is not returning in 500ms(default value).
|
||||
return setTimeout(() => {
|
||||
private startLoading(message: string, timeout: number): void {
|
||||
this._loadingTimeoutHandle = setTimeout(() => {
|
||||
this._loadingSpinner.loadingMessage = message;
|
||||
this._loadingSpinner.loading = true;
|
||||
this._container.removeChild(this._verticalSplitViewContainer);
|
||||
if (this._container.contains(this._verticalSplitViewContainer)) {
|
||||
this._container.removeChild(this._verticalSplitViewContainer);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
private stopLoading(handle: any, message: string): void {
|
||||
clearTimeout(handle);
|
||||
private stopLoading(message: string = ''): void {
|
||||
clearTimeout(this._loadingTimeoutHandle);
|
||||
this._loadingTimeoutHandle = undefined;
|
||||
if (this._loadingSpinner.loading) {
|
||||
this._loadingSpinner.loadingCompletedMessage = message;
|
||||
this._loadingSpinner.loading = false;
|
||||
this._container.appendChild(this._verticalSplitViewContainer);
|
||||
if (!this._container.contains(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user