mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-19 09:35:36 -05:00
move the designer component to workbench layer (#17620)
This commit is contained in:
757
src/sql/workbench/browser/designer/designer.ts
Normal file
757
src/sql/workbench/browser/designer/designer.ts
Normal file
@@ -0,0 +1,757 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import {
|
||||
DesignerComponentInput, DesignerEditType, DesignerTab, DesignerEdit, DesignerEditIdentifier, DesignerViewModel, DesignerDataPropertyInfo,
|
||||
DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties, DesignerComponentTypeName,
|
||||
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, DesignerUIState, DesignerTextEditor, ScriptProperty
|
||||
}
|
||||
from 'sql/workbench/browser/designer/interfaces';
|
||||
import { IPanelTab, ITabbedPanelStyles, TabbedPanel } from 'sql/base/browser/ui/panel/panel';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import 'vs/css!./media/designer';
|
||||
import { ITableStyles } from 'sql/base/browser/ui/table/interfaces';
|
||||
import { IThemable } from 'vs/base/common/styler';
|
||||
import { Checkbox, ICheckboxStyles } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { ISelectBoxStyles, SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TableCellEditorFactory } from 'sql/base/browser/ui/table/tableCellEditorFactory';
|
||||
import { CheckBoxColumn } from 'sql/base/browser/ui/table/plugins/checkboxColumn.plugin';
|
||||
import { DesignerTabPanelView } from 'sql/workbench/browser/designer/designerTabPanelView';
|
||||
import { DesignerPropertiesPane, PropertiesPaneObjectContext } from 'sql/workbench/browser/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';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { DesignerScriptEditor } from 'sql/workbench/browser/designer/designerScriptEditor';
|
||||
|
||||
export interface IDesignerStyle {
|
||||
tabbedPanelStyles?: ITabbedPanelStyles;
|
||||
inputBoxStyles?: IInputBoxStyles;
|
||||
tableStyles?: ITableStyles;
|
||||
selectBoxStyles?: ISelectBoxStyles;
|
||||
checkboxStyles?: ICheckboxStyles;
|
||||
buttonStyles?: IButtonStyles;
|
||||
paneSeparator?: Color;
|
||||
groupHeaderBackground?: Color;
|
||||
}
|
||||
|
||||
export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> | SelectBox;
|
||||
|
||||
export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], editIdentifierGetter: (property: DesignerDataPropertyInfo) => DesignerEditIdentifier) => DesignerUIComponent[];
|
||||
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
|
||||
|
||||
export class Designer extends Disposable implements IThemable {
|
||||
private _loadingSpinner: LoadingSpinner;
|
||||
private _horizontalSplitViewContainer: HTMLElement;
|
||||
private _verticalSplitViewContainer: HTMLElement;
|
||||
private _tabbedPanelContainer: HTMLElement;
|
||||
private _editorContainer: HTMLElement;
|
||||
private _horizontalSplitView: SplitView;
|
||||
private _verticalSplitView: SplitView;
|
||||
private _tabbedPanel: TabbedPanel;
|
||||
private _contentContainer: HTMLElement;
|
||||
private _topContentContainer: HTMLElement;
|
||||
private _propertiesPaneContainer: HTMLElement;
|
||||
private _styles: IDesignerStyle = {};
|
||||
private _supressEditProcessing: boolean = false;
|
||||
private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>();
|
||||
private _input: DesignerComponentInput;
|
||||
private _tableCellEditorFactory: TableCellEditorFactory;
|
||||
private _propertiesPane: DesignerPropertiesPane;
|
||||
private _buttons: Button[] = [];
|
||||
private _inputDisposable: DisposableStore;
|
||||
private _loadingTimeoutHandle: any;
|
||||
private _groupHeaders: HTMLElement[] = [];
|
||||
private _textEditor: DesignerTextEditor;
|
||||
|
||||
constructor(private readonly _container: HTMLElement,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IContextViewService private readonly _contextViewProvider: IContextViewService) {
|
||||
super();
|
||||
this._tableCellEditorFactory = new TableCellEditorFactory(
|
||||
{
|
||||
valueGetter: (item, column): string => {
|
||||
return item[column.field].value;
|
||||
},
|
||||
valueSetter: (context: string, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Update,
|
||||
property: {
|
||||
parentProperty: context,
|
||||
index: row,
|
||||
property: column.field
|
||||
},
|
||||
value: value
|
||||
});
|
||||
},
|
||||
optionsGetter: (item, column): string[] => {
|
||||
return item[column.field].options;
|
||||
},
|
||||
editorStyler: (component) => {
|
||||
this.styleComponent(component);
|
||||
}
|
||||
}, this._contextViewProvider
|
||||
);
|
||||
this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true });
|
||||
this._verticalSplitViewContainer = DOM.$('.designer-component');
|
||||
this._horizontalSplitViewContainer = DOM.$('.container');
|
||||
this._contentContainer = DOM.$('.content-container');
|
||||
this._topContentContainer = DOM.$('.top-content-container.components-grid');
|
||||
this._tabbedPanelContainer = DOM.$('.tabbed-panel-container');
|
||||
this._editorContainer = DOM.$('.editor-container');
|
||||
this._propertiesPaneContainer = DOM.$('.properties-container');
|
||||
this._verticalSplitView = new SplitView(this._verticalSplitViewContainer, { orientation: Orientation.VERTICAL });
|
||||
this._horizontalSplitView = new SplitView(this._horizontalSplitViewContainer, { orientation: Orientation.HORIZONTAL });
|
||||
this._tabbedPanel = new TabbedPanel(this._tabbedPanelContainer);
|
||||
this._container.appendChild(this._verticalSplitViewContainer);
|
||||
this._contentContainer.appendChild(this._topContentContainer);
|
||||
this._contentContainer.appendChild(this._tabbedPanelContainer);
|
||||
this._verticalSplitView.addView({
|
||||
element: this._horizontalSplitViewContainer,
|
||||
layout: size => {
|
||||
this.layoutTabbedPanel();
|
||||
},
|
||||
minimumSize: 200,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
this._textEditor = this._instantiationService.createInstance(DesignerScriptEditor, this._editorContainer);
|
||||
this._verticalSplitView.addView({
|
||||
element: this._editorContainer,
|
||||
layout: size => {
|
||||
this._textEditor.layout(new DOM.Dimension(this._editorContainer.clientWidth, size));
|
||||
},
|
||||
minimumSize: 100,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this._horizontalSplitView.addView({
|
||||
element: this._contentContainer,
|
||||
layout: size => {
|
||||
this.layoutTabbedPanel();
|
||||
},
|
||||
minimumSize: 400,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this._horizontalSplitView.addView({
|
||||
element: this._propertiesPaneContainer,
|
||||
layout: size => { },
|
||||
minimumSize: 200,
|
||||
maximumSize: Number.POSITIVE_INFINITY,
|
||||
onDidChange: Event.None
|
||||
}, Sizing.Distribute);
|
||||
|
||||
this._propertiesPane = new DesignerPropertiesPane(this._propertiesPaneContainer, (container, components, identifierGetter) => {
|
||||
return this.createComponents(container, components, this._propertiesPane.componentMap, this._propertiesPane.groupHeaders, identifierGetter, false, true);
|
||||
}, (definition, component, viewModel) => {
|
||||
this.setComponentValue(definition, component, viewModel);
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
component.style(this._styles.checkboxStyles);
|
||||
} else if (component instanceof TabbedPanel) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private styleGroupHeader(header: HTMLElement): void {
|
||||
if (this._styles.groupHeaderBackground) {
|
||||
header.style.backgroundColor = this._styles.groupHeaderBackground.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public style(styles: IDesignerStyle): void {
|
||||
this._styles = styles;
|
||||
this._componentMap.forEach((value, key, map) => {
|
||||
if (value.component.style) {
|
||||
this.styleComponent(value.component);
|
||||
}
|
||||
});
|
||||
this._propertiesPane.componentMap.forEach((value) => {
|
||||
this.styleComponent(value.component);
|
||||
});
|
||||
this._verticalSplitView.style({
|
||||
separatorBorder: styles.paneSeparator
|
||||
});
|
||||
|
||||
this._horizontalSplitView.style({
|
||||
separatorBorder: styles.paneSeparator
|
||||
});
|
||||
|
||||
this._buttons.forEach((button) => {
|
||||
this.styleComponent(button);
|
||||
});
|
||||
|
||||
this._groupHeaders.forEach((header) => {
|
||||
this.styleGroupHeader(header);
|
||||
});
|
||||
|
||||
this._propertiesPane.groupHeaders.forEach((header) => {
|
||||
this.styleGroupHeader(header);
|
||||
});
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension) {
|
||||
this._verticalSplitView.layout(dimension.height);
|
||||
this._horizontalSplitView.layout(dimension.width);
|
||||
}
|
||||
|
||||
|
||||
public setInput(input: DesignerComponentInput): void {
|
||||
// Save state
|
||||
if (this._input) {
|
||||
this._input.designerUIState = this.getUIState();
|
||||
}
|
||||
|
||||
// Clean up
|
||||
if (this._loadingTimeoutHandle) {
|
||||
this.stopLoading();
|
||||
}
|
||||
this._buttons = [];
|
||||
this._componentMap.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
this._tabbedPanel.clearTabs();
|
||||
this._propertiesPane.clear();
|
||||
this._inputDisposable?.dispose();
|
||||
this._groupHeaders = [];
|
||||
|
||||
|
||||
// Initialize with new input
|
||||
this._input = input;
|
||||
this._inputDisposable = new DisposableStore();
|
||||
this._inputDisposable.add(this._input.onInitialized(() => {
|
||||
this.initializeDesigner();
|
||||
}));
|
||||
this._inputDisposable.add(this._input.onEditProcessed((args) => {
|
||||
this.handleEditProcessedEvent(args);
|
||||
}));
|
||||
this._inputDisposable.add(this._input.onStateChange((args) => {
|
||||
this.handleInputStateChangedEvent(args);
|
||||
}));
|
||||
|
||||
if (this._input.view === undefined) {
|
||||
this._input.initialize();
|
||||
} else {
|
||||
this.initializeDesigner();
|
||||
}
|
||||
if (this._input.pendingAction) {
|
||||
this.updateLoadingStatus(this._input.pendingAction, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
super.dispose();
|
||||
this._inputDisposable?.dispose();
|
||||
}
|
||||
|
||||
private initializeDesigner(): void {
|
||||
const view = this._input.view;
|
||||
if (view.components) {
|
||||
this.createComponents(this._topContentContainer, view.components, this._componentMap, this._groupHeaders, component => component.propertyName, true, false);
|
||||
}
|
||||
view.tabs.forEach(tab => {
|
||||
this._tabbedPanel.pushTab(this.createTabView(tab));
|
||||
});
|
||||
this.layoutTabbedPanel();
|
||||
this.updateComponentValues();
|
||||
this.restoreUIState();
|
||||
}
|
||||
|
||||
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
||||
const edit = args.edit;
|
||||
const result = args.result;
|
||||
if (result.isValid) {
|
||||
this._supressEditProcessing = true;
|
||||
this.updateComponentValues();
|
||||
if (edit.type === DesignerEditType.Add) {
|
||||
// Move focus to the first cell of the newly added row.
|
||||
const propertyName = edit.property as string;
|
||||
const tableData = this._input.viewModel[propertyName] as DesignerTableProperties;
|
||||
const table = this._componentMap.get(propertyName).component as Table<Slick.SlickData>;
|
||||
table.setActiveCell(tableData.data.length - 1, 0);
|
||||
}
|
||||
this._supressEditProcessing = false;
|
||||
} else {
|
||||
//TODO: add error notification
|
||||
}
|
||||
}
|
||||
|
||||
private handleInputStateChangedEvent(args: DesignerStateChangedEventArgs): void {
|
||||
if (args.previousState.pendingAction !== args.currentState.pendingAction) {
|
||||
const showLoading = args.currentState.pendingAction !== undefined;
|
||||
const action = args.currentState.pendingAction || args.previousState.pendingAction;
|
||||
this.updateLoadingStatus(action, showLoading, true);
|
||||
}
|
||||
}
|
||||
|
||||
private updateLoadingStatus(action: DesignerAction, showLoading: boolean, useDelay: boolean): void {
|
||||
let message;
|
||||
let timeout;
|
||||
switch (action) {
|
||||
case 'save':
|
||||
message = showLoading ? localize('designer.savingChanges', "Saving changes...") : localize('designer.savingChangesCompleted', "Changes have been saved");
|
||||
timeout = 0;
|
||||
break;
|
||||
case 'initialize':
|
||||
message = showLoading ? localize('designer.loadingDesigner', "Loading designer...") : localize('designer.loadingDesignerCompleted', "Designer is loaded");
|
||||
timeout = 0;
|
||||
break;
|
||||
case 'processEdit':
|
||||
message = showLoading ? localize('designer.processingChanges', "Processing changes...") : localize('designer.processingChangesCompleted', "Changes have been processed");
|
||||
// To make the edit experience smoother, only show the loading indicator if the request is not returning in 500ms.
|
||||
timeout = 500;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (showLoading) {
|
||||
this.startLoading(message, useDelay ? timeout : 0);
|
||||
} else {
|
||||
this.stopLoading(message);
|
||||
}
|
||||
}
|
||||
|
||||
private layoutTabbedPanel() {
|
||||
this._tabbedPanel.layout(new DOM.Dimension(this._tabbedPanelContainer.clientWidth, this._tabbedPanelContainer.clientHeight));
|
||||
}
|
||||
|
||||
private updatePropertiesPane(newContext: PropertiesPaneObjectContext): void {
|
||||
const viewModel = this._input.viewModel;
|
||||
let type: string;
|
||||
let components: DesignerDataPropertyInfo[];
|
||||
let inputViewModel: DesignerViewModel;
|
||||
let context: PropertiesPaneObjectContext;
|
||||
if (newContext !== 'root') {
|
||||
context = newContext;
|
||||
const tableData = viewModel[newContext.parentProperty] as DesignerTableProperties;
|
||||
const tableProperties = this._componentMap.get(newContext.parentProperty).defintion.componentProperties as DesignerTableProperties;
|
||||
inputViewModel = tableData.data[newContext.index] as DesignerViewModel;
|
||||
components = tableProperties.itemProperties;
|
||||
type = tableProperties.objectTypeDisplayName;
|
||||
}
|
||||
|
||||
if (!inputViewModel) {
|
||||
context = 'root';
|
||||
components = [];
|
||||
this._componentMap.forEach(value => {
|
||||
components.push(value.defintion);
|
||||
});
|
||||
type = this._input.objectTypeDisplayName;
|
||||
inputViewModel = viewModel;
|
||||
}
|
||||
|
||||
if (inputViewModel) {
|
||||
this._propertiesPane.show({
|
||||
context: context,
|
||||
type: type,
|
||||
components: components,
|
||||
viewModel: inputViewModel
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private updateComponentValues(): void {
|
||||
const viewModel = this._input.viewModel;
|
||||
const scriptProperty = viewModel[ScriptProperty] as InputBoxProperties;
|
||||
if (scriptProperty) {
|
||||
this._textEditor.content = scriptProperty.value || '';
|
||||
}
|
||||
this._componentMap.forEach((value) => {
|
||||
this.setComponentValue(value.defintion, value.component, viewModel);
|
||||
});
|
||||
this.updatePropertiesPane(this._propertiesPane.context ?? 'root');
|
||||
}
|
||||
|
||||
private handleEdit(edit: DesignerEdit): void {
|
||||
if (this._supressEditProcessing) {
|
||||
return;
|
||||
}
|
||||
this.applyEdit(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 {
|
||||
const view = new DesignerTabPanelView(tab, (container, components, identifierGetter) => {
|
||||
return this.createComponents(container, components, this._componentMap, this._groupHeaders, identifierGetter, true, false);
|
||||
});
|
||||
return {
|
||||
identifier: tab.title,
|
||||
title: tab.title,
|
||||
view: view
|
||||
};
|
||||
}
|
||||
|
||||
private setComponentValue(definition: DesignerDataPropertyInfo, component: DesignerUIComponent, viewModel: DesignerViewModel): void {
|
||||
// Skip the property if it is not in the data model
|
||||
if (!viewModel[definition.propertyName]) {
|
||||
return;
|
||||
}
|
||||
this._supressEditProcessing = true;
|
||||
switch (definition.componentType) {
|
||||
case 'input':
|
||||
const input = component as InputBox;
|
||||
const inputData = viewModel[definition.propertyName] as InputBoxProperties;
|
||||
input.setEnabled(inputData.enabled ?? true);
|
||||
input.value = inputData.value?.toString() ?? '';
|
||||
break;
|
||||
case 'table':
|
||||
const table = component as Table<Slick.SlickData>;
|
||||
const tableDataView = table.getData() as TableDataView<Slick.SlickData>;
|
||||
const newData = (viewModel[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(newData);
|
||||
table.rerenderGrid();
|
||||
if (activeCell && newData.length > activeCell.row) {
|
||||
table.setActiveCell(activeCell.row, activeCell.cell);
|
||||
}
|
||||
break;
|
||||
case 'checkbox':
|
||||
const checkbox = component as Checkbox;
|
||||
const checkboxData = viewModel[definition.propertyName] as CheckBoxProperties;
|
||||
if (checkboxData.enabled === false) {
|
||||
checkbox.disable();
|
||||
} else {
|
||||
checkbox.enable();
|
||||
}
|
||||
checkbox.checked = checkboxData.checked;
|
||||
break;
|
||||
case 'dropdown':
|
||||
const dropdown = component as SelectBox;
|
||||
const defaultDropdownData = definition.componentProperties as DropDownProperties;
|
||||
const dropdownData = viewModel[definition.propertyName] as DropDownProperties;
|
||||
if (dropdownData.enabled === false) {
|
||||
dropdown.disable();
|
||||
} else {
|
||||
dropdown.enable();
|
||||
}
|
||||
const options = (dropdownData.values || defaultDropdownData.values || []) as string[];
|
||||
dropdown.setOptions(options);
|
||||
const idx = options?.indexOf(dropdownData.value as string);
|
||||
if (idx > -1) {
|
||||
dropdown.select(idx);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this._supressEditProcessing = false;
|
||||
}
|
||||
|
||||
private createComponents(container: HTMLElement,
|
||||
components: DesignerDataPropertyInfo[],
|
||||
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
||||
groupHeaders: HTMLElement[],
|
||||
identifierGetter: (definition: DesignerDataPropertyInfo) => DesignerEditIdentifier,
|
||||
setWidth: boolean, skipTableCreation: boolean = false): DesignerUIComponent[] {
|
||||
const uiComponents = [];
|
||||
const groupNames = [];
|
||||
const componentsToCreate = skipTableCreation ? components.filter(component => component.componentType !== 'table') : components;
|
||||
componentsToCreate.forEach(component => {
|
||||
if (groupNames.indexOf(component.group) === -1) {
|
||||
groupNames.push(component.group);
|
||||
}
|
||||
});
|
||||
|
||||
// only show groups when there are multiple of them.
|
||||
if (groupNames.length < 2) {
|
||||
componentsToCreate.forEach(component => {
|
||||
uiComponents.push(this.createComponent(container, component, identifierGetter(component), componentMap, setWidth));
|
||||
});
|
||||
} else {
|
||||
groupNames.forEach(group => {
|
||||
const groupHeader = container.appendChild(DOM.$('div.full-row.group-header'));
|
||||
groupHeaders.push(groupHeader);
|
||||
this.styleGroupHeader(groupHeader);
|
||||
groupHeader.innerText = group ?? localize('designer.generalGroupName', "General");
|
||||
componentsToCreate.forEach(component => {
|
||||
if (component.group === group) {
|
||||
uiComponents.push(this.createComponent(container, component, identifierGetter(component), componentMap, setWidth));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return uiComponents;
|
||||
}
|
||||
|
||||
private createComponent(container: HTMLElement,
|
||||
componentDefinition: DesignerDataPropertyInfo,
|
||||
editIdentifier: DesignerEditIdentifier,
|
||||
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
||||
setWidth: boolean): DesignerUIComponent {
|
||||
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(inputContainer, this._contextViewProvider, {
|
||||
ariaLabel: inputProperties.title,
|
||||
type: inputProperties.inputType,
|
||||
});
|
||||
input.onLoseFocus((args) => {
|
||||
if (args.hasChanged) {
|
||||
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: args.value });
|
||||
}
|
||||
});
|
||||
if (setWidth && inputProperties.width !== undefined) {
|
||||
input.width = inputProperties.width as number;
|
||||
}
|
||||
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(dropdownContainer);
|
||||
dropdown.selectElem.style.height = '25px';
|
||||
dropdown.onDidSelect((e) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: e.selected });
|
||||
});
|
||||
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(checkboxContainer, {
|
||||
label: checkboxProperties.title
|
||||
});
|
||||
checkbox.onChange((newValue) => {
|
||||
this.handleEdit({ type: DesignerEditType.Update, property: editIdentifier, value: newValue });
|
||||
});
|
||||
component = checkbox;
|
||||
break;
|
||||
case 'table':
|
||||
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
|
||||
if (tableProperties.canAddRows !== false) {
|
||||
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(() => {
|
||||
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 => {
|
||||
if (column.field) {
|
||||
return data[column.field].value;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
table.ariaLabel = tableProperties.ariaLabel;
|
||||
const columns = tableProperties.columns.map(propName => {
|
||||
const propertyDefinition = tableProperties.itemProperties.find(item => item.propertyName === propName);
|
||||
switch (propertyDefinition.componentType) {
|
||||
case 'checkbox':
|
||||
const checkboxColumn = new CheckBoxColumn({
|
||||
field: propertyDefinition.propertyName,
|
||||
name: propertyDefinition.componentProperties.title,
|
||||
width: propertyDefinition.componentProperties.width as number
|
||||
});
|
||||
table.registerPlugin(checkboxColumn);
|
||||
checkboxColumn.onChange((e) => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Update,
|
||||
property: {
|
||||
parentProperty: componentDefinition.propertyName,
|
||||
index: e.row,
|
||||
property: propertyDefinition.propertyName
|
||||
},
|
||||
value: e.value
|
||||
});
|
||||
});
|
||||
return checkboxColumn.definition;
|
||||
case 'dropdown':
|
||||
const dropdownProperties = propertyDefinition.componentProperties as DropDownProperties;
|
||||
return {
|
||||
name: dropdownProperties.title,
|
||||
field: propertyDefinition.propertyName,
|
||||
editor: this._tableCellEditorFactory.getSelectBoxEditorClass(componentDefinition.propertyName, dropdownProperties.values as string[]),
|
||||
width: dropdownProperties.width as number
|
||||
};
|
||||
default:
|
||||
const inputProperties = propertyDefinition.componentProperties as InputBoxProperties;
|
||||
return {
|
||||
name: inputProperties.title,
|
||||
field: propertyDefinition.propertyName,
|
||||
editor: this._tableCellEditorFactory.getTextEditorClass(componentDefinition.propertyName, inputProperties.inputType),
|
||||
width: inputProperties.width as number
|
||||
};
|
||||
}
|
||||
});
|
||||
if (tableProperties.canRemoveRows !== false) {
|
||||
const deleteRowColumn = new ButtonColumn({
|
||||
id: 'deleteRow',
|
||||
iconCssClass: Codicon.trash.classNames,
|
||||
title: localize('designer.removeRowText', "Remove"),
|
||||
width: 20,
|
||||
resizable: false,
|
||||
isFontIcon: true
|
||||
});
|
||||
deleteRowColumn.onClick((e) => {
|
||||
(this._input.viewModel[componentDefinition.propertyName] as DesignerTableProperties).data.splice(e.row, 1);
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Remove,
|
||||
property: componentDefinition.propertyName,
|
||||
value: e.item
|
||||
});
|
||||
});
|
||||
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) => {
|
||||
if (data.row !== undefined) {
|
||||
this.updatePropertiesPane({
|
||||
parentProperty: componentDefinition.propertyName,
|
||||
index: data.row
|
||||
});
|
||||
}
|
||||
});
|
||||
component = table;
|
||||
break;
|
||||
default:
|
||||
throw new Error(localize('tableDesigner.unknownComponentType', "The component type: {0} is not supported", componentDefinition.componentType));
|
||||
}
|
||||
componentMap.set(componentDefinition.propertyName, {
|
||||
defintion: componentDefinition,
|
||||
component: component
|
||||
});
|
||||
|
||||
this.styleComponent(component);
|
||||
return component;
|
||||
}
|
||||
|
||||
private startLoading(message: string, timeout: number): void {
|
||||
this._loadingTimeoutHandle = setTimeout(() => {
|
||||
this._loadingSpinner.loadingMessage = message;
|
||||
this._loadingSpinner.loading = true;
|
||||
if (this._container.contains(this._verticalSplitViewContainer)) {
|
||||
this._container.removeChild(this._verticalSplitViewContainer);
|
||||
}
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
private stopLoading(message: string = ''): void {
|
||||
clearTimeout(this._loadingTimeoutHandle);
|
||||
this._loadingTimeoutHandle = undefined;
|
||||
if (this._loadingSpinner.loading) {
|
||||
this._loadingSpinner.loadingCompletedMessage = message;
|
||||
this._loadingSpinner.loading = false;
|
||||
if (!this._container.contains(this._verticalSplitViewContainer)) {
|
||||
this._container.appendChild(this._verticalSplitViewContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getUIState(): DesignerUIState {
|
||||
return {
|
||||
activeTabId: this._tabbedPanel.activeTabId
|
||||
};
|
||||
}
|
||||
|
||||
private restoreUIState(): void {
|
||||
if (this._input.designerUIState) {
|
||||
this._tabbedPanel.showTab(this._input.designerUIState.activeTabId);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/sql/workbench/browser/designer/designerPropertiesPane.ts
Normal file
80
src/sql/workbench/browser/designer/designerPropertiesPane.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CreateComponentsFunc, DesignerUIComponent, SetComponentValueFunc } from 'sql/workbench/browser/designer/designer';
|
||||
import { DesignerViewModel, DesignerDataPropertyInfo } from 'sql/workbench/browser/designer/interfaces';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { equals } from 'vs/base/common/objects';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export type PropertiesPaneObjectContext = 'root' | {
|
||||
parentProperty: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
export interface ObjectInfo {
|
||||
context: PropertiesPaneObjectContext;
|
||||
type: string;
|
||||
components: DesignerDataPropertyInfo[];
|
||||
viewModel: DesignerViewModel;
|
||||
}
|
||||
|
||||
export class DesignerPropertiesPane {
|
||||
private _titleElement: HTMLElement;
|
||||
private _contentElement: HTMLElement;
|
||||
private _currentContext?: PropertiesPaneObjectContext;
|
||||
private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>();
|
||||
private _groupHeaders: HTMLElement[] = [];
|
||||
|
||||
constructor(container: HTMLElement, private _createComponents: CreateComponentsFunc, private _setComponentValue: SetComponentValueFunc) {
|
||||
const titleContainer = container.appendChild(DOM.$('.title-container'));
|
||||
this._titleElement = titleContainer.appendChild(DOM.$('div'));
|
||||
this._contentElement = container.appendChild(DOM.$('.properties-content.components-grid'));
|
||||
this._titleElement.innerText = localize('tableDesigner.propertiesPaneTitle', "Properties");
|
||||
}
|
||||
|
||||
public get groupHeaders(): HTMLElement[] {
|
||||
return this._groupHeaders;
|
||||
}
|
||||
|
||||
public get componentMap(): Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }> {
|
||||
return this._componentMap;
|
||||
}
|
||||
|
||||
public get context(): PropertiesPaneObjectContext | undefined {
|
||||
return this._currentContext;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._componentMap.forEach((value) => {
|
||||
value.component.dispose();
|
||||
});
|
||||
this._componentMap.clear();
|
||||
this._groupHeaders = [];
|
||||
DOM.clearNode(this._contentElement);
|
||||
this._currentContext = undefined;
|
||||
}
|
||||
|
||||
public show(item: ObjectInfo): void {
|
||||
if (!equals(item.context, this._currentContext)) {
|
||||
this.clear();
|
||||
this._currentContext = item.context;
|
||||
this._createComponents(this._contentElement, item.components, (property) => {
|
||||
return this._currentContext === 'root' ? property.propertyName : {
|
||||
parentProperty: this._currentContext.parentProperty,
|
||||
index: this._currentContext.index,
|
||||
property: property.propertyName
|
||||
};
|
||||
});
|
||||
}
|
||||
this._titleElement.innerText = localize({
|
||||
key: 'tableDesigner.propertiesPaneTitleWithContext',
|
||||
comment: ['{0} is the place holder for object type']
|
||||
}, "{0} Properties", item.type);
|
||||
this._componentMap.forEach((value) => {
|
||||
this._setComponentValue(value.defintion, value.component, item.viewModel);
|
||||
});
|
||||
}
|
||||
}
|
||||
116
src/sql/workbench/browser/designer/designerScriptEditor.ts
Normal file
116
src/sql/workbench/browser/designer/designerScriptEditor.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DesignerTextEditor } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput';
|
||||
import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { TextResourceEditorModel } from 'vs/workbench/common/editor/textResourceEditorModel';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
|
||||
import { IEditorOpenContext } from 'vs/workbench/common/editor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
class DesignerCodeEditor extends CodeEditorWidget {
|
||||
}
|
||||
|
||||
export class DesignerScriptEditor extends BaseTextEditor implements DesignerTextEditor {
|
||||
private _content: string;
|
||||
private _contentChangeEventEmitter: Emitter<string> = new Emitter<string>();
|
||||
readonly onDidContentChange: Event<string> = this._contentChangeEventEmitter.event;
|
||||
|
||||
private _untitledTextEditorModel: UntitledTextEditorModel;
|
||||
private _editorInput: UntitledTextEditorInput;
|
||||
private _editorModel: ITextModel;
|
||||
|
||||
public static ID = 'designer.editors.textEditor';
|
||||
constructor(
|
||||
private _container: HTMLElement,
|
||||
@IModelService private _modelService: IModelService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
) {
|
||||
super(DesignerScriptEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService);
|
||||
this.create(this._container);
|
||||
this.setVisible(true);
|
||||
this._untitledTextEditorModel = this.instantiationService.createInstance(UntitledTextEditorModel, URI.from({ scheme: Schemas.untitled }), false, undefined, 'sql', undefined);
|
||||
this._editorInput = this.instantiationService.createInstance(UntitledTextEditorInput, this._untitledTextEditorModel);
|
||||
this.setInput(this._editorInput, undefined, undefined).catch(onUnexpectedError);
|
||||
this._editorInput.resolve().then((model) => {
|
||||
this._editorModel = model.textEditorModel;
|
||||
});
|
||||
}
|
||||
|
||||
public override createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor {
|
||||
return this.instantiationService.createInstance(DesignerCodeEditor, parent, configuration, {});
|
||||
}
|
||||
|
||||
protected override getConfigurationOverrides(): IEditorOptions {
|
||||
const options = super.getConfigurationOverrides();
|
||||
options.readOnly = true;
|
||||
if (this.input) {
|
||||
options.inDiffEditor = false;
|
||||
options.scrollBeyondLastLine = false;
|
||||
options.folding = false;
|
||||
options.renderWhitespace = 'all';
|
||||
options.wordWrap = 'off';
|
||||
options.renderIndentGuides = false;
|
||||
options.rulers = [];
|
||||
options.glyphMargin = true;
|
||||
options.minimap = {
|
||||
enabled: true
|
||||
};
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
override async setInput(input: UntitledTextEditorInput, options: ITextEditorOptions, context: IEditorOpenContext): Promise<void> {
|
||||
await super.setInput(input, options, context, CancellationToken.None);
|
||||
const editorModel = await this.input.resolve() as TextResourceEditorModel;
|
||||
await editorModel.resolve();
|
||||
this.getControl().setModel(editorModel.textEditorModel);
|
||||
}
|
||||
|
||||
protected getAriaLabel(): string {
|
||||
return nls.localize('designer.textEditorAriaLabel', "Designer text editor.");
|
||||
}
|
||||
|
||||
public override layout(dimension: DOM.Dimension) {
|
||||
this.getControl().layout(dimension);
|
||||
}
|
||||
|
||||
get content(): string {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
set content(val: string) {
|
||||
this._content = val;
|
||||
this._modelService.updateModel(this._editorModel, this._content);
|
||||
this._untitledTextEditorModel.setDirty(false);
|
||||
this.layout(new DOM.Dimension(this._container.clientWidth, this._container.clientHeight));
|
||||
}
|
||||
}
|
||||
40
src/sql/workbench/browser/designer/designerTabPanelView.ts
Normal file
40
src/sql/workbench/browser/designer/designerTabPanelView.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DesignerTab } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
|
||||
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;
|
||||
|
||||
export class DesignerTabPanelView extends Disposable implements IPanelView {
|
||||
private _componentsContainer: HTMLElement;
|
||||
private _tables: Table<Slick.SlickData>[] = [];
|
||||
constructor(private readonly _tab: DesignerTab, private _createComponents: CreateComponentsFunc) {
|
||||
super();
|
||||
this._componentsContainer = DOM.$('.components-grid');
|
||||
const uiComponents = this._createComponents(this._componentsContainer, this._tab.components, component => component.propertyName);
|
||||
uiComponents.forEach(component => {
|
||||
if (component instanceof Table) {
|
||||
this._tables.push(component);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
container.appendChild(this._componentsContainer);
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension): void {
|
||||
this._tables.forEach(table => {
|
||||
table.layout(new DOM.Dimension(dimension.width - HorizontalPadding, dimension.height - VerticalPadding - ButtonHeight));
|
||||
});
|
||||
}
|
||||
}
|
||||
221
src/sql/workbench/browser/designer/interfaces.ts
Normal file
221
src/sql/workbench/browser/designer/interfaces.ts
Normal file
@@ -0,0 +1,221 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export interface DesignerComponentInput {
|
||||
/**
|
||||
* The event that is triggerd when the designer state changes.
|
||||
*/
|
||||
readonly onStateChange: Event<DesignerStateChangedEventArgs>;
|
||||
|
||||
/**
|
||||
* The event that is triggerd when the designer information is loaded.
|
||||
*/
|
||||
readonly onInitialized: Event<void>;
|
||||
|
||||
/**
|
||||
* The event that is triggerd when an edit is processed.
|
||||
*/
|
||||
readonly onEditProcessed: Event<DesignerEditProcessedEventArgs>;
|
||||
|
||||
/**
|
||||
* Gets the object type display name.
|
||||
*/
|
||||
readonly objectTypeDisplayName: string;
|
||||
|
||||
/**
|
||||
* Gets the designer view specification.
|
||||
*/
|
||||
readonly view: DesignerView;
|
||||
|
||||
/**
|
||||
* Gets the view model.
|
||||
*/
|
||||
readonly viewModel: DesignerViewModel;
|
||||
|
||||
/**
|
||||
* Start initilizing the designer input object.
|
||||
*/
|
||||
initialize(): void;
|
||||
|
||||
/**
|
||||
* Start processing the edit made in the designer, the OnEditProcessed event will be fired when the processing is done.
|
||||
* @param edit the information about the edit.
|
||||
*/
|
||||
processEdit(edit: DesignerEdit): void;
|
||||
|
||||
/**
|
||||
* A boolean value indicating whether the current state is valid.
|
||||
*/
|
||||
readonly valid: boolean;
|
||||
|
||||
/**
|
||||
* A boolean value indicating whether the current state is dirty.
|
||||
*/
|
||||
readonly dirty: boolean;
|
||||
|
||||
/**
|
||||
* Current in progress action.
|
||||
*/
|
||||
readonly pendingAction?: DesignerAction;
|
||||
|
||||
/**
|
||||
* The UI state of the designer, used to restore the state.
|
||||
*/
|
||||
designerUIState?: DesignerUIState;
|
||||
}
|
||||
|
||||
export interface DesignerUIState {
|
||||
activeTabId: PanelTabIdentifier;
|
||||
}
|
||||
|
||||
export type DesignerAction = 'save' | 'initialize' | 'processEdit';
|
||||
|
||||
export interface DesignerEditProcessedEventArgs {
|
||||
result: DesignerEditResult;
|
||||
edit: DesignerEdit
|
||||
}
|
||||
|
||||
export interface DesignerStateChangedEventArgs {
|
||||
currentState: DesignerState,
|
||||
previousState: DesignerState
|
||||
}
|
||||
export interface DesignerState {
|
||||
valid: boolean;
|
||||
dirty: boolean;
|
||||
pendingAction?: DesignerAction
|
||||
}
|
||||
|
||||
export const NameProperty = 'name';
|
||||
export const ScriptProperty = 'script';
|
||||
|
||||
export interface DesignerView {
|
||||
components?: DesignerDataPropertyInfo[]
|
||||
tabs: DesignerTab[];
|
||||
}
|
||||
|
||||
|
||||
export interface DesignerTab {
|
||||
title: string;
|
||||
components: DesignerDataPropertyInfo[];
|
||||
}
|
||||
|
||||
export interface DesignerViewModel {
|
||||
[key: string]: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties;
|
||||
}
|
||||
|
||||
export interface DesignerDataPropertyInfo {
|
||||
propertyName: string;
|
||||
description?: string;
|
||||
componentType: DesignerComponentTypeName;
|
||||
group?: string;
|
||||
componentProperties?: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties;
|
||||
}
|
||||
|
||||
export type DesignerComponentTypeName = 'input' | 'checkbox' | 'dropdown' | 'table';
|
||||
|
||||
export interface ComponentProperties {
|
||||
title?: string;
|
||||
|
||||
ariaLabel?: string;
|
||||
|
||||
width?: number | string;
|
||||
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface CategoryValue {
|
||||
displayName: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DropDownProperties extends ComponentProperties {
|
||||
value?: string | CategoryValue;
|
||||
values?: string[] | CategoryValue[];
|
||||
}
|
||||
|
||||
export interface CheckBoxProperties extends ComponentProperties {
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export interface InputBoxProperties extends ComponentProperties {
|
||||
value?: string;
|
||||
inputType?: 'text' | 'number';
|
||||
}
|
||||
|
||||
export interface DesignerTableProperties extends ComponentProperties {
|
||||
/**
|
||||
* the name of the properties to be displayed, properties not in this list will be accessible in details view.
|
||||
*/
|
||||
columns?: string[];
|
||||
|
||||
/**
|
||||
* The display name of the object type.
|
||||
*/
|
||||
objectTypeDisplayName: string;
|
||||
|
||||
/**
|
||||
* The properties of the table data item.
|
||||
*/
|
||||
itemProperties?: DesignerDataPropertyInfo[];
|
||||
|
||||
/**
|
||||
* The data to be displayed.
|
||||
*/
|
||||
data?: DesignerTableComponentRowData[];
|
||||
|
||||
/**
|
||||
* Whether user can add new rows to the table. The default value is true.
|
||||
*/
|
||||
canAddRows?: boolean;
|
||||
|
||||
/**
|
||||
* Whether user can remove rows from the table. The default value is true.
|
||||
*/
|
||||
canRemoveRows?: boolean;
|
||||
}
|
||||
|
||||
export interface DesignerTableComponentRowData {
|
||||
[key: string]: InputBoxProperties | CheckBoxProperties | DropDownProperties | DesignerTableProperties;
|
||||
}
|
||||
|
||||
|
||||
export enum DesignerEditType {
|
||||
Add = 0,
|
||||
Remove = 1,
|
||||
Update = 2
|
||||
}
|
||||
|
||||
export interface DesignerEdit {
|
||||
type: DesignerEditType;
|
||||
property: DesignerEditIdentifier;
|
||||
value?: any;
|
||||
}
|
||||
|
||||
export type DesignerEditIdentifier = string | { parentProperty: string, index: number, property: string };
|
||||
|
||||
export interface DesignerEditResult {
|
||||
isValid: boolean;
|
||||
errors?: { message: string, property?: DesignerEditIdentifier }[];
|
||||
}
|
||||
|
||||
export interface DesignerTextEditor {
|
||||
/**
|
||||
* Gets or sets the content of the text editor
|
||||
*/
|
||||
content: string;
|
||||
/**
|
||||
* Event fired when the content is changed by user
|
||||
*/
|
||||
readonly onDidContentChange: Event<string>;
|
||||
|
||||
/**
|
||||
* Update the size of the editor
|
||||
*/
|
||||
layout(dimensions: Dimension): void;
|
||||
}
|
||||
104
src/sql/workbench/browser/designer/media/designer.css
Normal file
104
src/sql/workbench/browser/designer/media/designer.css
Normal file
@@ -0,0 +1,104 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.designer-component, .designer-component .container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.designer-component .content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.designer-component .top-content-container {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.designer-component .editor-container {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.designer-component .tabbed-panel-container {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.designer-component .properties-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.designer-component .properties-container .title-container {
|
||||
padding: 5px;
|
||||
flex: 0 0 auto;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.designer-component .properties-container .properties-content {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.designer-component .component-label {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.designer-component .components-grid {
|
||||
display: grid;
|
||||
/* grid-template-columns: column 1 is for label, column 2 is for component.*/
|
||||
grid-template-columns: max-content auto;
|
||||
grid-template-rows: max-content;
|
||||
grid-gap: 5px;
|
||||
padding: 5px;
|
||||
align-content: start;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.designer-component .components-grid .full-row {
|
||||
grid-area: span 1 / span 2;
|
||||
}
|
||||
|
||||
.designer-component .monaco-table .slick-cell.editable {
|
||||
padding: 0px;
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.designer-component .add-row-button-container {
|
||||
display: flex;
|
||||
flex-flow: row-reverse;
|
||||
}
|
||||
|
||||
.designer-component .add-row-button-container .codicon.add-row-button {
|
||||
width: fit-content;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 13px;
|
||||
padding-left: 17px;
|
||||
background-position: 2px center;
|
||||
}
|
||||
|
||||
.designer-component .top-content-container .components-grid {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.designer-component .content-container .tabbedPanel {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.designer-component .components-grid .full-row.group-header {
|
||||
font-weight: bold;
|
||||
line-height: 25px;
|
||||
}
|
||||
Reference in New Issue
Block a user