multi-level table support (#17638)

* multi-level table support

* comments

* address comments

* add period to end of sentence.
This commit is contained in:
Alan Ren
2021-11-10 17:15:08 -08:00
committed by GitHub
parent e0ad23a559
commit 1a9f2a5903
7 changed files with 164 additions and 177 deletions

View File

@@ -1104,7 +1104,7 @@ declare module 'azdata' {
} }
/** /**
* The table designer view definition * The table designer view definition.
*/ */
export interface TableDesignerView { export interface TableDesignerView {
/** /**
@@ -1141,11 +1141,11 @@ declare module 'azdata' {
} }
/** /**
* The definition of a designer tab * The definition of a designer tab.
*/ */
export interface DesignerTab { export interface DesignerTab {
/** /**
* The title of the tab * The title of the tab.
*/ */
title: string; title: string;
/** /**
@@ -1159,22 +1159,25 @@ declare module 'azdata' {
*/ */
export interface DesignerDataPropertyInfo { export interface DesignerDataPropertyInfo {
/** /**
* The property name * The property name.
*/ */
propertyName: string; propertyName: string;
/** /**
* The description of the property * The description of the property.
*/ */
description?: string; description?: string;
/** /**
* The component type * The component type.
*/ */
componentType: DesignerComponentTypeName; componentType: DesignerComponentTypeName;
/** /**
* The group name, properties with the same group name will be displayed under the same group on the UI. * The group name, properties with the same group name will be displayed under the same group on the UI.
*/ */
group?: string; group?: string;
/**
* Whether the property should be displayed in the properties view. The default value is true.
*/
showInPropertiesView?: boolean;
/** /**
* The properties of the component. * The properties of the component.
*/ */
@@ -1196,12 +1199,12 @@ declare module 'azdata' {
columns?: string[]; columns?: string[];
/** /**
* The display name of the object type * The display name of the object type.
*/ */
objectTypeDisplayName: string; objectTypeDisplayName: string;
/** /**
* the properties of the table data item * the properties of the table data item.
*/ */
itemProperties?: DesignerDataPropertyInfo[]; itemProperties?: DesignerDataPropertyInfo[];
@@ -1233,15 +1236,15 @@ declare module 'azdata' {
*/ */
export enum DesignerEditType { export enum DesignerEditType {
/** /**
* Add a row to a table * Add a row to a table.
*/ */
Add = 0, Add = 0,
/** /**
* Remove a row from a table * Remove a row from a table.
*/ */
Remove = 1, Remove = 1,
/** /**
* Update a property * Update a property.
*/ */
Update = 2 Update = 2
} }
@@ -1251,23 +1254,35 @@ declare module 'azdata' {
*/ */
export interface DesignerEdit { export interface DesignerEdit {
/** /**
* The edit type * The edit type.
*/ */
type: DesignerEditType; type: DesignerEditType;
/** /**
* the property that was edited * the path of the edit target.
*/ */
property: DesignerEditIdentifier; path: DesignerEditPath;
/** /**
* the new value * the new value.
*/ */
value?: any; value?: any;
} }
/** /**
* The identifier of a property. The value is string typed if the property belongs to the root object, otherwise the type of the value is an object. * The path of the edit target.
* Below are the 3 scenarios and their expected path.
* Note: 'index-{x}' in the description below are numbers represent the index of the object in the list.
* 1. 'Add' scenario
* a. ['propertyName1']. Example: add a column to the columns property: ['columns'].
* b. ['propertyName1',index-1,'propertyName2']. Example: add a column mapping to the first foreign key: ['foreignKeys',0,'mappings'].
* 2. 'Update' scenario
* a. ['propertyName1']. Example: update the name of the table: ['name'].
* b. ['propertyName1',index-1,'propertyName2']. Example: update the name of a column: ['columns',0,'name'].
* c. ['propertyName1',index-1,'propertyName2',index-2,'propertyName3']. Example: update the source column of an entry in a foreign key's column mapping table: ['foreignKeys',0,'mappings',0,'source'].
* 3. 'Remove' scenario
* a. ['propertyName1',index-1]. Example: remove a column from the columns property: ['columns',0'].
* b. ['propertyName1',index-1,'propertyName2',index-2]. Example: remove a column mapping from a foreign key's column mapping table: ['foreignKeys',0,'mappings',0].
*/ */
export type DesignerEditIdentifier = string | { parentProperty: string, index: number, property: string }; export type DesignerEditPath = (string | number)[];
/** /**
* The result returned by the table designer provider after handling an edit request. * The result returned by the table designer provider after handling an edit request.
@@ -1284,7 +1299,7 @@ declare module 'azdata' {
/** /**
* Error messages of current state, and the property the caused the error. * Error messages of current state, and the property the caused the error.
*/ */
errors?: { message: string, property?: DesignerEditIdentifier }[]; errors?: { message: string, property?: DesignerEditPath }[];
} }
} }
} }

View File

@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { import {
DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerEditIdentifier, DesignerViewModel, DesignerDataPropertyInfo, DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerEditPath, DesignerViewModel, DesignerDataPropertyInfo,
DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties, DesignerComponentTypeName, DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties,
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState, DesignerTextEditor, ScriptProperty DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState, DesignerTextEditor, ScriptProperty, DesignerRootObjectPath
} }
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';
@@ -26,7 +26,7 @@ import { localize } from 'vs/nls';
import { TableCellEditorFactory } from 'sql/base/browser/ui/table/tableCellEditorFactory'; import { TableCellEditorFactory } from 'sql/base/browser/ui/table/tableCellEditorFactory';
import { CheckBoxColumn } from 'sql/base/browser/ui/table/plugins/checkboxColumn.plugin'; import { CheckBoxColumn } from 'sql/base/browser/ui/table/plugins/checkboxColumn.plugin';
import { DesignerTabPanelView } from 'sql/workbench/browser/designer/designerTabPanelView'; import { DesignerTabPanelView } from 'sql/workbench/browser/designer/designerTabPanelView';
import { DesignerPropertiesPane, PropertiesPaneObjectContext } from 'sql/workbench/browser/designer/designerPropertiesPane'; import { DesignerPropertiesPane } from 'sql/workbench/browser/designer/designerPropertiesPane';
import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button'; import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button';
import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin'; import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin';
import { Codicon } from 'vs/base/common/codicons'; import { Codicon } from 'vs/base/common/codicons';
@@ -35,6 +35,7 @@ import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinne
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { DesignerScriptEditor } from 'sql/workbench/browser/designer/designerScriptEditor'; import { DesignerScriptEditor } from 'sql/workbench/browser/designer/designerScriptEditor';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface IDesignerStyle { export interface IDesignerStyle {
tabbedPanelStyles?: ITabbedPanelStyles; tabbedPanelStyles?: ITabbedPanelStyles;
@@ -49,9 +50,12 @@ export interface IDesignerStyle {
export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> | SelectBox; export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> | SelectBox;
export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], editIdentifierGetter: (property: DesignerDataPropertyInfo) => DesignerEditIdentifier) => DesignerUIComponent[]; export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], parentPath: DesignerEditPath) => DesignerUIComponent[];
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void; export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
const TableRowHeight = 23;
const TableHeaderRowHeight = 28;
export class Designer extends Disposable implements IThemable { export class Designer extends Disposable implements IThemable {
private _loadingSpinner: LoadingSpinner; private _loadingSpinner: LoadingSpinner;
private _horizontalSplitViewContainer: HTMLElement; private _horizontalSplitViewContainer: HTMLElement;
@@ -78,21 +82,18 @@ export class Designer extends Disposable implements IThemable {
constructor(private readonly _container: HTMLElement, constructor(private readonly _container: HTMLElement,
@IInstantiationService private readonly _instantiationService: IInstantiationService, @IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextViewService private readonly _contextViewProvider: IContextViewService) { @IContextViewService private readonly _contextViewProvider: IContextViewService,
@INotificationService private readonly _notificationService: INotificationService) {
super(); super();
this._tableCellEditorFactory = new TableCellEditorFactory( this._tableCellEditorFactory = new TableCellEditorFactory(
{ {
valueGetter: (item, column): string => { valueGetter: (item, column): string => {
return item[column.field].value; return item[column.field].value;
}, },
valueSetter: (context: string, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => { valueSetter: (parentPath: DesignerEditPath, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
this.handleEdit({ this.handleEdit({
type: DesignerEditType.Update, type: DesignerEditType.Update,
property: { path: [...parentPath, row, column.field],
parentProperty: context,
index: row,
property: column.field
},
value: value value: value
}); });
}, },
@@ -150,14 +151,16 @@ export class Designer extends Disposable implements IThemable {
this._horizontalSplitView.addView({ this._horizontalSplitView.addView({
element: this._propertiesPaneContainer, element: this._propertiesPaneContainer,
layout: size => { }, layout: size => {
this.layoutPropertiesPane();
},
minimumSize: 200, minimumSize: 200,
maximumSize: Number.POSITIVE_INFINITY, maximumSize: Number.POSITIVE_INFINITY,
onDidChange: Event.None onDidChange: Event.None
}, Sizing.Distribute); }, Sizing.Distribute);
this._propertiesPane = new DesignerPropertiesPane(this._propertiesPaneContainer, (container, components, identifierGetter) => { this._propertiesPane = new DesignerPropertiesPane(this._propertiesPaneContainer, (container, components, parentPath) => {
return this.createComponents(container, components, this._propertiesPane.componentMap, this._propertiesPane.groupHeaders, identifierGetter, false, true); return this.createComponents(container, components, this._propertiesPane.componentMap, this._propertiesPane.groupHeaders, parentPath, false, false);
}, (definition, component, viewModel) => { }, (definition, component, viewModel) => {
this.setComponentValue(definition, component, viewModel); this.setComponentValue(definition, component, viewModel);
}); });
@@ -272,13 +275,14 @@ export class Designer extends Disposable implements IThemable {
private initializeDesigner(): void { private initializeDesigner(): void {
const view = this._input.view; const view = this._input.view;
if (view.components) { if (view.components) {
this.createComponents(this._topContentContainer, view.components, this._componentMap, this._groupHeaders, component => component.propertyName, true, false); this.createComponents(this._topContentContainer, view.components, this._componentMap, this._groupHeaders, DesignerRootObjectPath, true, true);
} }
view.tabs.forEach(tab => { view.tabs.forEach(tab => {
this._tabbedPanel.pushTab(this.createTabView(tab)); this._tabbedPanel.pushTab(this.createTabView(tab));
}); });
this.layoutTabbedPanel(); this.layoutTabbedPanel();
this.updateComponentValues(); this.updateComponentValues();
this.updatePropertiesPane(DesignerRootObjectPath);
this.restoreUIState(); this.restoreUIState();
} }
@@ -287,17 +291,26 @@ export class Designer extends Disposable implements IThemable {
const result = args.result; const result = args.result;
if (result.isValid) { if (result.isValid) {
this._supressEditProcessing = true; this._supressEditProcessing = true;
this.updateComponentValues(); try {
if (edit.type === DesignerEditType.Add) { this.updateComponentValues();
// Move focus to the first cell of the newly added row. if (edit.type === DesignerEditType.Add) {
const propertyName = edit.property as string; // For tables in the main view, move focus to the first cell of the newly added row, and the properties pane will be showing the new object.
const tableData = this._input.viewModel[propertyName] as DesignerTableProperties; if (edit.path.length === 1) {
const table = this._componentMap.get(propertyName).component as Table<Slick.SlickData>; const propertyName = edit.path[0] as string;
table.setActiveCell(tableData.data.length - 1, 0); 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);
}
} else if (edit.type === DesignerEditType.Update) {
// for edit, update the properties pane with new values of current object.
this.updatePropertiesPane(this._propertiesPane.objectPath);
}
} catch (err) {
this._notificationService.error(err);
} }
this._supressEditProcessing = false; this._supressEditProcessing = false;
} else { } else {
//TODO: add error notification this._notificationService.error(result.errors.map(e => e.message));
} }
} }
@@ -340,39 +353,53 @@ export class Designer extends Disposable implements IThemable {
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 updatePropertiesPane(newContext: PropertiesPaneObjectContext): void { private layoutPropertiesPane() {
const viewModel = this._input.viewModel; this._propertiesPane?.componentMap.forEach((v) => {
if (v.component instanceof Table) {
const rows = v.component.getData().getLength();
// Tables in properties pane, minimum height:2 rows, maximum height: 10 rows.
const actualHeight = this.getTableHeight(rows);
const minHeight = this.getTableHeight(2);
const maxHeight = this.getTableHeight(10);
const height = Math.min(Math.max(minHeight, actualHeight), maxHeight);
v.component.layout(new DOM.Dimension(this._propertiesPaneContainer.clientWidth - 10 /*padding*/, height));
}
});
}
private getTableHeight(rows: number): number {
return rows * TableRowHeight + TableHeaderRowHeight;
}
private updatePropertiesPane(objectPath: DesignerEditPath): void {
let type: string; let type: string;
let components: DesignerDataPropertyInfo[]; let components: DesignerDataPropertyInfo[];
let inputViewModel: DesignerViewModel; let objectViewModel: DesignerViewModel;
let context: PropertiesPaneObjectContext; if (objectPath.length === 0) { // root object
if (newContext !== 'root') { type = this._input.objectTypeDisplayName;
context = newContext; components = [];
const tableData = viewModel[newContext.parentProperty] as DesignerTableProperties; components.push(...this._input.view.components);
const tableProperties = this._componentMap.get(newContext.parentProperty).defintion.componentProperties as DesignerTableProperties; this._input.view.tabs.forEach(tab => {
inputViewModel = tableData.data[newContext.index] as DesignerViewModel; components.push(...tab.components);
});
objectViewModel = this._input.viewModel;
} else if (objectPath.length === 2) { // second level object
const parentPropertyName = objectPath[0] as string;
const objectIndex = objectPath[1] as number;
const tableData = this._input.viewModel[parentPropertyName] as DesignerTableProperties;
const tableProperties = this._componentMap.get(parentPropertyName).defintion.componentProperties as DesignerTableProperties;
objectViewModel = tableData.data[objectIndex] as DesignerViewModel;
components = tableProperties.itemProperties; components = tableProperties.itemProperties;
type = tableProperties.objectTypeDisplayName; type = tableProperties.objectTypeDisplayName;
} }
if (!inputViewModel) { this._propertiesPane.show({
context = 'root'; path: objectPath,
components = []; type: type,
this._componentMap.forEach(value => { components: components,
components.push(value.defintion); viewModel: objectViewModel
}); });
type = this._input.objectTypeDisplayName; this.layoutPropertiesPane();
inputViewModel = viewModel;
}
if (inputViewModel) {
this._propertiesPane.show({
context: context,
type: type,
components: components,
viewModel: inputViewModel
});
}
} }
private updateComponentValues(): void { private updateComponentValues(): void {
@@ -384,65 +411,18 @@ export class Designer extends Disposable implements IThemable {
this._componentMap.forEach((value) => { this._componentMap.forEach((value) => {
this.setComponentValue(value.defintion, value.component, viewModel); this.setComponentValue(value.defintion, value.component, viewModel);
}); });
this.updatePropertiesPane(this._propertiesPane.context ?? 'root');
} }
private handleEdit(edit: DesignerEdit): void { private handleEdit(edit: DesignerEdit): void {
if (this._supressEditProcessing) { if (this._supressEditProcessing) {
return; return;
} }
this.applyEdit(edit);
this._input.processEdit(edit); this._input.processEdit(edit);
} }
private applyEdit(edit: DesignerEdit): void {
const viewModel = this._input.viewModel;
switch (edit.type) {
case DesignerEditType.Update:
if (typeof edit.property === 'string') {
// if the type of the property is string then the property is a top level property
if (!viewModel[edit.property]) {
viewModel[edit.property] = {};
}
const componentData = viewModel[edit.property];
const componentType = this._componentMap.get(edit.property).defintion.componentType;
this.setComponentData(componentType, componentData, edit.value);
} else {
const columnPropertyName = edit.property.property;
const tableInfo = this._componentMap.get(edit.property.parentProperty).defintion.componentProperties as DesignerTableProperties;
const tableProperties = viewModel[edit.property.parentProperty] as DesignerTableProperties;
if (!tableProperties.data[edit.property.index][columnPropertyName]) {
tableProperties.data[edit.property.index][columnPropertyName] = {};
}
const componentData = tableProperties.data[edit.property.index][columnPropertyName];
const itemProperty = tableInfo.itemProperties.find(property => property.propertyName === columnPropertyName);
if (itemProperty) {
this.setComponentData(itemProperty.componentType, componentData, edit.value);
}
}
break;
default:
break;
}
}
private setComponentData(componentType: DesignerComponentTypeName, componentData: any, value: any): void {
switch (componentType) {
case 'checkbox':
(<CheckBoxProperties>componentData).checked = value;
break;
case 'dropdown':
(<DropDownProperties>componentData).value = value;
break;
case 'input':
(<InputBoxProperties>componentData).value = value;
break;
}
}
private createTabView(tab: DesignerTab): IPanelTab { private createTabView(tab: DesignerTab): IPanelTab {
const view = new DesignerTabPanelView(tab, (container, components, identifierGetter) => { const view = new DesignerTabPanelView(tab, (container, components, identifierGetter) => {
return this.createComponents(container, components, this._componentMap, this._groupHeaders, identifierGetter, true, false); return this.createComponents(container, components, this._componentMap, this._groupHeaders, identifierGetter, true, true);
}); });
return { return {
identifier: tab.title, identifier: tab.title,
@@ -517,11 +497,11 @@ export class Designer extends Disposable implements IThemable {
components: DesignerDataPropertyInfo[], components: DesignerDataPropertyInfo[],
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>, componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
groupHeaders: HTMLElement[], groupHeaders: HTMLElement[],
identifierGetter: (definition: DesignerDataPropertyInfo) => DesignerEditIdentifier, parentPath: DesignerEditPath,
setWidth: boolean, skipTableCreation: boolean = false): DesignerUIComponent[] { setWidth: boolean, isMainView: boolean): DesignerUIComponent[] {
const uiComponents = []; const uiComponents = [];
const groupNames = []; const groupNames = [];
const componentsToCreate = skipTableCreation ? components.filter(component => component.componentType !== 'table') : components; const componentsToCreate = !isMainView ? components.filter(component => component.showInPropertiesView !== false) : components;
componentsToCreate.forEach(component => { componentsToCreate.forEach(component => {
if (groupNames.indexOf(component.group) === -1) { if (groupNames.indexOf(component.group) === -1) {
groupNames.push(component.group); groupNames.push(component.group);
@@ -531,7 +511,7 @@ export class Designer extends Disposable implements IThemable {
// only show groups when there are multiple of them. // only show groups when there are multiple of them.
if (groupNames.length < 2) { if (groupNames.length < 2) {
componentsToCreate.forEach(component => { componentsToCreate.forEach(component => {
uiComponents.push(this.createComponent(container, component, identifierGetter(component), componentMap, setWidth)); uiComponents.push(this.createComponent(container, component, parentPath, componentMap, setWidth, isMainView));
}); });
} else { } else {
groupNames.forEach(group => { groupNames.forEach(group => {
@@ -541,7 +521,7 @@ export class Designer extends Disposable implements IThemable {
groupHeader.innerText = group ?? localize('designer.generalGroupName', "General"); groupHeader.innerText = group ?? localize('designer.generalGroupName', "General");
componentsToCreate.forEach(component => { componentsToCreate.forEach(component => {
if (component.group === group) { if (component.group === group) {
uiComponents.push(this.createComponent(container, component, identifierGetter(component), componentMap, setWidth)); uiComponents.push(this.createComponent(container, component, parentPath, componentMap, setWidth, isMainView));
} }
}); });
}); });
@@ -551,9 +531,11 @@ export class Designer extends Disposable implements IThemable {
private createComponent(container: HTMLElement, private createComponent(container: HTMLElement,
componentDefinition: DesignerDataPropertyInfo, componentDefinition: DesignerDataPropertyInfo,
editIdentifier: DesignerEditIdentifier, parentPath: DesignerEditPath,
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>, componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
setWidth: boolean): DesignerUIComponent { setWidth: boolean,
isMainView: boolean): DesignerUIComponent {
const propertyPath = [...parentPath, componentDefinition.propertyName];
let component: DesignerUIComponent; let component: DesignerUIComponent;
switch (componentDefinition.componentType) { switch (componentDefinition.componentType) {
case 'input': case 'input':
@@ -566,7 +548,7 @@ export class Designer extends Disposable implements IThemable {
}); });
input.onLoseFocus((args) => { input.onLoseFocus((args) => {
if (args.hasChanged) { if (args.hasChanged) {
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: args.value }); this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: args.value });
} }
}); });
if (setWidth && inputProperties.width !== undefined) { if (setWidth && inputProperties.width !== undefined) {
@@ -582,7 +564,7 @@ export class Designer extends Disposable implements IThemable {
dropdown.render(dropdownContainer); dropdown.render(dropdownContainer);
dropdown.selectElem.style.height = '25px'; dropdown.selectElem.style.height = '25px';
dropdown.onDidSelect((e) => { dropdown.onDidSelect((e) => {
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected }); this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: e.selected });
}); });
component = dropdown; component = dropdown;
break; break;
@@ -594,11 +576,14 @@ export class Designer extends Disposable implements IThemable {
label: checkboxProperties.title label: checkboxProperties.title
}); });
checkbox.onChange((newValue) => { checkbox.onChange((newValue) => {
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue }); this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: newValue });
}); });
component = checkbox; component = checkbox;
break; break;
case 'table': case 'table':
if (!isMainView) {
container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? '';
}
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties; const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
if (tableProperties.canAddRows !== false) { if (tableProperties.canAddRows !== false) {
const buttonContainer = container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('.add-row-button-container')); const buttonContainer = container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('.add-row-button-container'));
@@ -610,7 +595,7 @@ export class Designer extends Disposable implements IThemable {
addRowButton.onDidClick(() => { addRowButton.onDidClick(() => {
this.handleEdit({ this.handleEdit({
type: DesignerEditType.Add, type: DesignerEditType.Add,
property: componentDefinition.propertyName, path: propertyPath
}); });
}); });
this.styleComponent(addRowButton); this.styleComponent(addRowButton);
@@ -632,7 +617,9 @@ export class Designer extends Disposable implements IThemable {
} else { } else {
return undefined; return undefined;
} }
} },
rowHeight: TableRowHeight,
headerRowHeight: TableHeaderRowHeight
}); });
table.ariaLabel = tableProperties.ariaLabel; table.ariaLabel = tableProperties.ariaLabel;
const columns = tableProperties.columns.map(propName => { const columns = tableProperties.columns.map(propName => {
@@ -648,11 +635,7 @@ export class Designer extends Disposable implements IThemable {
checkboxColumn.onChange((e) => { checkboxColumn.onChange((e) => {
this.handleEdit({ this.handleEdit({
type: DesignerEditType.Update, type: DesignerEditType.Update,
property: { path: [...propertyPath, e.row, propertyDefinition.propertyName],
parentProperty: componentDefinition.propertyName,
index: e.row,
property: propertyDefinition.propertyName
},
value: e.value value: e.value
}); });
}); });
@@ -662,7 +645,7 @@ export class Designer extends Disposable implements IThemable {
return { return {
name: dropdownProperties.title, name: dropdownProperties.title,
field: propertyDefinition.propertyName, field: propertyDefinition.propertyName,
editor: this._tableCellEditorFactory.getSelectBoxEditorClass(componentDefinition.propertyName, dropdownProperties.values as string[]), editor: this._tableCellEditorFactory.getSelectBoxEditorClass(propertyPath, dropdownProperties.values as string[]),
width: dropdownProperties.width as number width: dropdownProperties.width as number
}; };
default: default:
@@ -670,7 +653,7 @@ export class Designer extends Disposable implements IThemable {
return { return {
name: inputProperties.title, name: inputProperties.title,
field: propertyDefinition.propertyName, field: propertyDefinition.propertyName,
editor: this._tableCellEditorFactory.getTextEditorClass(componentDefinition.propertyName, inputProperties.inputType), editor: this._tableCellEditorFactory.getTextEditorClass(propertyPath, inputProperties.inputType),
width: inputProperties.width as number width: inputProperties.width as number
}; };
} }
@@ -685,11 +668,9 @@ export class Designer extends Disposable implements IThemable {
isFontIcon: true isFontIcon: true
}); });
deleteRowColumn.onClick((e) => { deleteRowColumn.onClick((e) => {
(this._input.viewModel[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1);
this.handleEdit({ this.handleEdit({
type: DesignerEditType.Remove, type: DesignerEditType.Remove,
property: componentDefinition.propertyName, path: [...propertyPath, e.row]
value: e.item
}); });
}); });
table.registerPlugin(deleteRowColumn); table.registerPlugin(deleteRowColumn);
@@ -699,14 +680,15 @@ 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((e, data) => { if (isMainView === true) {
if (data.row !== undefined) { table.grid.onActiveCellChanged.subscribe((e, data) => {
this.updatePropertiesPane({ if (data.row !== undefined) {
parentProperty: componentDefinition.propertyName, this.updatePropertiesPane([...propertyPath, data.row]);
index: data.row } else {
}); this.updatePropertiesPane(DesignerRootObjectPath);
} }
}); });
}
component = table; component = table;
break; break;
default: default:

View File

@@ -4,18 +4,13 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { CreateComponentsFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/workbench/browser/designer/designer'; import { CreateComponentsFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/workbench/browser/designer/designer';
import { DesignerViewModel, DesignerDataPropertyInfo } from 'sql/workbench/browser/designer/interfaces'; import { DesignerViewModel, DesignerDataPropertyInfo, DesignerEditPath } from 'sql/workbench/browser/designer/interfaces';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { equals } from 'vs/base/common/objects'; import { equals } from 'vs/base/common/objects';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
export type PropertiesPaneObjectContext = 'root' | {
parentProperty: string;
index: number;
};
export interface ObjectInfo { export interface ObjectInfo {
context: PropertiesPaneObjectContext; path: DesignerEditPath;
type: string; type: string;
components: DesignerDataPropertyInfo[]; components: DesignerDataPropertyInfo[];
viewModel: DesignerViewModel; viewModel: DesignerViewModel;
@@ -24,7 +19,7 @@ export interface ObjectInfo {
export class DesignerPropertiesPane { export class DesignerPropertiesPane {
private _titleElement: HTMLElement; private _titleElement: HTMLElement;
private _contentElement: HTMLElement; private _contentElement: HTMLElement;
private _currentContext?: PropertiesPaneObjectContext; private _objectPath: DesignerEditPath;
private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>(); private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>();
private _groupHeaders: HTMLElement[] = []; private _groupHeaders: HTMLElement[] = [];
@@ -43,8 +38,8 @@ export class DesignerPropertiesPane {
return this._componentMap; return this._componentMap;
} }
public get context(): PropertiesPaneObjectContext | undefined { public get objectPath(): DesignerEditPath {
return this._currentContext; return this._objectPath;
} }
public clear(): void { public clear(): void {
@@ -54,20 +49,14 @@ export class DesignerPropertiesPane {
this._componentMap.clear(); this._componentMap.clear();
this._groupHeaders = []; this._groupHeaders = [];
DOM.clearNode(this._contentElement); DOM.clearNode(this._contentElement);
this._currentContext = undefined; this._objectPath = undefined;
} }
public show(item: ObjectInfo): void { public show(item: ObjectInfo): void {
if (!equals(item.context, this._currentContext)) { if (!equals(item.path, this._objectPath)) {
this.clear(); this.clear();
this._currentContext = item.context; this._objectPath = item.path;
this._createComponents(this._contentElement, item.components, (property) => { this._createComponents(this._contentElement, item.components, this.objectPath);
return this._currentContext === 'root' ? property.propertyName : {
parentProperty: this._currentContext.parentProperty,
index: this._currentContext.index,
property: property.propertyName
};
});
} }
this._titleElement.innerText = localize({ this._titleElement.innerText = localize({
key: 'tableDesigner.propertiesPaneTitleWithContext', key: 'tableDesigner.propertiesPaneTitleWithContext',

View File

@@ -3,7 +3,7 @@
* 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 { DesignerTab } from 'sql/workbench/browser/designer/interfaces'; import { DesignerRootObjectPath, DesignerTab } from 'sql/workbench/browser/designer/interfaces';
import { IPanelView } from 'sql/base/browser/ui/panel/panel'; import { IPanelView } from 'sql/base/browser/ui/panel/panel';
import { Table } from 'sql/base/browser/ui/table/table'; import { Table } from 'sql/base/browser/ui/table/table';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
@@ -20,7 +20,7 @@ export class DesignerTabPanelView extends Disposable implements IPanelView {
constructor(private readonly _tab: DesignerTab, private _createComponents: CreateComponentsFunc) { constructor(private readonly _tab: DesignerTab, private _createComponents: CreateComponentsFunc) {
super(); super();
this._componentsContainer = DOM.$('.components-grid'); this._componentsContainer = DOM.$('.components-grid');
const uiComponents = this._createComponents(this._componentsContainer, this._tab.components, component => component.propertyName); const uiComponents = this._createComponents(this._componentsContainer, this._tab.components, DesignerRootObjectPath);
uiComponents.forEach(component => { uiComponents.forEach(component => {
if (component instanceof Table) { if (component instanceof Table) {
this._tables.push(component); this._tables.push(component);

View File

@@ -113,6 +113,7 @@ export interface DesignerDataPropertyInfo {
propertyName: string; propertyName: string;
description?: string; description?: string;
componentType: DesignerComponentTypeName; componentType: DesignerComponentTypeName;
showInPropertiesView?: boolean;
group?: string; group?: string;
componentProperties?: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties; componentProperties?: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties;
} }
@@ -193,15 +194,16 @@ export enum DesignerEditType {
export interface DesignerEdit { export interface DesignerEdit {
type: DesignerEditType; type: DesignerEditType;
property: DesignerEditIdentifier; path: DesignerEditPath;
value?: any; value?: any;
} }
export type DesignerEditIdentifier = string | { parentProperty: string, index: number, property: string }; export type DesignerEditPath = (string | number)[];
export const DesignerRootObjectPath: DesignerEditPath = [];
export interface DesignerEditResult { export interface DesignerEditResult {
isValid: boolean; isValid: boolean;
errors?: { message: string, property?: DesignerEditIdentifier }[]; errors?: { message: string, property?: DesignerEditPath }[];
} }
export interface DesignerTextEditor { export interface DesignerTextEditor {

View File

@@ -10,7 +10,6 @@ import { TableDesignerInput } from 'sql/workbench/browser/editor/tableDesigner/t
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -32,8 +31,7 @@ export class TableDesignerEditor extends EditorPane {
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService, @IWorkbenchThemeService themeService: IWorkbenchThemeService,
@IStorageService storageService: IStorageService, @IStorageService storageService: IStorageService,
@IContextViewService private _contextViewService: IContextViewService, @IInstantiationService private _instantiationService: IInstantiationService,
@IInstantiationService private _instantiationService: IInstantiationService
) { ) {
super(TableDesignerEditor.ID, telemetryService, themeService, storageService); super(TableDesignerEditor.ID, telemetryService, themeService, storageService);
} }
@@ -60,7 +58,7 @@ export class TableDesignerEditor extends EditorPane {
this._saveChangesAction.enabled = false; this._saveChangesAction.enabled = false;
actionbar.push(this._saveChangesAction, { icon: true, label: false }); actionbar.push(this._saveChangesAction, { icon: true, label: false });
this._designer = new Designer(designerContainer, this._instantiationService, this._contextViewService); this._designer = this._instantiationService.createInstance(Designer, designerContainer);
this._register(attachDesignerStyler(this._designer, this.themeService)); this._register(attachDesignerStyler(this._designer, this.themeService));
this._register(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { this._register(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const border = theme.getColor(DesignerPaneSeparator); const border = theme.getColor(DesignerPaneSeparator);

View File

@@ -251,6 +251,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
{ {
componentType: 'table', componentType: 'table',
propertyName: designers.TableProperty.Columns, propertyName: designers.TableProperty.Columns,
showInPropertiesView: false,
componentProperties: <DesignerTableProperties>{ componentProperties: <DesignerTableProperties>{
ariaLabel: localize('tableDesigner.columnsTabTitle', "Columns"), ariaLabel: localize('tableDesigner.columnsTabTitle', "Columns"),
columns: columnsTableProperties, columns: columnsTableProperties,