table designer bug fixes (#18701)

* table designer bug fixes

* pr comments

* fix debounce issue
This commit is contained in:
Alan Ren
2022-03-11 12:14:51 -08:00
committed by GitHub
parent 5c14fe4f4c
commit 4551329db0
2 changed files with 82 additions and 52 deletions

View File

@@ -10,12 +10,16 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
import { debounce } from 'vs/base/common/decorators';
import { Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
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) => 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 | Dropdown) => void editorStyler: (component: InputBox | SelectBox | Dropdown) => void,
onStyleChange: Event<void>;
} }
export class TableCellEditorFactory { export class TableCellEditorFactory {
@@ -32,18 +36,20 @@ export class TableCellEditorFactory {
optionsGetter: options.optionsGetter ?? function (item, column) { optionsGetter: options.optionsGetter ?? function (item, column) {
return []; return [];
}, },
editorStyler: options.editorStyler editorStyler: options.editorStyler,
onStyleChange: options.onStyleChange
}; };
} }
public getTextEditorClass(context: any, inputType: 'text' | 'number' = 'text'): any { public getTextEditorClass(context: any, inputType: 'text' | 'number' = 'text'): any {
const self = this; const self = this;
class TextEditor { class TextEditor extends Disposable {
private _originalValue: string; private _originalValue: string;
private _input: InputBox; private _input: InputBox;
private _keyCaptureList: number[]; private _keyCaptureList: number[];
constructor(private _args: Slick.Editors.EditorOptions<Slick.SlickData>) { constructor(private _args: Slick.Editors.EditorOptions<Slick.SlickData>) {
super();
this.init(); this.init();
const keycodesToCapture = [KeyCode.Home, KeyCode.End, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow]; const keycodesToCapture = [KeyCode.Home, KeyCode.End, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow];
this._keyCaptureList = keycodesToCapture.map(keycode => getCodeForKeyCode(keycode)); this._keyCaptureList = keycodesToCapture.map(keycode => getCodeForKeyCode(keycode));
@@ -63,10 +69,26 @@ export class TableCellEditorFactory {
self._options.editorStyler(this._input); self._options.editorStyler(this._input);
this._input.element.style.height = '100%'; this._input.element.style.height = '100%';
this._input.focus(); this._input.focus();
this._input.onDidChange(async () => {
await this.commitEdit();
});
this._register(this._input);
this._register(self._options.onStyleChange(() => {
self._options.editorStyler(this._input);
}));
}
@debounce(200)
private async commitEdit(): Promise<void> {
if (this.isValueChanged()) {
const item = this._args.grid.getDataItem(this._args.grid.getActiveCell().row);
await this.applyValue(item, this._input.value);
this._originalValue = this._input.value;
}
} }
public destroy(): void { public destroy(): void {
this._input.dispose(); this.dispose();
} }
public focus(): void { public focus(): void {
@@ -84,8 +106,7 @@ export class TableCellEditorFactory {
} }
public isValueChanged(): boolean { public isValueChanged(): boolean {
return this._input.value !== this._originalValue.toString(); return this._input.value !== this._originalValue;
} }
public serializeValue(): any { public serializeValue(): any {
@@ -104,13 +125,13 @@ export class TableCellEditorFactory {
public getDropdownEditorClass(context: any, defaultOptions: string[], isEditable?: boolean): any { public getDropdownEditorClass(context: any, defaultOptions: string[], isEditable?: boolean): any {
const self = this; const self = this;
class TextEditor { class DropdownEditor extends Disposable {
private _originalValue: string; private _originalValue: string;
private _selectBox: SelectBox; private _component: SelectBox | Dropdown;
private _dropdown: Dropdown;
private _keyCaptureList: number[]; private _keyCaptureList: number[];
constructor(private _args: Slick.Editors.EditorOptions<Slick.SlickData>) { constructor(private _args: Slick.Editors.EditorOptions<Slick.SlickData>) {
super();
this.init(); this.init();
const keycodesToCapture = [KeyCode.Home, KeyCode.End, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow]; const keycodesToCapture = [KeyCode.Home, KeyCode.End, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow];
this._keyCaptureList = keycodesToCapture.map(keycode => getCodeForKeyCode(keycode)); this._keyCaptureList = keycodesToCapture.map(keycode => getCodeForKeyCode(keycode));
@@ -126,37 +147,43 @@ export class TableCellEditorFactory {
public init(): void { public init(): void {
const container = DOM.$(''); const container = DOM.$('');
this._args.container.appendChild(container); this._args.container.appendChild(container);
container.style.height = '100%';
container.style.width = '100%';
if (isEditable) { if (isEditable) {
this._dropdown = new Dropdown(container, self._contextViewProvider); this._component = new Dropdown(container, self._contextViewProvider);
container.style.height = '100%'; this._component.onValueChange(async () => {
container.style.width = '100%'; await this.commitEdit();
self._options.editorStyler(this._dropdown); });
this._dropdown.focus();
} else { } else {
this._selectBox = new SelectBox([], undefined, self._contextViewProvider); this._component = new SelectBox([], undefined, self._contextViewProvider);
container.style.height = '100%'; this._component.render(container);
container.style.width = '100%'; this._component.selectElem.style.height = '100%';
this._selectBox.render(container); this._component.onDidSelect(async () => {
this._selectBox.selectElem.style.height = '100%'; await this.commitEdit();
self._options.editorStyler(this._selectBox); });
this._selectBox.focus(); }
self._options.editorStyler(this._component);
this._component.focus();
this._register(this._component);
this._register(self._options.onStyleChange(() => {
self._options.editorStyler(this._component);
}));
}
private async commitEdit(): Promise<void> {
if (this.isValueChanged()) {
const item = this._args.grid.getDataItem(this._args.grid.getActiveCell().row);
await this.applyValue(item, this._component.value);
this._originalValue = this._component.value;
} }
} }
public destroy(): void { public destroy(): void {
if (isEditable) { this.dispose();
this._dropdown.dispose();
} else {
this._selectBox.dispose();
}
} }
public focus(): void { public focus(): void {
if (isEditable) { this._component.focus();
this._dropdown.focus();
} else {
this._selectBox.focus();
}
} }
public loadValue(item: Slick.SlickData): void { public loadValue(item: Slick.SlickData): void {
@@ -164,12 +191,12 @@ export class TableCellEditorFactory {
const options = self._options.optionsGetter(item, this._args.column) ?? defaultOptions; const options = self._options.optionsGetter(item, this._args.column) ?? defaultOptions;
const idx = options?.indexOf(this._originalValue); const idx = options?.indexOf(this._originalValue);
if (idx > -1) { if (idx > -1) {
if (isEditable) { if (this._component instanceof Dropdown) {
this._dropdown.values = options; this._component.values = options;
this._dropdown.value = options[idx]; this._component.value = options[idx];
} else { } else {
this._selectBox.setOptions(options); this._component.setOptions(options);
this._selectBox.select(idx); this._component.select(idx);
} }
} }
} }
@@ -180,19 +207,11 @@ export class TableCellEditorFactory {
} }
public isValueChanged(): boolean { public isValueChanged(): boolean {
if (isEditable) { return this._component.value !== this._originalValue;
return this._dropdown.value !== this._originalValue.toString();
} else {
return this._selectBox.value !== this._originalValue.toString();
}
} }
public serializeValue(): any { public serializeValue(): any {
if (isEditable) { return this._component.value;
return this._dropdown.value;
} else {
return this._selectBox.value;
}
} }
public validate(): Slick.ValidateResults { public validate(): Slick.ValidateResults {
@@ -202,6 +221,6 @@ export class TableCellEditorFactory {
}; };
} }
} }
return TextEditor; return DropdownEditor;
} }
} }

View File

@@ -11,7 +11,7 @@ import {
from 'sql/workbench/browser/designer/interfaces'; from 'sql/workbench/browser/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 { Emitter, 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, DisposableStore } 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';
@@ -45,6 +45,7 @@ import { alert } from 'vs/base/browser/ui/aria/aria';
import { layoutDesignerTable, TableHeaderRowHeight, TableRowHeight } from 'sql/workbench/browser/designer/designerTableUtil'; import { layoutDesignerTable, TableHeaderRowHeight, TableRowHeight } from 'sql/workbench/browser/designer/designerTableUtil';
import { Dropdown, IDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { Dropdown, IDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { debounce } from 'vs/base/common/decorators';
export interface IDesignerStyle { export interface IDesignerStyle {
tabbedPanelStyles?: ITabbedPanelStyles; tabbedPanelStyles?: ITabbedPanelStyles;
@@ -96,6 +97,7 @@ export class Designer extends Disposable implements IThemable {
private _groupHeaders: HTMLElement[] = []; private _groupHeaders: HTMLElement[] = [];
private _messagesView: DesignerMessagesTabPanelView; private _messagesView: DesignerMessagesTabPanelView;
private _scriptEditorView: DesignerScriptEditorTabPanelView; private _scriptEditorView: DesignerScriptEditorTabPanelView;
private _onStyleChangeEventEmitter = new Emitter<void>();
constructor(private readonly _container: HTMLElement, constructor(private readonly _container: HTMLElement,
@IInstantiationService private readonly _instantiationService: IInstantiationService, @IInstantiationService private readonly _instantiationService: IInstantiationService,
@@ -122,7 +124,8 @@ export class Designer extends Disposable implements IThemable {
}, },
editorStyler: (component) => { editorStyler: (component) => {
this.styleComponent(component); this.styleComponent(component);
} },
onStyleChange: this._onStyleChangeEventEmitter.event
}, this._contextViewProvider }, this._contextViewProvider
); );
this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true }); this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true });
@@ -254,6 +257,7 @@ export class Designer extends Disposable implements IThemable {
}); });
this._propertiesPane.descriptionElement.style.borderColor = styles.paneSeparator.toString(); this._propertiesPane.descriptionElement.style.borderColor = styles.paneSeparator.toString();
this._onStyleChangeEventEmitter.fire();
} }
public layout(dimension: DOM.Dimension) { public layout(dimension: DOM.Dimension) {
@@ -718,9 +722,11 @@ export class Designer extends Disposable implements IThemable {
ariaLabel: inputProperties.title, ariaLabel: inputProperties.title,
type: inputProperties.inputType, type: inputProperties.inputType,
}); });
input.onLoseFocus((args) => { input.onDidChange(() => {
if (args.hasChanged) { // The supress edit processing check is done in the handleEdit method, but since we have debounce operation on input box we
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: args.value, source: view }); // have to do it here to avoid treating system originated value setting operation as user edits.
if (!this._supressEditProcessing) {
this.handleInputBoxEdit({ type: DesignerEditType.Update, path: propertyPath, value: input.value, source: view });
} }
}); });
input.onInputFocus(() => { input.onInputFocus(() => {
@@ -937,6 +943,11 @@ export class Designer extends Disposable implements IThemable {
return component; return component;
} }
@debounce(200)
private handleInputBoxEdit(edit: DesignerEdit) {
this.handleEdit(edit);
}
private startLoading(message: string, timeout: number): void { private startLoading(message: string, timeout: number): void {
this._loadingTimeoutHandle = setTimeout(() => { this._loadingTimeoutHandle = setTimeout(() => {
this._loadingSpinner.loadingMessage = message; this._loadingSpinner.loadingMessage = message;