mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 09:35:41 -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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user