mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 17:22:59 -05:00
table designer new features (#18682)
* support graph tables * ignore script compare * ability to refresh view after edit * reserve focus after refresh view * primary key and default constraint * bug fixes * vbump sts * comments * update type * fix issue
This commit is contained in:
@@ -516,7 +516,7 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
|
||||
initializeTableDesigner(tableInfo: azdata.designers.TableInfo): Thenable<azdata.designers.TableDesignerInfo> {
|
||||
return self._proxy.$initializeTableDesigner(handle, tableInfo);
|
||||
},
|
||||
processTableEdit(table, edit): Thenable<azdata.designers.DesignerEditResult> {
|
||||
processTableEdit(table, edit): Thenable<azdata.designers.DesignerEditResult<azdata.designers.TableDesignerView>> {
|
||||
return self._proxy.$processTableDesignerEdit(handle, table, edit);
|
||||
},
|
||||
publishChanges(tableInfo: azdata.designers.TableInfo): Thenable<azdata.designers.PublishChangesResult> {
|
||||
|
||||
@@ -897,7 +897,7 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
return this._resolveProvider<azdata.designers.TableDesignerProvider>(handle).initializeTableDesigner(table);
|
||||
}
|
||||
|
||||
public override $processTableDesignerEdit(handle: number, table: azdata.designers.TableInfo, edit: azdata.designers.DesignerEdit): Thenable<azdata.designers.DesignerEditResult> {
|
||||
public override $processTableDesignerEdit(handle: number, table: azdata.designers.TableInfo, edit: azdata.designers.DesignerEdit): Thenable<azdata.designers.DesignerEditResult<azdata.designers.TableDesignerView>> {
|
||||
return this._resolveProvider<azdata.designers.TableDesignerProvider>(handle).processTableEdit(table, edit);
|
||||
}
|
||||
|
||||
|
||||
@@ -541,7 +541,7 @@ export abstract class ExtHostDataProtocolShape {
|
||||
/**
|
||||
* Process the table edit.
|
||||
*/
|
||||
$processTableDesignerEdit(handle: number, table: azdata.designers.TableInfo, edit: azdata.designers.DesignerEdit): Thenable<azdata.designers.DesignerEditResult> { throw ni(); }
|
||||
$processTableDesignerEdit(handle: number, table: azdata.designers.TableInfo, edit: azdata.designers.DesignerEdit): Thenable<azdata.designers.DesignerEditResult<azdata.designers.TableDesignerView>> { throw ni(); }
|
||||
|
||||
/**
|
||||
* Publish the table designer changes.
|
||||
|
||||
@@ -965,7 +965,9 @@ export namespace designers {
|
||||
Script = 'script',
|
||||
ForeignKeys = 'foreignKeys',
|
||||
CheckConstraints = 'checkConstraints',
|
||||
Indexes = 'indexes'
|
||||
Indexes = 'indexes',
|
||||
PrimaryKeyName = 'primaryKeyName',
|
||||
PrimaryKeyColumns = 'primaryKeyColumns'
|
||||
}
|
||||
|
||||
export enum TableColumnProperty {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import {
|
||||
DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerPropertyPath, DesignerViewModel, DesignerDataPropertyInfo,
|
||||
DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties,
|
||||
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState, ScriptProperty, DesignerRootObjectPath
|
||||
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, ScriptProperty, DesignerRootObjectPath, CanBeDeletedProperty, DesignerUIArea
|
||||
}
|
||||
from 'sql/workbench/browser/designer/interfaces';
|
||||
import { IPanelTab, ITabbedPanelStyles, TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||
@@ -42,6 +42,7 @@ import { DesignerPropertyPathValidator } from 'sql/workbench/browser/designer/de
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { layoutDesignerTable, TableHeaderRowHeight, TableRowHeight } from 'sql/workbench/browser/designer/designerTableUtil';
|
||||
import { Dropdown, IDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
|
||||
@@ -62,13 +63,14 @@ export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> |
|
||||
export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], parentPath: DesignerPropertyPath) => DesignerUIComponent[];
|
||||
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
|
||||
|
||||
const TableRowHeight = 25;
|
||||
const TableHeaderRowHeight = 28;
|
||||
interface DesignerTableCellContext {
|
||||
view: DesignerUIArea;
|
||||
path: DesignerPropertyPath;
|
||||
}
|
||||
|
||||
const ScriptTabId = 'scripts';
|
||||
const MessagesTabId = 'messages';
|
||||
|
||||
type DesignerUIArea = 'PropertiesView' | 'ScriptView' | 'TopContentView' | 'TabsView';
|
||||
|
||||
export class Designer extends Disposable implements IThemable {
|
||||
private _loadingSpinner: LoadingSpinner;
|
||||
private _horizontalSplitViewContainer: HTMLElement;
|
||||
@@ -107,11 +109,12 @@ export class Designer extends Disposable implements IThemable {
|
||||
valueGetter: (item, column): string => {
|
||||
return item[column.field].value;
|
||||
},
|
||||
valueSetter: (parentPath: DesignerPropertyPath, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||
valueSetter: (context: DesignerTableCellContext, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Update,
|
||||
path: [...parentPath, row, column.field],
|
||||
value: value
|
||||
path: [...context.path, row, column.field],
|
||||
value: value,
|
||||
source: context.view
|
||||
});
|
||||
},
|
||||
optionsGetter: (item, column): string[] => {
|
||||
@@ -148,7 +151,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
this._scriptTabbedPannel = new TabbedPanel(this._editorContainer);
|
||||
this._messagesView = this._instantiationService.createInstance(DesignerMessagesTabPanelView);
|
||||
this._register(this._messagesView.onMessageSelected((path) => {
|
||||
this.selectProperty(path);
|
||||
if (path && path.length > 0) {
|
||||
this.selectProperty(path);
|
||||
}
|
||||
}));
|
||||
this._scriptEditorView = new DesignerScriptEditorTabPanelView(this._instantiationService);
|
||||
this._scriptTabbedPannel.pushTab({
|
||||
@@ -258,24 +263,12 @@ export class Designer extends Disposable implements IThemable {
|
||||
|
||||
|
||||
public setInput(input: DesignerComponentInput): void {
|
||||
// Save state
|
||||
if (this._input) {
|
||||
this._input.designerUIState = this.getUIState();
|
||||
}
|
||||
|
||||
// Clean up
|
||||
this.saveUIState();
|
||||
if (this._loadingTimeoutHandle) {
|
||||
this.stopLoading();
|
||||
}
|
||||
this._buttons = [];
|
||||
this._componentMap.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
this._contentTabbedPanel.clearTabs();
|
||||
this._propertiesPane.clear();
|
||||
this.clearUI();
|
||||
this._inputDisposable?.dispose();
|
||||
this._groupHeaders = [];
|
||||
|
||||
|
||||
// Initialize with new input
|
||||
this._input = input;
|
||||
this._inputDisposable = new DisposableStore();
|
||||
@@ -307,6 +300,17 @@ export class Designer extends Disposable implements IThemable {
|
||||
this._inputDisposable?.dispose();
|
||||
}
|
||||
|
||||
private clearUI(): void {
|
||||
this._buttons.forEach(button => button.dispose());
|
||||
this._buttons = [];
|
||||
this._componentMap.forEach(item => item.component.dispose());
|
||||
this._componentMap.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
this._contentTabbedPanel.clearTabs();
|
||||
this._propertiesPane.clear();
|
||||
this._groupHeaders = [];
|
||||
}
|
||||
|
||||
private initializeDesigner(): void {
|
||||
const view = this._input.view;
|
||||
if (view.components) {
|
||||
@@ -315,8 +319,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
view.tabs.forEach(tab => {
|
||||
this._contentTabbedPanel.pushTab(this.createTabView(tab));
|
||||
});
|
||||
this.layoutTabbedPanel();
|
||||
this.updateComponentValues();
|
||||
this.layoutTabbedPanel();
|
||||
this.updatePropertiesPane(DesignerRootObjectPath);
|
||||
this.restoreUIState();
|
||||
}
|
||||
@@ -328,7 +332,15 @@ export class Designer extends Disposable implements IThemable {
|
||||
alert(localize('designer.errorCountAlert', "{0} validation errors found.", args.result.errors.length));
|
||||
}
|
||||
try {
|
||||
this.updateComponentValues();
|
||||
if (args.result.refreshView) {
|
||||
this.refresh();
|
||||
if (!args.result.isValid) {
|
||||
this._scriptTabbedPannel.showTab(MessagesTabId);
|
||||
}
|
||||
} else {
|
||||
this.updateComponentValues();
|
||||
this.layoutTabbedPanel();
|
||||
}
|
||||
if (edit.type === DesignerEditType.Add) {
|
||||
// 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.
|
||||
if (edit.path.length === 1) {
|
||||
@@ -353,6 +365,10 @@ export class Designer extends Disposable implements IThemable {
|
||||
this.updatePropertiesPane(this._propertiesPane.objectPath);
|
||||
}
|
||||
}
|
||||
// try to move the focus back to where it was
|
||||
if (args.result.refreshView) {
|
||||
this.selectProperty(args.edit.path, args.edit.source, false);
|
||||
}
|
||||
} catch (err) {
|
||||
this._notificationService.error(err);
|
||||
}
|
||||
@@ -397,8 +413,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
private refresh() {
|
||||
this.updateComponentValues();
|
||||
this.updatePropertiesPane(this._propertiesPane.objectPath);
|
||||
this.saveUIState();
|
||||
this.clearUI();
|
||||
this.initializeDesigner();
|
||||
}
|
||||
|
||||
private layoutTabbedPanel() {
|
||||
@@ -408,21 +425,11 @@ export class Designer extends Disposable implements IThemable {
|
||||
private layoutPropertiesPane() {
|
||||
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));
|
||||
layoutDesignerTable(v.component, this._propertiesPaneContainer.clientWidth);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getTableHeight(rows: number): number {
|
||||
return rows * TableRowHeight + TableHeaderRowHeight;
|
||||
}
|
||||
|
||||
private updatePropertiesPane(objectPath: DesignerPropertyPath): void {
|
||||
let type: string;
|
||||
let components: DesignerDataPropertyInfo[];
|
||||
@@ -485,7 +492,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
this._messagesView.updateMessages(this._input.validationErrors);
|
||||
}
|
||||
|
||||
private selectProperty(path: DesignerPropertyPath): void {
|
||||
private selectProperty(path: DesignerPropertyPath, view?: DesignerUIArea, highlight: boolean = true): void {
|
||||
if (!DesignerPropertyPathValidator.validate(path, this._input.viewModel)) {
|
||||
return;
|
||||
}
|
||||
@@ -505,7 +512,10 @@ export class Designer extends Disposable implements IThemable {
|
||||
if (tab) {
|
||||
for (const component of tab.components) {
|
||||
if (path[0] === component.propertyName) {
|
||||
this._contentTabbedPanel.showTab(tab.title);
|
||||
// if we are editing the top level property and the view is properties view, then we don't have to switch to the tab.
|
||||
if (path.length !== 1 || view !== 'PropertiesView') {
|
||||
this._contentTabbedPanel.showTab(tab.title);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -520,7 +530,12 @@ export class Designer extends Disposable implements IThemable {
|
||||
if (found) {
|
||||
const propertyInfo = this._componentMap.get(<string>path[0]);
|
||||
if (propertyInfo.defintion.componentType !== 'table') {
|
||||
propertyInfo.component.focus();
|
||||
if (view === 'PropertiesView') {
|
||||
this.updatePropertiesPane(DesignerRootObjectPath);
|
||||
this._propertiesPane.selectProperty(path);
|
||||
} else {
|
||||
propertyInfo.component.focus();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
const tableComponent = <Table<Slick.SlickData>>propertyInfo.component;
|
||||
@@ -533,7 +548,9 @@ export class Designer extends Disposable implements IThemable {
|
||||
this._propertiesPane.selectProperty(relativePath);
|
||||
}
|
||||
}
|
||||
this.highlightActiveElement();
|
||||
if (highlight) {
|
||||
this.highlightActiveElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,7 +720,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
});
|
||||
input.onLoseFocus((args) => {
|
||||
if (args.hasChanged) {
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: args.value });
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: args.value, source: view });
|
||||
}
|
||||
});
|
||||
input.onInputFocus(() => {
|
||||
@@ -727,7 +744,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
dropdown = new Dropdown(dropdownContainer, this._contextViewProvider, { values: dropdownProperties.values as string[] || [] });
|
||||
dropdown.ariaLabel = componentDefinition.componentProperties?.title;
|
||||
dropdown.onValueChange((value) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: value });
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: value, source: view });
|
||||
});
|
||||
dropdown.onFocus(() => {
|
||||
if (view === 'PropertiesView') {
|
||||
@@ -742,7 +759,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
dropdown.render(dropdownContainer);
|
||||
dropdown.selectElem.style.height = '25px';
|
||||
dropdown.onDidSelect((e) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: e.selected });
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: e.selected, source: view });
|
||||
});
|
||||
dropdown.onDidFocus(() => {
|
||||
if (view === 'PropertiesView') {
|
||||
@@ -760,7 +777,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
const checkboxProperties = componentDefinition.componentProperties as CheckBoxProperties;
|
||||
const checkbox = new Checkbox(checkboxContainer, { label: '', ariaLabel: checkboxProperties.title });
|
||||
checkbox.onChange((newValue) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: newValue });
|
||||
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: newValue, source: view });
|
||||
});
|
||||
checkbox.onFocus(() => {
|
||||
if (view === 'PropertiesView') {
|
||||
@@ -778,7 +795,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
|
||||
if (tableProperties.canAddRows) {
|
||||
const buttonContainer = container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('.add-row-button-container'));
|
||||
const addNewText = localize('designer.newRowText', "Add New");
|
||||
const addNewText = tableProperties.labelForAddNewButton ?? localize('designer.newRowText', "Add New");
|
||||
const addRowButton = new Button(buttonContainer, {
|
||||
title: addNewText,
|
||||
secondary: true
|
||||
@@ -786,7 +803,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
addRowButton.onDidClick(() => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Add,
|
||||
path: propertyPath
|
||||
path: propertyPath,
|
||||
source: view
|
||||
});
|
||||
});
|
||||
this.styleComponent(addRowButton);
|
||||
@@ -829,7 +847,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Update,
|
||||
path: [...propertyPath, e.row, propertyDefinition.propertyName],
|
||||
value: e.value
|
||||
value: e.value,
|
||||
source: view
|
||||
});
|
||||
});
|
||||
return checkboxColumn.definition;
|
||||
@@ -838,7 +857,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
return {
|
||||
name: dropdownProperties.title,
|
||||
field: propertyDefinition.propertyName,
|
||||
editor: this._tableCellEditorFactory.getDropdownEditorClass(propertyPath, dropdownProperties.values as string[], dropdownProperties.isEditable),
|
||||
editor: this._tableCellEditorFactory.getDropdownEditorClass({ view: view, path: propertyPath }, dropdownProperties.values as string[], dropdownProperties.isEditable),
|
||||
width: dropdownProperties.width as number
|
||||
};
|
||||
default:
|
||||
@@ -846,7 +865,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
return {
|
||||
name: inputProperties.title,
|
||||
field: propertyDefinition.propertyName,
|
||||
editor: this._tableCellEditorFactory.getTextEditorClass(propertyPath, inputProperties.inputType),
|
||||
editor: this._tableCellEditorFactory.getTextEditorClass({ view: view, path: propertyPath }, inputProperties.inputType),
|
||||
width: inputProperties.width as number
|
||||
};
|
||||
}
|
||||
@@ -858,7 +877,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
title: localize('designer.removeRowText', "Remove"),
|
||||
width: 20,
|
||||
resizable: false,
|
||||
isFontIcon: true
|
||||
isFontIcon: true,
|
||||
enabledField: CanBeDeletedProperty
|
||||
});
|
||||
deleteRowColumn.onClick(async (e) => {
|
||||
if (tableProperties.showRemoveRowConfirmation) {
|
||||
@@ -873,7 +893,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Remove,
|
||||
path: [...propertyPath, e.row]
|
||||
path: [...propertyPath, e.row],
|
||||
source: view
|
||||
});
|
||||
});
|
||||
table.registerPlugin(deleteRowColumn);
|
||||
@@ -887,7 +908,11 @@ export class Designer extends Disposable implements IThemable {
|
||||
table.grid.onActiveCellChanged.subscribe((e, data) => {
|
||||
if (view === 'TabsView' || view === 'TopContentView') {
|
||||
if (data.row !== undefined) {
|
||||
this.updatePropertiesPane([...propertyPath, data.row]);
|
||||
if (tableProperties.showItemDetailInPropertiesView === false) {
|
||||
this.updatePropertiesPane(DesignerRootObjectPath);
|
||||
} else {
|
||||
this.updatePropertiesPane([...propertyPath, data.row]);
|
||||
}
|
||||
} else {
|
||||
this.updatePropertiesPane(DesignerRootObjectPath);
|
||||
}
|
||||
@@ -934,11 +959,13 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
}
|
||||
|
||||
private getUIState(): DesignerUIState {
|
||||
return {
|
||||
activeContentTabId: this._contentTabbedPanel.activeTabId,
|
||||
activeScriptTabId: this._scriptTabbedPannel.activeTabId
|
||||
};
|
||||
private saveUIState(): void {
|
||||
if (this._input) {
|
||||
this._input.designerUIState = {
|
||||
activeContentTabId: this._contentTabbedPanel.activeTabId,
|
||||
activeScriptTabId: this._scriptTabbedPannel.activeTabId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private restoreUIState(): void {
|
||||
|
||||
@@ -56,7 +56,7 @@ export class DesignerPropertyPathValidator {
|
||||
if (!tableData.data || tableData.data.length - 1 < objectIndex) {
|
||||
return false;
|
||||
}
|
||||
currentObject = tableData.data[objectIndex];
|
||||
currentObject = tableData.data[objectIndex] as DesignerViewModel;
|
||||
index++;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -9,17 +9,16 @@ import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { CreateComponentsFunc } from 'sql/workbench/browser/designer/designer';
|
||||
|
||||
const ButtonHeight = 30;
|
||||
const HorizontalPadding = 10;
|
||||
const VerticalPadding = 20;
|
||||
import { layoutDesignerTable } from 'sql/workbench/browser/designer/designerTableUtil';
|
||||
|
||||
export class DesignerTabPanelView extends Disposable implements IPanelView {
|
||||
private _viewContainer: HTMLElement;
|
||||
private _componentsContainer: HTMLElement;
|
||||
private _tables: Table<Slick.SlickData>[] = [];
|
||||
constructor(private readonly _tab: DesignerTab, private _createComponents: CreateComponentsFunc) {
|
||||
super();
|
||||
this._componentsContainer = DOM.$('.components-grid');
|
||||
this._viewContainer = DOM.$('.designer-tab-view');
|
||||
this._componentsContainer = this._viewContainer.appendChild(DOM.$('.components-grid'));
|
||||
const uiComponents = this._createComponents(this._componentsContainer, this._tab.components, DesignerRootObjectPath);
|
||||
uiComponents.forEach(component => {
|
||||
if (component instanceof Table) {
|
||||
@@ -29,12 +28,12 @@ export class DesignerTabPanelView extends Disposable implements IPanelView {
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
container.appendChild(this._componentsContainer);
|
||||
container.appendChild(this._viewContainer);
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this._tables.forEach(table => {
|
||||
table.layout(new DOM.Dimension(dimension.width - HorizontalPadding, dimension.height - VerticalPadding - ButtonHeight));
|
||||
layoutDesignerTable(table, dimension.width);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
37
src/sql/workbench/browser/designer/designerTableUtil.ts
Normal file
37
src/sql/workbench/browser/designer/designerTableUtil.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
export const TableRowHeight = 25;
|
||||
export const TableHeaderRowHeight = 28;
|
||||
const minHeight = getTableHeight(2);
|
||||
|
||||
/**
|
||||
* Layout the table, the height will be determined by the number of rows in it.
|
||||
* @param table the table.
|
||||
* @param width width of the table
|
||||
*/
|
||||
export function layoutDesignerTable(table: Table<Slick.SlickData>, width: number): void {
|
||||
let activeCell: Slick.Cell = undefined;
|
||||
if (table.container.contains(document.activeElement)) {
|
||||
// Note down the current active cell if the focus is currently in the table
|
||||
// After the table layout operation is done, the focus will be restored.
|
||||
activeCell = deepClone(table.activeCell);
|
||||
}
|
||||
const rows = table.getData().getLength();
|
||||
const actualHeight = getTableHeight(rows);
|
||||
const height = Math.max(minHeight, actualHeight);
|
||||
table.layout(new DOM.Dimension(width - 20 /* Padding and scroll bar */, height));
|
||||
if (activeCell && rows > activeCell.row) {
|
||||
table.setActiveCell(activeCell.row, activeCell.cell);
|
||||
}
|
||||
}
|
||||
|
||||
function getTableHeight(rows: number): number {
|
||||
return rows * TableRowHeight + TableHeaderRowHeight;
|
||||
}
|
||||
@@ -104,6 +104,7 @@ export interface DesignerState {
|
||||
|
||||
export const NameProperty = 'name';
|
||||
export const ScriptProperty = 'script';
|
||||
export const CanBeDeletedProperty = 'canBeDeleted';
|
||||
|
||||
export interface DesignerView {
|
||||
components?: DesignerDataPropertyInfo[]
|
||||
@@ -194,10 +195,19 @@ export interface DesignerTableProperties extends ComponentProperties {
|
||||
* The confirmation message to be displayed when user removes a row.
|
||||
*/
|
||||
removeRowConfirmationMessage?: string;
|
||||
/**
|
||||
* Whether to show the item detail in properties view. The default value is true.
|
||||
*/
|
||||
showItemDetailInPropertiesView?: boolean;
|
||||
/**
|
||||
* The label of the add new button. The default value is 'Add New'.
|
||||
*/
|
||||
labelForAddNewButton?: string;
|
||||
}
|
||||
|
||||
export interface DesignerTableComponentRowData {
|
||||
[key: string]: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties;
|
||||
[key: string]: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties | boolean;
|
||||
canBeDeleted?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,8 +221,11 @@ export interface DesignerEdit {
|
||||
type: DesignerEditType;
|
||||
path: DesignerPropertyPath;
|
||||
value?: any;
|
||||
source: DesignerUIArea;
|
||||
}
|
||||
|
||||
export type DesignerUIArea = 'PropertiesView' | 'ScriptView' | 'TopContentView' | 'TabsView';
|
||||
|
||||
export type DesignerPropertyPath = (string | number)[];
|
||||
export const DesignerRootObjectPath: DesignerPropertyPath = [];
|
||||
|
||||
@@ -220,6 +233,7 @@ export type DesignerValidationError = { message: string, propertyPath?: Designer
|
||||
|
||||
export interface DesignerEditResult {
|
||||
isValid: boolean;
|
||||
refreshView?: boolean;
|
||||
errors?: DesignerValidationError[];
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,12 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.designer-component .designer-tab-view {
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.designer-component .component-label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { DesignerViewModel, DesignerEdit, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerEditProcessedEventArgs, DesignerAction, DesignerStateChangedEventArgs, DesignerPropertyPath, DesignerValidationError } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { DesignerViewModel, DesignerEdit, DesignerComponentInput, DesignerView, DesignerTab, DesignerDataPropertyInfo, DropDownProperties, DesignerTableProperties, DesignerEditProcessedEventArgs, DesignerAction, DesignerStateChangedEventArgs, DesignerPropertyPath, DesignerValidationError, ScriptProperty } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface';
|
||||
import { localize } from 'vs/nls';
|
||||
import { designers } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
@@ -90,14 +90,18 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
this._provider.processTableEdit(this.tableInfo, edit).then(
|
||||
result => {
|
||||
this._viewModel = result.viewModel;
|
||||
if (result.view) {
|
||||
this.setDesignerView(result.view);
|
||||
}
|
||||
this._validationErrors = result.errors;
|
||||
this.updateState(result.isValid, !equals(this._viewModel, this._originalViewModel), undefined);
|
||||
this.updateState(result.isValid, this.isDirty(), undefined);
|
||||
|
||||
this._onEditProcessed.fire({
|
||||
edit: edit,
|
||||
result: {
|
||||
isValid: result.isValid,
|
||||
errors: result.errors
|
||||
errors: result.errors,
|
||||
refreshView: !!result.view
|
||||
}
|
||||
});
|
||||
editAction.withAdditionalMeasurements({
|
||||
@@ -152,6 +156,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
const result = await this._provider.publishChanges(this.tableInfo);
|
||||
this._viewModel = result.viewModel;
|
||||
this._originalViewModel = result.viewModel;
|
||||
this.setDesignerView(result.view);
|
||||
saveNotificationHandle.updateMessage(localize('tableDesigner.publishChangeSuccess', "The changes have been successfully published."));
|
||||
this.tableInfo = result.newTableInfo;
|
||||
this.updateState(true, false);
|
||||
@@ -185,7 +190,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
return;
|
||||
}
|
||||
const dialog = this._instantiationService.createInstance(TableDesignerPublishDialog);
|
||||
const result = await dialog.open(report);
|
||||
const result = await dialog.open(<any>report.report);
|
||||
if (result === TableDesignerPublishDialogResult.GenerateScript) {
|
||||
await this.generateScript();
|
||||
} else if (result === TableDesignerPublishDialogResult.UpdateDatabase) {
|
||||
@@ -239,31 +244,35 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
this.updateState(true, this.tableInfo.isNewTable);
|
||||
this._viewModel = designerInfo.viewModel;
|
||||
this._originalViewModel = this.tableInfo.isNewTable ? undefined : deepClone(this._viewModel);
|
||||
this.setDefaultData();
|
||||
this.setDesignerView(designerInfo.view);
|
||||
}
|
||||
|
||||
private setDesignerView(tableDesignerView: azdata.designers.TableDesignerView) {
|
||||
const tabs = [];
|
||||
|
||||
if (designerInfo.view.columnTableOptions?.showTable) {
|
||||
tabs.push(this.getColumnsTab(designerInfo.view.columnTableOptions, designerInfo.columnTypes));
|
||||
if (tableDesignerView.columnTableOptions?.showTable) {
|
||||
tabs.push(this.getColumnsTab(tableDesignerView.columnTableOptions));
|
||||
}
|
||||
|
||||
if (designerInfo.view.foreignKeyTableOptions?.showTable) {
|
||||
tabs.push(this.getForeignKeysTab(designerInfo.view.foreignKeyTableOptions));
|
||||
tabs.push(this.getPrimaryKeyTab(tableDesignerView));
|
||||
|
||||
if (tableDesignerView.foreignKeyTableOptions?.showTable) {
|
||||
tabs.push(this.getForeignKeysTab(tableDesignerView.foreignKeyTableOptions, tableDesignerView.foreignKeyColumnMappingTableOptions));
|
||||
}
|
||||
|
||||
if (designerInfo.view.checkConstraintTableOptions?.showTable) {
|
||||
tabs.push(this.getCheckConstraintsTab(designerInfo.view.checkConstraintTableOptions));
|
||||
if (tableDesignerView.checkConstraintTableOptions?.showTable) {
|
||||
tabs.push(this.getCheckConstraintsTab(tableDesignerView.checkConstraintTableOptions));
|
||||
}
|
||||
|
||||
if (designerInfo.view.indexTableOptions?.showTable) {
|
||||
tabs.push(this.getIndexesTab(designerInfo.view.indexTableOptions, designerInfo.view.indexColumnSpecificationTableOptions));
|
||||
if (tableDesignerView.indexTableOptions?.showTable) {
|
||||
tabs.push(this.getIndexesTab(tableDesignerView.indexTableOptions, tableDesignerView.indexColumnSpecificationTableOptions));
|
||||
}
|
||||
|
||||
if (designerInfo.view.additionalTabs) {
|
||||
tabs.push(...designerInfo.view.additionalTabs);
|
||||
if (tableDesignerView.additionalTabs) {
|
||||
tabs.push(...tableDesignerView.additionalTabs);
|
||||
}
|
||||
|
||||
tabs.push(this.getGeneralTab(designerInfo));
|
||||
tabs.push(this.getGeneralTab(tableDesignerView));
|
||||
|
||||
this._view = {
|
||||
components: [{
|
||||
@@ -279,7 +288,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
};
|
||||
}
|
||||
|
||||
private getGeneralTab(designerInfo: azdata.designers.TableDesignerInfo): DesignerTab {
|
||||
private getGeneralTab(tableDesignerView: azdata.designers.TableDesignerView): DesignerTab {
|
||||
const generalTabComponents: DesignerDataPropertyInfo[] = [
|
||||
{
|
||||
componentType: 'dropdown',
|
||||
@@ -287,7 +296,6 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
description: localize('designer.table.description.schema', "The schema that contains the table."),
|
||||
componentProperties: <DropDownProperties>{
|
||||
title: localize('tableDesigner.schemaTitle', "Schema"),
|
||||
values: designerInfo.schemas
|
||||
}
|
||||
}, {
|
||||
componentType: 'input',
|
||||
@@ -299,8 +307,8 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
}
|
||||
];
|
||||
|
||||
if (designerInfo.view.additionalTableProperties) {
|
||||
generalTabComponents.push(...designerInfo.view.additionalTableProperties);
|
||||
if (tableDesignerView.additionalTableProperties) {
|
||||
generalTabComponents.push(...tableDesignerView.additionalTableProperties);
|
||||
}
|
||||
|
||||
return <DesignerTab>{
|
||||
@@ -309,7 +317,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
};
|
||||
}
|
||||
|
||||
private getColumnsTab(options: azdata.designers.TableDesignerBuiltInTableViewOptions, columnTypes: string[]): DesignerTab {
|
||||
private getColumnsTab(options: azdata.designers.TableDesignerBuiltInTableViewOptions): DesignerTab {
|
||||
|
||||
const columnProperties: DesignerDataPropertyInfo[] = [
|
||||
{
|
||||
@@ -326,8 +334,7 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
description: localize('designer.column.description.dataType', "Displays the data type name for the column"),
|
||||
componentProperties: {
|
||||
title: localize('tableDesigner.columnTypeTitle', "Type"),
|
||||
width: 100,
|
||||
values: columnTypes
|
||||
width: 100
|
||||
}
|
||||
}, {
|
||||
componentType: 'input',
|
||||
@@ -404,14 +411,15 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
canAddRows: options.canAddRows,
|
||||
canRemoveRows: options.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
||||
labelForAddNewButton: options.labelForAddNewButton ?? localize('tableDesigner.addNewColumn', "New Column")
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private getForeignKeysTab(options: azdata.designers.TableDesignerBuiltInTableViewOptions): DesignerTab {
|
||||
private getForeignKeysTab(options: azdata.designers.TableDesignerBuiltInTableViewOptions, columnMappingTableOptions: azdata.designers.TableDesignerBuiltInTableViewOptions): DesignerTab {
|
||||
|
||||
const foreignKeyColumnMappingProperties: DesignerDataPropertyInfo[] = [
|
||||
{
|
||||
@@ -476,13 +484,12 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
group: localize('tableDesigner.foreignKeyColumns', "Columns"),
|
||||
componentProperties: <DesignerTableProperties>{
|
||||
ariaLabel: localize('tableDesigner.foreignKeyColumns', "Columns"),
|
||||
columns: [designers.ForeignKeyColumnMappingProperty.Column, designers.ForeignKeyColumnMappingProperty.ForeignColumn],
|
||||
itemProperties: foreignKeyColumnMappingProperties,
|
||||
objectTypeDisplayName: '',
|
||||
canAddRows: options.canAddRows,
|
||||
canRemoveRows: options.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation
|
||||
columns: this.getTableDisplayProperties(columnMappingTableOptions, [designers.ForeignKeyColumnMappingProperty.Column, designers.ForeignKeyColumnMappingProperty.ForeignColumn]),
|
||||
itemProperties: this.addAdditionalTableProperties(columnMappingTableOptions, foreignKeyColumnMappingProperties),
|
||||
canAddRows: columnMappingTableOptions.canAddRows,
|
||||
canRemoveRows: columnMappingTableOptions.canRemoveRows,
|
||||
removeRowConfirmationMessage: columnMappingTableOptions.removeRowConfirmationMessage,
|
||||
labelForAddNewButton: columnMappingTableOptions.labelForAddNewButton ?? localize('tableDesigner.addNewColumnMapping', "New Column Mapping")
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -502,13 +509,70 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
canAddRows: options.canAddRows,
|
||||
canRemoveRows: options.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
||||
labelForAddNewButton: options.labelForAddNewButton ?? localize('tableDesigner.addForeignKey', "New Foreign Key")
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private getPrimaryKeyTab(view: azdata.designers.TableDesignerView): DesignerTab {
|
||||
const options = view.primaryKeyColumnSpecificationTableOptions;
|
||||
const columnSpecProperties: DesignerDataPropertyInfo[] = [
|
||||
{
|
||||
componentType: 'dropdown',
|
||||
propertyName: designers.TableIndexColumnSpecificationProperty.Column,
|
||||
description: localize('designer.index.column.description.name', "The name of the column."),
|
||||
componentProperties: {
|
||||
title: localize('tableDesigner.index.column.name', "Column"),
|
||||
width: 100
|
||||
}
|
||||
}];
|
||||
|
||||
const tabComponents = [];
|
||||
tabComponents.push(
|
||||
{
|
||||
componentType: 'input',
|
||||
propertyName: designers.TableProperty.PrimaryKeyName,
|
||||
showInPropertiesView: false,
|
||||
description: localize('designer.table.primaryKeyName.description', "Name of the primary key."),
|
||||
componentProperties: {
|
||||
title: localize('tableDesigner.primaryKeyNameTitle', "Name")
|
||||
}
|
||||
});
|
||||
if (view.additionalPrimaryKeyProperties) {
|
||||
view.additionalPrimaryKeyProperties.forEach(component => {
|
||||
component.showInPropertiesView = false;
|
||||
tabComponents.push(component);
|
||||
});
|
||||
}
|
||||
tabComponents.push({
|
||||
componentType: 'table',
|
||||
propertyName: designers.TableProperty.PrimaryKeyColumns,
|
||||
showInPropertiesView: false,
|
||||
description: localize('designer.table.primaryKeyColumns.description', "Columns in the primary key."),
|
||||
componentProperties: <DesignerTableProperties>{
|
||||
title: localize('tableDesigner.primaryKeyColumnsTitle', "Primary Key Columns"),
|
||||
ariaLabel: localize('tableDesigner.primaryKeyColumnsTitle', "Primary Key Columns"),
|
||||
columns: this.getTableDisplayProperties(options, [designers.TableIndexColumnSpecificationProperty.Column]),
|
||||
itemProperties: this.addAdditionalTableProperties(options, columnSpecProperties),
|
||||
objectTypeDisplayName: '',
|
||||
canAddRows: options.canAddRows,
|
||||
canRemoveRows: options.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
||||
showItemDetailInPropertiesView: false,
|
||||
labelForAddNewButton: options.labelForAddNewButton ?? localize('tableDesigner.addNewColumnToPrimaryKey', "Add Column")
|
||||
}
|
||||
});
|
||||
|
||||
return <DesignerTab>{
|
||||
title: localize('tableDesigner.PrimaryKeyTabTitle', "Primary Key"),
|
||||
components: tabComponents
|
||||
};
|
||||
}
|
||||
|
||||
private getCheckConstraintsTab(options: azdata.designers.TableDesignerBuiltInTableViewOptions): DesignerTab {
|
||||
const checkConstraintProperties: DesignerDataPropertyInfo[] = [
|
||||
{
|
||||
@@ -545,7 +609,8 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
canAddRows: options.canAddRows,
|
||||
canRemoveRows: options.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
||||
labelForAddNewButton: options.labelForAddNewButton ?? localize('tableDesigner.addNewCheckConstraint', "New Check Constraint")
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -584,8 +649,9 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
objectTypeDisplayName: '',
|
||||
canAddRows: columnSpecTableOptions.canAddRows,
|
||||
canRemoveRows: columnSpecTableOptions.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation
|
||||
removeRowConfirmationMessage: columnSpecTableOptions.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: columnSpecTableOptions.showRemoveRowConfirmation,
|
||||
labelForAddNewButton: columnSpecTableOptions.labelForAddNewButton ?? localize('tableDesigner.addNewColumnToIndex', "Add Column")
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -605,7 +671,8 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
canAddRows: options.canAddRows,
|
||||
canRemoveRows: options.canRemoveRows,
|
||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation
|
||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
||||
labelForAddNewButton: options.labelForAddNewButton ?? localize('tableDesigner.addNewIndex', "New Index")
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -623,19 +690,6 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
return properties;
|
||||
}
|
||||
|
||||
private setDefaultData(): void {
|
||||
const properties = Object.keys(this._viewModel);
|
||||
this.setDefaultInputData(properties, designers.TableProperty.Name);
|
||||
this.setDefaultInputData(properties, designers.TableProperty.Schema);
|
||||
this.setDefaultInputData(properties, designers.TableProperty.Description);
|
||||
}
|
||||
|
||||
private setDefaultInputData(allProperties: string[], property: string): void {
|
||||
if (allProperties.indexOf(property) === -1) {
|
||||
this._viewModel[property] = {};
|
||||
}
|
||||
}
|
||||
|
||||
private createTelemetryInfo(): ITelemetryEventProperties {
|
||||
let telemetryInfo = {
|
||||
provider: this._provider.providerId,
|
||||
@@ -645,6 +699,21 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
||||
return telemetryInfo;
|
||||
}
|
||||
|
||||
private isDirty(): boolean {
|
||||
const copyOfViewModel = deepClone(this._viewModel);
|
||||
const copyOfOriginalViewModel = deepClone(this._originalViewModel);
|
||||
// The generated script might be slightly different even though the models are the same
|
||||
// espeically the order of the description property statements.
|
||||
// we should take the script out for comparison.
|
||||
if (copyOfViewModel) {
|
||||
delete copyOfViewModel[ScriptProperty];
|
||||
}
|
||||
if (copyOfOriginalViewModel) {
|
||||
delete copyOfOriginalViewModel[ScriptProperty];
|
||||
}
|
||||
return !equals(copyOfViewModel, copyOfOriginalViewModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 'Add' scenario
|
||||
a. ['propertyName1']. Example: add a column to the columns property: ['columns'].
|
||||
|
||||
Reference in New Issue
Block a user