mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 09:35:38 -05:00
Table Designer: Move columns UI (#19154)
* format * added buttons and initial drag plugin * initial drag and drop working * add actions and taskbar * drag and drop bugfix and other changes * fix few issues * more changes * fix all move and insertion issues * PRcomments * fit and finish comments * remove dead code * bump sts * add style for object being dragged * add plugin to copyright filter * Add to eslintrc * fix drag contrast ratio * generalize logic for cell focus * demo feedback * feedback * add action state * feedback * remove unncecessary check * add move actions to context menu * change to const * fix bug with tables and fix drop color Co-authored-by: chgagnon <chgagnon@microsoft.com>
This commit is contained in:
@@ -16,7 +16,7 @@ import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/spl
|
||||
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 { ITableMouseEvent, 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';
|
||||
@@ -33,18 +33,24 @@ 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 { 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 { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { 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, IDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
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';
|
||||
|
||||
export interface IDesignerStyle {
|
||||
tabbedPanelStyles?: ITabbedPanelStyles;
|
||||
@@ -90,12 +96,13 @@ export class Designer extends Disposable implements IThemable {
|
||||
private _input: DesignerComponentInput;
|
||||
private _tableCellEditorFactory: TableCellEditorFactory;
|
||||
private _propertiesPane: DesignerPropertiesPane;
|
||||
private _buttons: Button[] = [];
|
||||
private _inputDisposable: DisposableStore;
|
||||
private _loadingTimeoutHandle: any;
|
||||
private _groupHeaders: HTMLElement[] = [];
|
||||
private _issuesView: DesignerIssuesTabPanelView;
|
||||
private _scriptEditorView: DesignerScriptEditorTabPanelView;
|
||||
private _taskbars: Taskbar[] = [];
|
||||
private _actionsMap: Map<Taskbar, DesignerTableAction[]> = new Map<Taskbar, DesignerTableAction[]>();
|
||||
private _onStyleChangeEventEmitter = new Emitter<void>();
|
||||
|
||||
constructor(private readonly _container: HTMLElement,
|
||||
@@ -103,7 +110,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IDialogService private readonly _dialogService: IDialogService,
|
||||
@IThemeService private readonly _themeService: IThemeService) {
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IContextMenuService private readonly _contextMenuService: IContextMenuService,) {
|
||||
super();
|
||||
this._tableCellEditorFactory = new TableCellEditorFactory(
|
||||
{
|
||||
@@ -209,6 +217,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
} else if (component instanceof TabbedPanel) {
|
||||
component.style(this._styles.tabbedPanelStyles);
|
||||
} else if (component instanceof Table) {
|
||||
this.removeTableSelectionStyles();
|
||||
component.style(this._styles.tableStyles);
|
||||
} else if (component instanceof Button) {
|
||||
component.style(this._styles.buttonStyles);
|
||||
@@ -219,6 +228,17 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
}
|
||||
|
||||
private removeTableSelectionStyles(): void {
|
||||
this._styles.tableStyles.listActiveSelectionBackground = undefined;
|
||||
this._styles.tableStyles.listActiveSelectionForeground = undefined;
|
||||
this._styles.tableStyles.listFocusAndSelectionBackground = undefined;
|
||||
this._styles.tableStyles.listFocusAndSelectionForeground = undefined;
|
||||
this._styles.tableStyles.listInactiveFocusBackground = undefined;
|
||||
this._styles.tableStyles.listInactiveFocusForeground = undefined;
|
||||
this._styles.tableStyles.listInactiveSelectionBackground = undefined;
|
||||
this._styles.tableStyles.listInactiveSelectionForeground = undefined;
|
||||
}
|
||||
|
||||
private styleGroupHeader(header: HTMLElement): void {
|
||||
if (this._styles.groupHeaderBackground) {
|
||||
header.style.backgroundColor = this._styles.groupHeaderBackground.toString();
|
||||
@@ -243,10 +263,6 @@ export class Designer extends Disposable implements IThemable {
|
||||
separatorBorder: styles.paneSeparator
|
||||
});
|
||||
|
||||
this._buttons.forEach((button) => {
|
||||
this.styleComponent(button);
|
||||
});
|
||||
|
||||
this._groupHeaders.forEach((header) => {
|
||||
this.styleGroupHeader(header);
|
||||
});
|
||||
@@ -304,14 +320,13 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
private clearUI(): void {
|
||||
this._buttons.forEach(button => button.dispose());
|
||||
this._buttons = [];
|
||||
this._componentMap.forEach(item => item.component.dispose());
|
||||
this._componentMap.clear();
|
||||
DOM.clearNode(this._topContentContainer);
|
||||
this._contentTabbedPanel.clearTabs();
|
||||
this._propertiesPane.clear();
|
||||
this._groupHeaders = [];
|
||||
this._taskbars.map(t => t.dispose());
|
||||
}
|
||||
|
||||
private initializeDesigner(): void {
|
||||
@@ -328,6 +343,27 @@ export class Designer extends Disposable implements IThemable {
|
||||
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;
|
||||
@@ -344,21 +380,8 @@ export class Designer extends Disposable implements IThemable {
|
||||
this.updateComponentValues();
|
||||
this.layoutTabbedPanel();
|
||||
}
|
||||
if (edit.type === DesignerEditType.Add) {
|
||||
// For tables in the main view, move focus to the first cell of the newly added row, and the properties pane will be showing the new object.
|
||||
if (edit.path.length === 1) {
|
||||
const propertyName = edit.path[0] as string;
|
||||
const tableData = this._input.viewModel[propertyName] as DesignerTableProperties;
|
||||
const table = this._componentMap.get(propertyName).component as Table<Slick.SlickData>;
|
||||
try {
|
||||
table.setActiveCell(tableData.data.length - 1, 0);
|
||||
}
|
||||
catch {
|
||||
// Ignore the slick grid error when setting active cell.
|
||||
}
|
||||
} else {
|
||||
this.updatePropertiesPane(this._propertiesPane.objectPath);
|
||||
}
|
||||
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);
|
||||
@@ -574,7 +597,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
}
|
||||
|
||||
private handleEdit(edit: DesignerEdit): void {
|
||||
public handleEdit(edit: DesignerEdit): void {
|
||||
if (this._supressEditProcessing) {
|
||||
return;
|
||||
}
|
||||
@@ -797,28 +820,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? '';
|
||||
}
|
||||
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
|
||||
if (tableProperties.canAddRows) {
|
||||
const buttonContainer = container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('.add-row-button-container'));
|
||||
const addNewText = tableProperties.labelForAddNewButton ?? localize('designer.newRowText', "Add New");
|
||||
const addRowButton = new Button(buttonContainer, {
|
||||
title: addNewText,
|
||||
secondary: true
|
||||
});
|
||||
addRowButton.onDidClick(() => {
|
||||
this.handleEdit({
|
||||
type: DesignerEditType.Add,
|
||||
path: propertyPath,
|
||||
source: view
|
||||
});
|
||||
});
|
||||
this.styleComponent(addRowButton);
|
||||
addRowButton.label = addNewText;
|
||||
addRowButton.icon = {
|
||||
id: `add-row-button new codicon`
|
||||
};
|
||||
addRowButton.ariaLabel = localize('designer.newRowButtonAriaLabel', "Add new row to '{0}' table", tableProperties.ariaLabel);
|
||||
this._buttons.push(addRowButton);
|
||||
}
|
||||
const taskbar = this.addTableTaskbar(container, tableProperties);
|
||||
const tableContainer = container.appendChild(DOM.$('.full-row'));
|
||||
const table = new Table(tableContainer, {
|
||||
dataProvider: new TableDataView()
|
||||
@@ -836,12 +838,55 @@ export class Designer extends Disposable implements IThemable {
|
||||
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.canInsertRows || tableProperties.canMoveRows) {
|
||||
// Add move context menu actions
|
||||
this._register(table.onContextMenu((e) => {
|
||||
this.openContextMenu(table, e, propertyPath, view, tableProperties);
|
||||
}));
|
||||
}
|
||||
if (tableProperties.canMoveRows) {
|
||||
// Add row move drag and drop
|
||||
const moveRowsPlugin = new RowMoveManager({
|
||||
cancelEditOnDrag: true,
|
||||
id: 'moveRow',
|
||||
iconCssClass: Codicon.grabber.classNames,
|
||||
title: localize('designer.moveRowText', 'Move Row'),
|
||||
width: 20,
|
||||
resizable: false,
|
||||
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;
|
||||
const columns = tableProperties.columns.map(propName => {
|
||||
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
|
||||
@@ -859,6 +904,7 @@ export class Designer extends Disposable implements IThemable {
|
||||
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),
|
||||
@@ -867,13 +913,14 @@ export class Designer extends Disposable implements IThemable {
|
||||
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 deleteRowColumn = new ButtonColumn({
|
||||
id: 'deleteRow',
|
||||
@@ -908,7 +955,10 @@ export class Designer extends Disposable implements IThemable {
|
||||
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) {
|
||||
@@ -925,12 +975,20 @@ export class Designer extends Disposable implements IThemable {
|
||||
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([]);
|
||||
table.grid.resetActiveCell();
|
||||
});
|
||||
|
||||
component = table;
|
||||
break;
|
||||
default:
|
||||
throw new Error(localize('tableDesigner.unknownComponentType', "The component type: {0} is not supported", componentDefinition.componentType));
|
||||
throw new Error(localize('designer.unknownComponentType', "The component type: {0} is not supported", componentDefinition.componentType));
|
||||
}
|
||||
componentMap.set(componentDefinition.propertyName, {
|
||||
defintion: componentDefinition,
|
||||
@@ -941,6 +999,78 @@ export class Designer extends Disposable implements IThemable {
|
||||
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>,
|
||||
event: ITableMouseEvent,
|
||||
propertyPath: DesignerPropertyPath,
|
||||
view: DesignerUIArea,
|
||||
tableProperties: DesignerTableProperties
|
||||
): void {
|
||||
const rowIndex = event.cell.row;
|
||||
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: () => event.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;
|
||||
@@ -979,3 +1109,27 @@ export class Designer extends Disposable implements IThemable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -187,6 +187,14 @@ export interface DesignerTableProperties extends ComponentProperties {
|
||||
* Whether user can remove rows from the table. The default value is true.
|
||||
*/
|
||||
canRemoveRows?: boolean;
|
||||
/**
|
||||
* Whether user can move rows from one index to another. The default value is false.
|
||||
*/
|
||||
canMoveRows?: boolean;
|
||||
/**
|
||||
* Whether user can insert rows at a given index to the table. The default value is false.
|
||||
*/
|
||||
canInsertRows?: boolean;
|
||||
/**
|
||||
* Whether to show confirmation when user removes a row. The default value is false.
|
||||
*/
|
||||
@@ -214,7 +222,8 @@ export interface DesignerTableComponentRowData {
|
||||
export enum DesignerEditType {
|
||||
Add = 0,
|
||||
Remove = 1,
|
||||
Update = 2
|
||||
Update = 2,
|
||||
Move = 3
|
||||
}
|
||||
|
||||
export interface DesignerEdit {
|
||||
|
||||
@@ -114,10 +114,13 @@
|
||||
width: fit-content;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 13px;
|
||||
padding-left: 17px;
|
||||
background-position: 2px center;
|
||||
}
|
||||
|
||||
.designer-component .add-row-button-container .actions-container {
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
|
||||
.designer-component .top-content-container .components-grid {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
@@ -152,3 +155,7 @@
|
||||
padding-top: 10px;
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.designer-component .codicon-grabber {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
194
src/sql/workbench/browser/designer/tableActions.ts
Normal file
194
src/sql/workbench/browser/designer/tableActions.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { Designer } from 'sql/workbench/browser/designer/designer';
|
||||
import { DesignerEditType, DesignerPropertyPath, DesignerTableProperties, DesignerUIArea } from 'sql/workbench/browser/designer/interfaces';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export interface DesignerTableActionContext {
|
||||
table: Table<Slick.SlickData>;
|
||||
path: DesignerPropertyPath;
|
||||
source: DesignerUIArea;
|
||||
selectedRow?: number;
|
||||
}
|
||||
|
||||
export class DesignerTableAction extends Action {
|
||||
|
||||
protected _table: Table<Slick.SlickData>;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
icon: string,
|
||||
protected needsRowSelection: boolean
|
||||
) {
|
||||
super(id, label, icon);
|
||||
}
|
||||
|
||||
|
||||
public set table(table: Table<Slick.SlickData>) {
|
||||
this._table = table;
|
||||
}
|
||||
|
||||
public updateState(row?: number) {
|
||||
if (row === undefined) {
|
||||
if (!this.needsRowSelection) {
|
||||
this.enabled = true;
|
||||
} else {
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AddRowAction extends DesignerTableAction {
|
||||
public static ID = 'designer.addRowAction';
|
||||
public static ICON = 'add-row-button new codicon';
|
||||
public static LABEL = localize('designer.addColumnAction', 'Add New');
|
||||
|
||||
constructor(
|
||||
private designer: Designer,
|
||||
tableProperties: DesignerTableProperties,
|
||||
) {
|
||||
super(AddRowAction.ID, tableProperties.labelForAddNewButton || AddRowAction.LABEL, AddRowAction.ICON, false);
|
||||
this.designer = designer;
|
||||
this._tooltip = localize('designer.newRowButtonAriaLabel', "Add new row to '{0}' table", tableProperties.ariaLabel);
|
||||
}
|
||||
|
||||
public override async run(context: DesignerTableActionContext): Promise<void> {
|
||||
const lastIndex = context.table.getData().getItems().length;
|
||||
return new Promise((resolve) => {
|
||||
this.designer.handleEdit({
|
||||
type: DesignerEditType.Add,
|
||||
path: [...context.path, lastIndex],
|
||||
source: context.source,
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveRowUpAction extends DesignerTableAction {
|
||||
public static ID = 'designer.moveRowUpAction';
|
||||
public static ICON = 'move-row-up-button arrow-up codicon';
|
||||
public static LABEL = localize('designer.moveRowUpAction', 'Move Up');
|
||||
|
||||
constructor(private designer: Designer) {
|
||||
super(MoveRowUpAction.ID, MoveRowUpAction.LABEL, MoveRowUpAction.ICON, true);
|
||||
this.designer = designer;
|
||||
this._tooltip = localize('designer.moveRowUpButtonAriaLabel', "Move selected row up one position");
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public override async run(context: DesignerTableActionContext): Promise<void> {
|
||||
let rowIndex = context.selectedRow ?? context.table.getSelectedRows()[0];
|
||||
if (rowIndex - 1 < 0) {
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
this.designer.handleEdit({
|
||||
type: DesignerEditType.Move,
|
||||
path: [...context.path, rowIndex],
|
||||
source: context.source,
|
||||
value: rowIndex - 1
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public override updateState(row?: number): void {
|
||||
if (row === 0) {
|
||||
this.enabled = false;
|
||||
} else {
|
||||
this.enabled = true;
|
||||
}
|
||||
super.updateState(row);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveRowDownAction extends DesignerTableAction {
|
||||
public static ID = 'designer.moveRowDownAction';
|
||||
public static ICON = 'move-row-down-button arrow-down codicon';
|
||||
public static LABEL = localize('designer.moveRowDownAction', 'Move Down');
|
||||
|
||||
constructor(private designer: Designer) {
|
||||
super(MoveRowDownAction.ID, MoveRowDownAction.LABEL, MoveRowDownAction.ICON, true);
|
||||
this.designer = designer;
|
||||
this._tooltip = localize('designer.moveRowDownButtonAriaLabel', "Move selected row down one position");
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
public override async run(context: DesignerTableActionContext): Promise<void> {
|
||||
let rowIndex = context.selectedRow ?? context.table.getSelectedRows()[0];
|
||||
const tableData = context.table.getData().getItems();
|
||||
if (rowIndex + 1 >= tableData.length) {
|
||||
return;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
this.designer.handleEdit({
|
||||
type: DesignerEditType.Move,
|
||||
path: [...context.path, rowIndex],
|
||||
source: context.source,
|
||||
value: rowIndex + 1
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
public override updateState(row?: number): void {
|
||||
super.updateState(row);
|
||||
if (row === this._table.getData().getLength() - 1) {
|
||||
this.enabled = false;
|
||||
} else {
|
||||
this.enabled = true;
|
||||
}
|
||||
super.updateState(row);
|
||||
}
|
||||
}
|
||||
|
||||
export class InsertBeforeSelectedRowAction extends Action {
|
||||
public static ID = 'designer.insertBeforeSelectedRow';
|
||||
public static LABEL = localize('designer.insertBeforeSelectedRow', 'Insert before');
|
||||
|
||||
constructor(private designer: Designer) {
|
||||
super(InsertBeforeSelectedRowAction.ID, InsertBeforeSelectedRowAction.LABEL, 'insertBeforeSelectedRow', true);
|
||||
this.designer = designer;
|
||||
}
|
||||
|
||||
public override async run(context: DesignerTableActionContext): Promise<void> {
|
||||
const rowIndex = context.selectedRow;
|
||||
return new Promise((resolve) => {
|
||||
this.designer.handleEdit({
|
||||
type: DesignerEditType.Add,
|
||||
path: [...context.path, rowIndex],
|
||||
source: context.source
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class InsertAfterSelectedRowAction extends Action {
|
||||
public static ID = 'designer.insertAfterSelectedColumn';
|
||||
public static LABEL = localize('designer.insertAfterSelectedColumn', 'Insert after');
|
||||
|
||||
constructor(private designer: Designer) {
|
||||
super(InsertAfterSelectedRowAction.ID, InsertAfterSelectedRowAction.LABEL, 'insertAfterSelectedColumn', true);
|
||||
}
|
||||
|
||||
public override async run(context: DesignerTableActionContext): Promise<void> {
|
||||
const rowIndex = context.selectedRow;
|
||||
return new Promise((resolve) => {
|
||||
this.designer.handleEdit({
|
||||
type: DesignerEditType.Add,
|
||||
path: [...context.path, rowIndex + 1],
|
||||
source: context.source
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user