mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -05:00
1120 lines
45 KiB
TypeScript
1120 lines
45 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* 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, DesignerPropertyPath, DesignerViewModel, DesignerDataPropertyInfo,
|
|
DesignerTableComponentRowData, DesignerTableProperties, InputBoxProperties, DropDownProperties, CheckBoxProperties,
|
|
DesignerEditProcessedEventArgs, DesignerStateChangedEventArgs, DesignerAction, ScriptProperty, DesignerRootObjectPath, CanBeDeletedProperty, DesignerUIArea
|
|
}
|
|
from 'sql/workbench/browser/designer/interfaces';
|
|
import { IPanelTab, 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 { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
|
import 'vs/css!./media/designer';
|
|
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
|
|
import { Table } from 'sql/base/browser/ui/table/table';
|
|
import { 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 } from 'sql/workbench/browser/designer/designerPropertiesPane';
|
|
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 { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
|
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
|
import { DesignerIssuesTabPanelView } from 'sql/workbench/browser/designer/designerIssuesTabPanelView';
|
|
import { DesignerScriptEditorTabPanelView } from 'sql/workbench/browser/designer/designerScriptEditorTabPanelView';
|
|
import { DesignerPropertyPathValidator } from 'sql/workbench/browser/designer/designerPropertyPathValidator';
|
|
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
|
import { asCssVariable, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground } 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 } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
|
import { IAction } from 'vs/base/common/actions';
|
|
import { InsertAfterSelectedRowAction, InsertBeforeSelectedRowAction, AddRowAction, DesignerTableActionContext, MoveRowDownAction, MoveRowUpAction, DesignerTableAction } from 'sql/workbench/browser/designer/tableActions';
|
|
import { RowMoveManager, RowMoveOnDragEventData } from 'sql/base/browser/ui/table/plugins/rowMoveManager.plugin';
|
|
import { ITaskbarContent, Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
|
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
|
import { listFocusAndSelectionBackground } from 'sql/platform/theme/common/colors';
|
|
import { timeout } from 'vs/base/common/async';
|
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
|
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
|
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
|
import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService';
|
|
import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
|
|
import { ThemeIcon } from 'vs/base/common/themables';
|
|
import { defaultCheckboxStyles, defaultEditableDropdownStyles, defaultSelectBoxStyles, getTableStyles } from 'sql/platform/theme/browser/defaultStyles';
|
|
import { GroupHeaderBackground } from 'sql/platform/theme/common/colorRegistry';
|
|
|
|
export type DesignerUIComponent = InputBox | Checkbox | Table<Slick.SlickData> | SelectBox | Dropdown;
|
|
|
|
export type CreateComponentsFunc = (container: HTMLElement, components: DesignerDataPropertyInfo[], parentPath: DesignerPropertyPath) => DesignerUIComponent[];
|
|
export type SetComponentValueFunc = (definition: DesignerDataPropertyInfo, component: DesignerUIComponent, data: DesignerViewModel) => void;
|
|
|
|
export interface IDesignerStyles {
|
|
paneSeparator: Color;
|
|
}
|
|
|
|
interface DesignerTableCellContext {
|
|
view: DesignerUIArea;
|
|
path: DesignerPropertyPath;
|
|
}
|
|
|
|
const ScriptTabId = 'scripts';
|
|
const IssuesTabId = 'issues';
|
|
|
|
export class Designer extends Disposable {
|
|
private _loadingSpinner: LoadingSpinner;
|
|
private _horizontalSplitViewContainer: HTMLElement;
|
|
private _verticalSplitViewContainer: HTMLElement;
|
|
private _tabbedPanelContainer: HTMLElement;
|
|
private _editorContainer: HTMLElement;
|
|
private _horizontalSplitView: SplitView;
|
|
private _verticalSplitView: SplitView;
|
|
private _contentTabbedPanel: TabbedPanel;
|
|
private _scriptTabbedPannel: TabbedPanel;
|
|
private _contentContainer: HTMLElement;
|
|
private _topContentContainer: HTMLElement;
|
|
private _propertiesPaneContainer: HTMLElement;
|
|
private _supressEditProcessing: boolean = false;
|
|
private _componentMap = new Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>();
|
|
private _input: DesignerComponentInput;
|
|
private _tableCellEditorFactory: TableCellEditorFactory;
|
|
private _propertiesPane: DesignerPropertiesPane;
|
|
private _inputDisposable: DisposableStore;
|
|
private _loadingTimeoutHandle: any;
|
|
private _issuesView: DesignerIssuesTabPanelView;
|
|
private _scriptEditorView: DesignerScriptEditorTabPanelView;
|
|
private _taskbars: Taskbar[] = [];
|
|
private _actionsMap: Map<Taskbar, DesignerTableAction[]> = new Map<Taskbar, DesignerTableAction[]>();
|
|
|
|
constructor(private readonly _container: HTMLElement,
|
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
|
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
|
@INotificationService private readonly _notificationService: INotificationService,
|
|
@IDialogService private readonly _dialogService: IDialogService,
|
|
@IThemeService private readonly _themeService: IThemeService,
|
|
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
|
|
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
|
|
@IQuickInputService private readonly _quickInputService: IQuickInputService,
|
|
@IComponentContextService private readonly _componentContextService: IComponentContextService) {
|
|
super();
|
|
this._tableCellEditorFactory = new TableCellEditorFactory(
|
|
{
|
|
valueGetter: (item, column): string => {
|
|
return item[column.field].value;
|
|
},
|
|
valueSetter: (context: DesignerTableCellContext, row: number, item: DesignerTableComponentRowData, column: Slick.Column<Slick.SlickData>, value: string): void => {
|
|
this.handleEdit({
|
|
type: DesignerEditType.Update,
|
|
path: [...context.path, row, column.field],
|
|
value: value,
|
|
source: context.view
|
|
});
|
|
},
|
|
optionsGetter: (item, column): string[] => {
|
|
return item[column.field].values;
|
|
},
|
|
inputBoxStyles: defaultInputBoxStyles,
|
|
editableDropdownStyles: defaultEditableDropdownStyles,
|
|
selectBoxStyles: defaultSelectBoxStyles
|
|
}, 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._contentTabbedPanel = this._register(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._scriptTabbedPannel = this._register(new TabbedPanel(this._editorContainer));
|
|
this._issuesView = this._instantiationService.createInstance(DesignerIssuesTabPanelView);
|
|
this._register(this._issuesView.onIssueSelected((path) => {
|
|
if (path && path.length > 0) {
|
|
this.selectProperty(path);
|
|
}
|
|
}));
|
|
this._scriptEditorView = new DesignerScriptEditorTabPanelView(this._instantiationService);
|
|
this._scriptTabbedPannel.pushTab({
|
|
title: localize('designer.scriptTabTitle', "Scripts"),
|
|
identifier: ScriptTabId,
|
|
view: this._scriptEditorView
|
|
});
|
|
this._verticalSplitView.addView({
|
|
element: this._editorContainer,
|
|
layout: size => {
|
|
this._scriptTabbedPannel.layout(new DOM.Dimension(this._editorContainer.clientWidth, size - this._scriptTabbedPannel.headersize));
|
|
},
|
|
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 => {
|
|
this.layoutPropertiesPane();
|
|
},
|
|
minimumSize: 200,
|
|
maximumSize: Number.POSITIVE_INFINITY,
|
|
onDidChange: Event.None
|
|
}, Sizing.Distribute);
|
|
|
|
|
|
this._propertiesPane = new DesignerPropertiesPane(this._propertiesPaneContainer, (container, components, parentPath) => {
|
|
return this.createComponents(container, components, this._propertiesPane.componentMap, parentPath, 'PropertiesView');
|
|
}, (definition, component, viewModel) => {
|
|
this.setComponentValue(definition, component, viewModel);
|
|
}, this._instantiationService);
|
|
}
|
|
|
|
public style(styles: IDesignerStyles): void {
|
|
this._verticalSplitView.style({
|
|
separatorBorder: styles.paneSeparator
|
|
});
|
|
|
|
this._horizontalSplitView.style({
|
|
separatorBorder: styles.paneSeparator
|
|
});
|
|
|
|
this._propertiesPane.descriptionElement.style.borderColor = styles.paneSeparator.toString();
|
|
}
|
|
|
|
public layout(dimension: DOM.Dimension) {
|
|
this._verticalSplitView.layout(dimension.height);
|
|
this._horizontalSplitView.layout(dimension.width);
|
|
}
|
|
|
|
|
|
public setInput(input: DesignerComponentInput): void {
|
|
if (this._input) {
|
|
void this.submitPendingChanges().catch(onUnexpectedError);
|
|
}
|
|
this.saveUIState();
|
|
if (this._loadingTimeoutHandle) {
|
|
this.stopLoading();
|
|
}
|
|
this.clearUI();
|
|
this._inputDisposable?.dispose();
|
|
// 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);
|
|
}));
|
|
this._inputDisposable.add(this._input.onRefreshRequested(() => {
|
|
this.refresh();
|
|
}));
|
|
this._inputDisposable.add(this._input.onSubmitPendingEditRequested(async () => {
|
|
await this.submitPendingChanges();
|
|
}));
|
|
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();
|
|
}
|
|
|
|
public async submitPendingChanges(): Promise<void> {
|
|
if (this._container.contains(document.activeElement) && document.activeElement instanceof HTMLInputElement) {
|
|
// Force the elements to fire the blur event to submit the pending changes.
|
|
document.activeElement.blur();
|
|
return timeout(10);
|
|
}
|
|
}
|
|
|
|
private clearUI(): void {
|
|
this._componentMap.forEach(item => item.component.dispose());
|
|
this._componentMap.clear();
|
|
DOM.clearNode(this._topContentContainer);
|
|
this._contentTabbedPanel.clearTabs();
|
|
this._propertiesPane.clear();
|
|
this._taskbars.map(t => t.dispose());
|
|
}
|
|
|
|
private initializeDesigner(): void {
|
|
const view = this._input.view;
|
|
if (view.components) {
|
|
this.createComponents(this._topContentContainer, view.components, this._componentMap, DesignerRootObjectPath, 'TopContentView');
|
|
}
|
|
view.tabs.forEach(tab => {
|
|
this._contentTabbedPanel.pushTab(this.createTabView(tab));
|
|
});
|
|
this.updateComponentValues();
|
|
this.layoutTabbedPanel();
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
this.restoreUIState();
|
|
}
|
|
|
|
private handleCellFocusAfterAddOrMove(edit: DesignerEdit): void {
|
|
if (edit.path.length === 2) {
|
|
const propertyName = edit.path[0] as string;
|
|
const index = edit.type === DesignerEditType.Add ? edit.path[1] as number : edit.value as number;
|
|
const table = this._componentMap.get(propertyName).component as Table<Slick.SlickData>;
|
|
const tableProperties = this._componentMap.get(propertyName).defintion.componentProperties as DesignerTableProperties;
|
|
let selectedCellIndex = tableProperties.itemProperties.findIndex(p => p.componentType === 'input');
|
|
selectedCellIndex = tableProperties.canMoveRows ? selectedCellIndex + 1 : selectedCellIndex;
|
|
try {
|
|
table.grid.resetActiveCell();
|
|
table.setActiveCell(index, selectedCellIndex);
|
|
table.setSelectedRows([index]);
|
|
}
|
|
catch {
|
|
// Ignore the slick grid error when setting active cell.
|
|
}
|
|
} else {
|
|
this.updatePropertiesPane(this._propertiesPane.objectPath);
|
|
}
|
|
}
|
|
|
|
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
|
const edit = args.edit;
|
|
this._supressEditProcessing = true;
|
|
if (args.result.issues?.length > 0) {
|
|
alert(localize('designer.issueCountAlert', "{0} validation issues found.", args.result.issues.length));
|
|
}
|
|
try {
|
|
if (args.result.refreshView) {
|
|
this.refresh();
|
|
if (!args.result.isValid) {
|
|
this._scriptTabbedPannel.showTab(IssuesTabId);
|
|
}
|
|
} else {
|
|
this.updateComponentValues();
|
|
this.layoutTabbedPanel();
|
|
}
|
|
if (edit.type === DesignerEditType.Add || edit.type === DesignerEditType.Move) {
|
|
this.handleCellFocusAfterAddOrMove(edit);
|
|
} else if (edit.type === DesignerEditType.Update) {
|
|
// for edit, update the properties pane with new values of current object.
|
|
this.updatePropertiesPane(this._propertiesPane.objectPath);
|
|
} else if (edit.type === DesignerEditType.Remove) {
|
|
// removing the secondary level entities, the properties pane needs to be updated to reflect the changes.
|
|
if (edit.path.length === 4) {
|
|
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);
|
|
}
|
|
this._supressEditProcessing = false;
|
|
}
|
|
|
|
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 'publish':
|
|
message = showLoading ? localize('designer.publishingChanges', "Publishing changes...") : localize('designer.publishChangesCompleted', "Changes have been published");
|
|
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:
|
|
message = showLoading ? localize('designer.processing', "Processing...") : localize('designer.processingCompleted', "Processing completed");
|
|
timeout = 0;
|
|
break;
|
|
}
|
|
if (showLoading) {
|
|
this.startLoading(message, useDelay ? timeout : 0);
|
|
} else {
|
|
this.stopLoading(message);
|
|
}
|
|
}
|
|
|
|
private refresh() {
|
|
this.saveUIState();
|
|
this.clearUI();
|
|
this.initializeDesigner();
|
|
}
|
|
|
|
private layoutTabbedPanel() {
|
|
this._contentTabbedPanel.layout(new DOM.Dimension(this._tabbedPanelContainer.clientWidth, this._tabbedPanelContainer.clientHeight));
|
|
}
|
|
|
|
private layoutPropertiesPane() {
|
|
this._propertiesPane?.componentMap.forEach((v) => {
|
|
if (v.component instanceof Table) {
|
|
layoutDesignerTable(v.component, this._propertiesPaneContainer.clientWidth);
|
|
}
|
|
});
|
|
}
|
|
|
|
private updatePropertiesPane(objectPath: DesignerPropertyPath): void {
|
|
let type: string;
|
|
let components: DesignerDataPropertyInfo[];
|
|
let objectViewModel: DesignerViewModel;
|
|
if (objectPath.length === 0) { // root object
|
|
type = this._input.objectTypeDisplayName;
|
|
components = [];
|
|
components.push(...this._input.view.components);
|
|
this._input.view.tabs.forEach(tab => {
|
|
components.push(...tab.components);
|
|
});
|
|
objectViewModel = this._input.viewModel;
|
|
} else if (objectPath.length === 2) { // second level object
|
|
const parentPropertyName = objectPath[0] as string;
|
|
const objectIndex = objectPath[1] as number;
|
|
const tableData = this._input.viewModel[parentPropertyName] as DesignerTableProperties;
|
|
const tableProperties = this._componentMap.get(parentPropertyName).defintion.componentProperties as DesignerTableProperties;
|
|
objectViewModel = tableData.data[objectIndex] as DesignerViewModel;
|
|
components = tableProperties.itemProperties;
|
|
type = tableProperties.objectTypeDisplayName;
|
|
}
|
|
|
|
this._propertiesPane.show({
|
|
path: objectPath,
|
|
type: type,
|
|
components: components,
|
|
viewModel: objectViewModel
|
|
});
|
|
this.layoutPropertiesPane();
|
|
}
|
|
|
|
private updateComponentValues(): void {
|
|
this.updateIssuesTab();
|
|
const viewModel = this._input.viewModel;
|
|
const scriptProperty = viewModel[ScriptProperty] as InputBoxProperties;
|
|
if (scriptProperty) {
|
|
this._scriptEditorView.content = scriptProperty.value || '';
|
|
}
|
|
this._componentMap.forEach((value) => {
|
|
this.setComponentValue(value.defintion, value.component, viewModel);
|
|
});
|
|
}
|
|
|
|
private updateIssuesTab(): void {
|
|
if (!this._input) {
|
|
return;
|
|
}
|
|
if (this._scriptTabbedPannel.contains(IssuesTabId)) {
|
|
this._scriptTabbedPannel.removeTab(IssuesTabId);
|
|
}
|
|
|
|
if (this._input.issues === undefined || this._input.issues.length === 0) {
|
|
return;
|
|
}
|
|
this._scriptTabbedPannel.pushTab({
|
|
title: localize('designer.issuesTabTitle', "Issues ({0})", this._input.issues.length),
|
|
identifier: IssuesTabId,
|
|
view: this._issuesView
|
|
});
|
|
this._scriptTabbedPannel.showTab(IssuesTabId);
|
|
this._issuesView.updateIssues(this._input.issues);
|
|
}
|
|
|
|
private selectProperty(path: DesignerPropertyPath, view?: DesignerUIArea, highlight: boolean = true): void {
|
|
if (!DesignerPropertyPathValidator.validate(path, this._input.viewModel)) {
|
|
return;
|
|
}
|
|
|
|
// Find top level property
|
|
let found = false;
|
|
if (this._input.view.components) {
|
|
for (const component of this._input.view.components) {
|
|
if (path[0] === component.propertyName) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (this._input.view.tabs) {
|
|
for (const tab of this._input.view.tabs) {
|
|
if (tab) {
|
|
for (const component of tab.components) {
|
|
if (path[0] === component.propertyName) {
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
if (found) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
const propertyInfo = this._componentMap.get(<string>path[0]);
|
|
if (propertyInfo.defintion.componentType !== 'table') {
|
|
if (view === 'PropertiesView') {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
this._propertiesPane.selectProperty(path);
|
|
} else {
|
|
propertyInfo.component.focus();
|
|
}
|
|
return;
|
|
} else {
|
|
const tableComponent = <Table<Slick.SlickData>>propertyInfo.component;
|
|
const targetRow = <number>path[1];
|
|
const targetCell = 0;
|
|
tableComponent.setActiveCell(targetRow, targetCell);
|
|
tableComponent.grid.scrollCellIntoView(targetRow, targetCell, false);
|
|
if (path.length > 2) {
|
|
const relativePath = path.slice(2);
|
|
this._propertiesPane.selectProperty(relativePath);
|
|
}
|
|
}
|
|
if (highlight) {
|
|
this.highlightActiveElement();
|
|
}
|
|
}
|
|
}
|
|
|
|
private highlightActiveElement(): void {
|
|
const bgColor = this._themeService.getColorTheme().getColor(listActiveSelectionBackground);
|
|
const color = this._themeService.getColorTheme().getColor(listActiveSelectionForeground);
|
|
const currentElement = document.activeElement as HTMLElement;
|
|
if (currentElement) {
|
|
const originalBGColor = currentElement.style.backgroundColor;
|
|
const originalColor = currentElement.style.color;
|
|
currentElement.style.backgroundColor = bgColor.toString();
|
|
currentElement.style.color = color.toString();
|
|
setTimeout(() => {
|
|
currentElement.style.color = originalColor;
|
|
currentElement.style.backgroundColor = originalBGColor;
|
|
}, 500);
|
|
}
|
|
}
|
|
|
|
public handleEdit(edit: DesignerEdit): void {
|
|
if (this._supressEditProcessing) {
|
|
return;
|
|
}
|
|
this._input.processEdit(edit);
|
|
}
|
|
|
|
private createTabView(tab: DesignerTab): IPanelTab {
|
|
const view = new DesignerTabPanelView(tab, (container, components, identifierGetter) => {
|
|
return this.createComponents(container, components, this._componentMap, identifierGetter, 'TabsView');
|
|
});
|
|
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 dropdownProperties = definition.componentProperties as DropDownProperties;
|
|
const dropdownData = viewModel[definition.propertyName] as DropDownProperties;
|
|
const options = (dropdownData.values || dropdownProperties.values || []) as string[];
|
|
const idx = options?.indexOf(dropdownData.value as string);
|
|
let dropdown: Dropdown | SelectBox;
|
|
if (dropdownProperties.isEditable) {
|
|
dropdown = component as Dropdown;
|
|
if (dropdownData.enabled === false) {
|
|
dropdown.enabled = false;
|
|
} else {
|
|
dropdown.enabled = true;
|
|
}
|
|
dropdown.values = options;
|
|
if (idx > -1) {
|
|
dropdown.value = options[idx];
|
|
}
|
|
} else {
|
|
dropdown = component as SelectBox;
|
|
if (dropdownData.enabled === false) {
|
|
dropdown.disable();
|
|
} else {
|
|
dropdown.enable();
|
|
}
|
|
dropdown.setOptions(options);
|
|
if (idx > -1) {
|
|
dropdown.select(idx);
|
|
}
|
|
}
|
|
this._register(dropdown);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
this._supressEditProcessing = false;
|
|
}
|
|
|
|
private createComponents(container: HTMLElement,
|
|
components: DesignerDataPropertyInfo[],
|
|
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
|
parentPath: DesignerPropertyPath,
|
|
area: DesignerUIArea): DesignerUIComponent[] {
|
|
const uiComponents = [];
|
|
const groupNames = [];
|
|
const componentsToCreate = area === 'PropertiesView' ? components.filter(component => component.showInPropertiesView !== false) : components;
|
|
componentsToCreate.forEach(component => {
|
|
// Set the default group name if not set (undefined or null).
|
|
component.group = component.group || localize('designer.generalGroupName', "General");
|
|
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, parentPath, componentMap, area));
|
|
});
|
|
} else {
|
|
groupNames.forEach(group => {
|
|
const groupHeader = container.appendChild(DOM.$('div.full-row.group-header'));
|
|
groupHeader.style.backgroundColor = asCssVariable(GroupHeaderBackground);
|
|
groupHeader.innerText = group;
|
|
componentsToCreate.forEach(component => {
|
|
if (component.group === group) {
|
|
uiComponents.push(this.createComponent(container, component, parentPath, componentMap, area));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
return uiComponents;
|
|
}
|
|
|
|
private createComponent(container: HTMLElement,
|
|
componentDefinition: DesignerDataPropertyInfo,
|
|
parentPath: DesignerPropertyPath,
|
|
componentMap: Map<string, { defintion: DesignerDataPropertyInfo, component: DesignerUIComponent }>,
|
|
view: DesignerUIArea): DesignerUIComponent {
|
|
const propertyPath = [...parentPath, componentDefinition.propertyName];
|
|
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 = this._register(new InputBox(inputContainer, this._contextViewProvider, {
|
|
ariaLabel: inputProperties.title,
|
|
type: inputProperties.inputType,
|
|
ariaDescription: componentDefinition.description,
|
|
inputBoxStyles: defaultInputBoxStyles
|
|
}));
|
|
this._register(input.onLoseFocus((args) => {
|
|
if (args.hasChanged) {
|
|
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: args.value, source: view });
|
|
}
|
|
}));
|
|
this._register(input.onInputFocus(() => {
|
|
if (view === 'PropertiesView') {
|
|
this._propertiesPane.updateDescription(componentDefinition);
|
|
} else if (view === 'TabsView' || view === 'TopContentView') {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
}
|
|
}));
|
|
if (view === 'TopContentView' && inputProperties.width) {
|
|
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;
|
|
let dropdown;
|
|
if (dropdownProperties.isEditable) {
|
|
dropdown = this._register(new Dropdown(dropdownContainer, this._contextViewProvider, {
|
|
values: dropdownProperties.values as string[] || [],
|
|
ariaLabel: componentDefinition.componentProperties?.title,
|
|
ariaDescription: componentDefinition.description,
|
|
...defaultEditableDropdownStyles
|
|
}));
|
|
this._register(dropdown.onValueChange((value) => {
|
|
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: value, source: view });
|
|
}));
|
|
this._register(dropdown.onFocus(() => {
|
|
if (view === 'PropertiesView') {
|
|
this._propertiesPane.updateDescription(componentDefinition);
|
|
} else if (view === 'TabsView' || view === 'TopContentView') {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
}
|
|
}));
|
|
} else {
|
|
dropdown = this._register(new SelectBox(dropdownProperties.values as string[] || [], undefined, defaultSelectBoxStyles, this._contextViewProvider, undefined, {
|
|
ariaLabel: componentDefinition.componentProperties?.title,
|
|
ariaDescription: componentDefinition.description
|
|
}));
|
|
dropdown.render(dropdownContainer);
|
|
dropdown.selectElem.style.height = '25px';
|
|
this._register(dropdown.onDidSelect((e) => {
|
|
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: e.selected, source: view });
|
|
}));
|
|
this._register(dropdown.onDidFocus(() => {
|
|
if (view === 'PropertiesView') {
|
|
this._propertiesPane.updateDescription(componentDefinition);
|
|
} else if (view === 'TabsView' || view === 'TopContentView') {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
}
|
|
}));
|
|
}
|
|
component = dropdown;
|
|
break;
|
|
case 'checkbox':
|
|
container.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? '';
|
|
const checkboxContainer = container.appendChild(DOM.$(''));
|
|
const checkboxProperties = componentDefinition.componentProperties as CheckBoxProperties;
|
|
const checkbox = this._register(new Checkbox(checkboxContainer, { ...defaultCheckboxStyles, label: '', ariaLabel: checkboxProperties.title, ariaDescription: componentDefinition.description }));
|
|
this._register(checkbox.onChange((newValue) => {
|
|
this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: newValue, source: view });
|
|
}));
|
|
this._register(checkbox.onFocus(() => {
|
|
if (view === 'PropertiesView') {
|
|
this._propertiesPane.updateDescription(componentDefinition);
|
|
} else if (view === 'TabsView' || view === 'TopContentView') {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
}
|
|
}));
|
|
component = checkbox;
|
|
break;
|
|
case 'table':
|
|
if (view === 'PropertiesView') {
|
|
container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? '';
|
|
}
|
|
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
|
|
const taskbar = this.addTableTaskbar(container, tableProperties);
|
|
const tableContainer = container.appendChild(DOM.$('.full-row'));
|
|
const table = this._register(new Table(tableContainer, this._accessibilityService, this._quickInputService, getTableStyles({
|
|
listActiveSelectionBackground: undefined,
|
|
listActiveSelectionForeground: undefined,
|
|
listFocusAndSelectionBackground: undefined,
|
|
listFocusAndSelectionForeground: undefined,
|
|
listInactiveFocusBackground: undefined,
|
|
listInactiveSelectionBackground: undefined,
|
|
listInactiveSelectionForeground: undefined
|
|
}), {
|
|
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;
|
|
}
|
|
},
|
|
rowHeight: TableRowHeight,
|
|
headerRowHeight: TableHeaderRowHeight,
|
|
editorLock: new Slick.EditorLock()
|
|
}));
|
|
table.grid.setSelectionModel(new RowSelectionModel());
|
|
if (taskbar) {
|
|
taskbar.context = { table: table, path: propertyPath, source: view };
|
|
this._actionsMap.get(taskbar).map(a => a.table = table);
|
|
}
|
|
const columns: Slick.Column<Slick.SlickData>[] = [];
|
|
if (tableProperties.canMoveRows) {
|
|
// Add row move drag and drop
|
|
const moveRowsPlugin = new RowMoveManager({
|
|
cancelEditOnDrag: true,
|
|
id: 'moveRow',
|
|
iconCssClass: ThemeIcon.asClassName(Codicon.grabber),
|
|
name: localize('designer.moveRowText', 'Move'),
|
|
width: 50,
|
|
resizable: true,
|
|
isFontIcon: true,
|
|
behavior: 'selectAndMove'
|
|
});
|
|
table.registerPlugin(moveRowsPlugin);
|
|
moveRowsPlugin.onMoveRows.subscribe((e: Slick.EventData, data: RowMoveOnDragEventData) => {
|
|
const row = data.rows[0];
|
|
// no point in moving before or after itself
|
|
if (row === data.insertBefore || row === data.insertBefore - 1) {
|
|
e.stopPropagation();
|
|
return;
|
|
}
|
|
this.handleEdit({
|
|
type: DesignerEditType.Move,
|
|
path: [...propertyPath, row],
|
|
source: view,
|
|
value: data.insertBefore < row ? data.insertBefore : data.insertBefore - 1
|
|
});
|
|
});
|
|
table.grid.registerPlugin(moveRowsPlugin);
|
|
columns.push(moveRowsPlugin.definition);
|
|
}
|
|
table.ariaLabel = tableProperties.ariaLabel;
|
|
columns.push(...tableProperties.columns.map((propName, index) => {
|
|
const propertyDefinition = tableProperties.itemProperties.find(item => item.propertyName === propName);
|
|
switch (propertyDefinition.componentType) {
|
|
case 'checkbox':
|
|
const checkboxColumn = new CheckBoxColumn({
|
|
id: index.toString(),
|
|
field: propertyDefinition.propertyName,
|
|
name: propertyDefinition.componentProperties.title,
|
|
width: propertyDefinition.componentProperties.width as number
|
|
});
|
|
table.registerPlugin(checkboxColumn);
|
|
this._register(checkboxColumn.onChange((e) => {
|
|
this.handleEdit({
|
|
type: DesignerEditType.Update,
|
|
path: [...propertyPath, e.row, propertyDefinition.propertyName],
|
|
value: e.value,
|
|
source: view
|
|
});
|
|
}));
|
|
return checkboxColumn.definition;
|
|
case 'dropdown':
|
|
const dropdownProperties = propertyDefinition.componentProperties as DropDownProperties;
|
|
return {
|
|
id: index.toString(),
|
|
name: dropdownProperties.title,
|
|
field: propertyDefinition.propertyName,
|
|
editor: this._tableCellEditorFactory.getDropdownEditorClass({ view: view, path: propertyPath }, dropdownProperties.values as string[], dropdownProperties.isEditable),
|
|
width: dropdownProperties.width as number
|
|
};
|
|
default:
|
|
const inputProperties = propertyDefinition.componentProperties as InputBoxProperties;
|
|
return {
|
|
id: index.toString(),
|
|
name: inputProperties.title,
|
|
field: propertyDefinition.propertyName,
|
|
editor: this._tableCellEditorFactory.getTextEditorClass({ view: view, path: propertyPath }, inputProperties.inputType),
|
|
width: inputProperties.width as number
|
|
};
|
|
}
|
|
}));
|
|
if (tableProperties.canRemoveRows) {
|
|
const removeText = localize('designer.removeRowText', "Remove");
|
|
const deleteRowColumn = new ButtonColumn({
|
|
id: 'deleteRow',
|
|
iconCssClass: ThemeIcon.asClassName(Codicon.trash),
|
|
name: removeText,
|
|
title: removeText,
|
|
width: 60,
|
|
resizable: true,
|
|
isFontIcon: true,
|
|
enabledField: CanBeDeletedProperty
|
|
});
|
|
deleteRowColumn.onClick(async (e) => {
|
|
if (tableProperties.showRemoveRowConfirmation) {
|
|
const confirmMessage = tableProperties.removeRowConfirmationMessage || localize('designer.defaultRemoveRowConfirmationMessage', "Are you sure you want to remove the row?");
|
|
const result = await this._dialogService.confirm({
|
|
type: 'question',
|
|
message: confirmMessage
|
|
});
|
|
if (!result.confirmed) {
|
|
return;
|
|
}
|
|
}
|
|
this.handleEdit({
|
|
type: DesignerEditType.Remove,
|
|
path: [...propertyPath, e.row],
|
|
source: view
|
|
});
|
|
});
|
|
table.registerPlugin(deleteRowColumn);
|
|
columns.push(deleteRowColumn.definition);
|
|
}
|
|
if (tableProperties.canInsertRows || tableProperties.canMoveRows) {
|
|
const moreActionsText = localize('designer.actions', "More Actions");
|
|
const actionsColumn = new ButtonColumn({
|
|
id: 'actions',
|
|
iconCssClass: ThemeIcon.asClassName(Codicon.ellipsis),
|
|
name: moreActionsText,
|
|
title: moreActionsText,
|
|
width: 100,
|
|
resizable: true,
|
|
isFontIcon: true
|
|
});
|
|
this._register(actionsColumn.onClick((e) => {
|
|
this.openContextMenu(table, e.row, e.position, propertyPath, view, tableProperties);
|
|
}));
|
|
table.registerPlugin(actionsColumn);
|
|
columns.push(actionsColumn.definition);
|
|
// Add move context menu actions
|
|
this._register(table.onContextMenu((e) => {
|
|
this.openContextMenu(table, e.cell.row, e.anchor, propertyPath, view, tableProperties);
|
|
}));
|
|
}
|
|
table.columns = columns;
|
|
table.grid.onBeforeEditCell.subscribe((e, data): boolean => {
|
|
return data.item[data.column.field].enabled !== false;
|
|
});
|
|
let currentTableActions = [];
|
|
if (taskbar) {
|
|
currentTableActions = this._actionsMap.get(taskbar);
|
|
}
|
|
table.grid.onActiveCellChanged.subscribe((e, data) => {
|
|
if (view === 'TabsView' || view === 'TopContentView') {
|
|
if (data.row !== undefined) {
|
|
if (tableProperties.showItemDetailInPropertiesView === false) {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
} else {
|
|
this.updatePropertiesPane([...propertyPath, data.row]);
|
|
}
|
|
} else {
|
|
this.updatePropertiesPane(DesignerRootObjectPath);
|
|
}
|
|
} else if (view === 'PropertiesView') {
|
|
if (data.row !== undefined) {
|
|
this._propertiesPane.updateDescription(componentDefinition);
|
|
}
|
|
}
|
|
if (data.row !== undefined) {
|
|
currentTableActions.forEach(a => a.updateState(data.row));
|
|
table.grid.setSelectedRows([data.row]);
|
|
}
|
|
});
|
|
table.onBlur((e) => {
|
|
currentTableActions.forEach(a => a.updateState());
|
|
table.grid.setSelectedRows([]);
|
|
});
|
|
this._register(this._componentContextService.registerTable(table));
|
|
component = table;
|
|
break;
|
|
default:
|
|
throw new Error(localize('designer.unknownComponentType', "The component type: {0} is not supported", componentDefinition.componentType));
|
|
}
|
|
componentMap.set(componentDefinition.propertyName, {
|
|
defintion: componentDefinition,
|
|
component: component
|
|
});
|
|
return component;
|
|
}
|
|
|
|
private addTableTaskbar(container: HTMLElement, tableProperties: DesignerTableProperties): Taskbar | undefined {
|
|
if (tableProperties.canAddRows || tableProperties.canMoveRows) {
|
|
const taskbarContainer = container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('.add-row-button-container'));
|
|
const taskbar = new Taskbar(taskbarContainer);
|
|
const actions = [];
|
|
if (tableProperties.canAddRows) {
|
|
const addRowAction = this._instantiationService.createInstance(AddRowAction, this, tableProperties);
|
|
actions.push(addRowAction);
|
|
}
|
|
if (tableProperties.canMoveRows) {
|
|
const moveUpAction = this._instantiationService.createInstance(MoveRowUpAction, this);
|
|
const moveDownAction = this._instantiationService.createInstance(MoveRowDownAction, this);
|
|
actions.push(moveUpAction);
|
|
actions.push(moveDownAction);
|
|
}
|
|
const taskbarContent: ITaskbarContent[] = actions.map((a) => { return { action: a }; });
|
|
taskbar.setContent(taskbarContent);
|
|
this._actionsMap.set(taskbar, actions);
|
|
return taskbar;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
private openContextMenu(
|
|
table: Table<Slick.SlickData>,
|
|
rowIndex: number,
|
|
anchor: HTMLElement | { x: number, y: number },
|
|
propertyPath: DesignerPropertyPath,
|
|
view: DesignerUIArea,
|
|
tableProperties: DesignerTableProperties
|
|
): void {
|
|
const tableActionContext: DesignerTableActionContext = {
|
|
table: table,
|
|
path: propertyPath,
|
|
source: view,
|
|
selectedRow: rowIndex
|
|
};
|
|
const data = table.grid.getData() as Slick.DataProvider<Slick.SlickData>;
|
|
if (!data || rowIndex >= data.getLength()) {
|
|
return undefined;
|
|
}
|
|
const actions = this.getTableActions(tableProperties);
|
|
actions.forEach(a => {
|
|
if (a instanceof DesignerTableAction) {
|
|
a.table = table;
|
|
a.updateState(rowIndex);
|
|
}
|
|
});
|
|
this._contextMenuService.showContextMenu({
|
|
getAnchor: () => anchor,
|
|
getActions: () => actions,
|
|
getActionsContext: () => (tableActionContext)
|
|
});
|
|
}
|
|
|
|
private getTableActions(tableProperties: DesignerTableProperties): IAction[] {
|
|
const actions: IAction[] = [];
|
|
if (tableProperties.canInsertRows) {
|
|
const insertRowBefore = this._instantiationService.createInstance(InsertBeforeSelectedRowAction, this);
|
|
const insertRowAfter = this._instantiationService.createInstance(InsertAfterSelectedRowAction, this);
|
|
actions.push(insertRowBefore);
|
|
actions.push(insertRowAfter);
|
|
}
|
|
if (tableProperties.canMoveRows) {
|
|
const moveRowUp = this._instantiationService.createInstance(MoveRowUpAction, this);
|
|
const moveRowDown = this._instantiationService.createInstance(MoveRowDownAction, this);
|
|
actions.push(moveRowUp);
|
|
actions.push(moveRowDown);
|
|
}
|
|
return actions;
|
|
}
|
|
|
|
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 saveUIState(): void {
|
|
if (this._input) {
|
|
this._input.designerUIState = {
|
|
activeContentTabId: this._contentTabbedPanel.selectedTabId,
|
|
activeScriptTabId: this._scriptTabbedPannel.selectedTabId
|
|
};
|
|
}
|
|
}
|
|
|
|
private restoreUIState(): void {
|
|
if (this._input.designerUIState) {
|
|
this._contentTabbedPanel.showTab(this._input.designerUIState.activeContentTabId);
|
|
this._scriptTabbedPannel.showTab(this._input.designerUIState.activeScriptTabId);
|
|
}
|
|
}
|
|
}
|
|
|
|
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
|
const listHoverBackgroundColor = theme.getColor(listHoverBackground);
|
|
const listActiveSelectionBackgroundColor = theme.getColor(listActiveSelectionBackground);
|
|
const listFocusSelectionBackgroundColor = theme.getColor(listFocusAndSelectionBackground);
|
|
if (listHoverBackgroundColor) {
|
|
collector.addRule(`
|
|
.designer-component .slick-cell.isDragging {
|
|
background-color: ${listHoverBackgroundColor};
|
|
}
|
|
.designer-component .slick-reorder-proxy {
|
|
background: ${listActiveSelectionBackgroundColor};
|
|
opacity: 0.5;
|
|
}
|
|
.vs-dark .designer-component .slick-reorder-proxy {
|
|
opacity: 0.3;
|
|
}
|
|
.designer-component .slick-reorder-guide {
|
|
background: ${listFocusSelectionBackgroundColor};
|
|
opacity: 1;
|
|
}
|
|
`);
|
|
}
|
|
});
|