mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -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:
@@ -1151,6 +1151,7 @@
|
|||||||
"src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts",
|
"src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts",
|
||||||
"src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts",
|
"src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts",
|
||||||
"src/sql/base/browser/ui/table/plugins/rowDetailView.ts",
|
"src/sql/base/browser/ui/table/plugins/rowDetailView.ts",
|
||||||
|
"src/sql/base/browser/ui/table/plugins/rowMoveManager.plugin.ts",
|
||||||
"src/sql/base/browser/ui/table/plugins/rowSelectionModel.plugin.ts",
|
"src/sql/base/browser/ui/table/plugins/rowSelectionModel.plugin.ts",
|
||||||
"src/sql/workbench/services/notebook/browser/outputs/factories.ts",
|
"src/sql/workbench/services/notebook/browser/outputs/factories.ts",
|
||||||
"src/sql/workbench/services/notebook/browser/outputs/mimemodel.ts",
|
"src/sql/workbench/services/notebook/browser/outputs/mimemodel.ts",
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ module.exports.copyrightFilter = [
|
|||||||
'!src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts',
|
'!src/sql/base/browser/ui/table/plugins/checkboxSelectColumn.plugin.ts',
|
||||||
'!src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts',
|
'!src/sql/base/browser/ui/table/plugins/cellSelectionModel.plugin.ts',
|
||||||
'!src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts',
|
'!src/sql/base/browser/ui/table/plugins/autoSizeColumns.plugin.ts',
|
||||||
|
'!src/sql/base/browser/ui/table/plugins/rowMoveManager.plugin.ts',
|
||||||
'!src/sql/workbench/services/notebook/browser/outputs/sanitizer.ts',
|
'!src/sql/workbench/services/notebook/browser/outputs/sanitizer.ts',
|
||||||
'!src/sql/workbench/contrib/notebook/browser/outputs/renderers.ts',
|
'!src/sql/workbench/contrib/notebook/browser/outputs/renderers.ts',
|
||||||
'!src/sql/workbench/services/notebook/browser/outputs/tableRenderers.ts',
|
'!src/sql/workbench/services/notebook/browser/outputs/tableRenderers.ts',
|
||||||
|
|||||||
14
src/sql/azdata.proposed.d.ts
vendored
14
src/sql/azdata.proposed.d.ts
vendored
@@ -1015,6 +1015,14 @@ declare module 'azdata' {
|
|||||||
* Whether user can remove rows from the table. The default value is true.
|
* Whether user can remove rows from the table. The default value is true.
|
||||||
*/
|
*/
|
||||||
canRemoveRows?: boolean;
|
canRemoveRows?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether user can move rows from one index to another. The default value is true.
|
||||||
|
*/
|
||||||
|
canMoveRows?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether user can insert rows at a given index to the table. The default value is true.
|
||||||
|
*/
|
||||||
|
canInsertRows?: boolean;
|
||||||
/**
|
/**
|
||||||
* Whether to show confirmation when user removes a row. The default value is false.
|
* Whether to show confirmation when user removes a row. The default value is false.
|
||||||
*/
|
*/
|
||||||
@@ -1081,7 +1089,11 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* Update a property.
|
* Update a property.
|
||||||
*/
|
*/
|
||||||
Update = 2
|
Update = 2,
|
||||||
|
/**
|
||||||
|
* Change the position of an item in the collection.
|
||||||
|
*/
|
||||||
|
Move = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
175
src/sql/base/browser/ui/table/plugins/rowMoveManager.plugin.ts
Normal file
175
src/sql/base/browser/ui/table/plugins/rowMoveManager.plugin.ts
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
// Adopted and converted to typescript from https://github.com/mleibman/SlickGrid/blob/gh-pages/plugins/slick.rowmovemanager.js
|
||||||
|
// heavily modified
|
||||||
|
|
||||||
|
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';
|
||||||
|
import { BaseClickableColumn, ClickableColumnOptions, IconColumnOptions } from 'sql/base/browser/ui/table/plugins/tableColumn';
|
||||||
|
import { mixin } from 'vs/base/common/objects';
|
||||||
|
|
||||||
|
const defaultOptions: IRowMoveManagerOptions = {
|
||||||
|
cancelEditOnDrag: false
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IRowMoveManagerOptions extends IconColumnOptions, ClickableColumnOptions, Slick.Column<Slick.SlickData> {
|
||||||
|
cancelEditOnDrag?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RowMoveOnDragEventArgs {
|
||||||
|
selectionProxy?: JQuery<HTMLElement>;
|
||||||
|
guide?: JQuery<HTMLElement>;
|
||||||
|
selectedRows?: number[];
|
||||||
|
insertBefore?: number;
|
||||||
|
canMove?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RowMoveOnDragEventData {
|
||||||
|
rows?: number[];
|
||||||
|
insertBefore: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapper interfaces for drag arguments to support selection
|
||||||
|
export interface OnRowMoveDragInitEventArgs<T extends Slick.SlickData> extends Slick.OnDragInitEventArgs<T>, RowMoveOnDragEventArgs { }
|
||||||
|
export interface OnRowMoveDragStartEventArgs<T extends Slick.SlickData> extends Slick.OnDragStartEventArgs<T>, RowMoveOnDragEventArgs { }
|
||||||
|
export interface OnRowMoveDragEventArgs<T extends Slick.SlickData> extends Slick.OnDragEventArgs<T>, RowMoveOnDragEventArgs { }
|
||||||
|
export interface OnRowMoveDragEndEventArgs<T extends Slick.SlickData> extends Slick.OnDragEndEventArgs<T>, RowMoveOnDragEventArgs { }
|
||||||
|
|
||||||
|
export class RowMoveManager<T extends Slick.SlickData> extends BaseClickableColumn<T> {
|
||||||
|
|
||||||
|
private _canvas: HTMLCanvasElement;
|
||||||
|
private _dragging: boolean;
|
||||||
|
|
||||||
|
public onBeforeMoveRows: Slick.Event<any> = new Slick.Event();
|
||||||
|
public onMoveRows: Slick.Event<any> = new Slick.Event();
|
||||||
|
|
||||||
|
constructor(private options: IRowMoveManagerOptions) {
|
||||||
|
super(options);
|
||||||
|
this.options = mixin(options, defaultOptions, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get definition(): Slick.Column<T> {
|
||||||
|
return {
|
||||||
|
id: this.options.id || this.options.title || this.options.field,
|
||||||
|
width: this.options.width ?? 26,
|
||||||
|
name: this.options.name,
|
||||||
|
resizable: this.options.resizable,
|
||||||
|
selectable: false,
|
||||||
|
behavior: this.options.behavior,
|
||||||
|
cssClass: this.options.iconCssClass
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override init(grid: Slick.Grid<T>) {
|
||||||
|
this._grid = grid;
|
||||||
|
this._grid.setSelectionModel(new RowSelectionModel());
|
||||||
|
this._canvas = this._grid.getCanvasNode();
|
||||||
|
this._handler
|
||||||
|
.subscribe(this._grid.onDragInit, (e: DOMEvent, data: OnRowMoveDragInitEventArgs<T>) => this.onDragInit(e as MouseEvent, data))
|
||||||
|
.subscribe(this._grid.onDragStart, (e: DOMEvent, data: OnRowMoveDragStartEventArgs<T>) => this.onDragStart(e as MouseEvent, data))
|
||||||
|
.subscribe(this._grid.onDrag, (e: DOMEvent, data: OnRowMoveDragEventArgs<T>) => this.onDrag(e as MouseEvent, data))
|
||||||
|
.subscribe(this._grid.onDragEnd, (e: DOMEvent, data: OnRowMoveDragEndEventArgs<T>) => this.onDragEnd(e as MouseEvent, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragInit(e: MouseEvent, data: OnRowMoveDragInitEventArgs<T>) {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragStart(e: MouseEvent, data: OnRowMoveDragStartEventArgs<T>) {
|
||||||
|
const cell = this._grid.getCellFromEvent(e);
|
||||||
|
const highlightStyle = {};
|
||||||
|
const columns = this._grid.getColumns();
|
||||||
|
highlightStyle[cell.row] = {};
|
||||||
|
columns.forEach((c) => {
|
||||||
|
highlightStyle[cell.row][c.id] = 'isDragging';
|
||||||
|
});
|
||||||
|
this._grid.setCellCssStyles('isDragging', highlightStyle);
|
||||||
|
|
||||||
|
if (this.options.cancelEditOnDrag && this._grid.getEditorLock().isActive()) {
|
||||||
|
this._grid.getEditorLock().cancelCurrentEdit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._grid.getEditorLock().isActive() || !/move|selectAndMove/.test(this._grid.getColumns()[cell.cell].behavior)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dragging = true;
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
let selectedRows = this._grid.getSelectedRows();
|
||||||
|
|
||||||
|
if (selectedRows.length === 0 || jQuery.inArray(cell.row, selectedRows) === -1) {
|
||||||
|
selectedRows = [cell.row];
|
||||||
|
this._grid.setSelectedRows(selectedRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowHeight = this._grid.getOptions().rowHeight;
|
||||||
|
|
||||||
|
data.selectedRows = selectedRows;
|
||||||
|
|
||||||
|
data.selectionProxy = jQuery('<div class="slick-reorder-proxy"/>')
|
||||||
|
.css('position', 'absolute')
|
||||||
|
.css('zIndex', '99999')
|
||||||
|
.css('width', jQuery(this._canvas).innerWidth())
|
||||||
|
.css('height', rowHeight * selectedRows.length)
|
||||||
|
.appendTo(this._canvas);
|
||||||
|
|
||||||
|
data.guide = jQuery('<div class="slick-reorder-guide"/>')
|
||||||
|
.css('position', 'absolute')
|
||||||
|
.css('zIndex', '99998')
|
||||||
|
.css('width', jQuery(this._canvas).innerWidth())
|
||||||
|
.css('top', -1000)
|
||||||
|
.appendTo(this._canvas);
|
||||||
|
|
||||||
|
data.insertBefore = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDrag(e: MouseEvent, data: OnRowMoveDragEventArgs<T>) {
|
||||||
|
if (!this._dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
const top = e.pageY - jQuery(this._canvas).offset().top;
|
||||||
|
data.selectionProxy.css('top', top - 5);
|
||||||
|
|
||||||
|
const insertBefore = Math.max(0, Math.min(Math.round(top / this._grid.getOptions().rowHeight), this._grid.getDataLength()));
|
||||||
|
if (insertBefore !== data.insertBefore) {
|
||||||
|
const eventData: RowMoveOnDragEventData = {
|
||||||
|
'rows': data.selectedRows,
|
||||||
|
'insertBefore': insertBefore
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onBeforeMoveRows.notify(eventData) === false) {
|
||||||
|
data.guide.css('top', -1000);
|
||||||
|
data.canMove = false;
|
||||||
|
} else {
|
||||||
|
data.guide.css('top', insertBefore * this._grid.getOptions().rowHeight);
|
||||||
|
data.canMove = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.insertBefore = insertBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDragEnd(e: MouseEvent, data: OnRowMoveDragEndEventArgs<T>) {
|
||||||
|
if (!this._dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._dragging = false;
|
||||||
|
this._grid.removeCellCssStyles('isDragging');
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
data.guide.remove();
|
||||||
|
data.selectionProxy.remove();
|
||||||
|
|
||||||
|
if (data.canMove) {
|
||||||
|
const eventData: RowMoveOnDragEventData = {
|
||||||
|
'rows': data.selectedRows,
|
||||||
|
'insertBefore': data.insertBefore
|
||||||
|
};
|
||||||
|
this.onMoveRows.notify(eventData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,8 +25,8 @@ export interface ClickableColumnOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseClickableColumn<T extends Slick.SlickData> implements Slick.Plugin<T>, TableColumn<T> {
|
export abstract class BaseClickableColumn<T extends Slick.SlickData> implements Slick.Plugin<T>, TableColumn<T> {
|
||||||
private _handler = new Slick.EventHandler();
|
protected _handler = new Slick.EventHandler();
|
||||||
private _grid!: Slick.Grid<T>;
|
protected _grid!: Slick.Grid<T>;
|
||||||
private _onClick = new Emitter<TableCellClickEventArgs<T>>();
|
private _onClick = new Emitter<TableCellClickEventArgs<T>>();
|
||||||
public onClick = this._onClick.event;
|
public onClick = this._onClick.event;
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
|
|||||||
private _onColumnResize = new Emitter<void>();
|
private _onColumnResize = new Emitter<void>();
|
||||||
public readonly onColumnResize = this._onColumnResize.event;
|
public readonly onColumnResize = this._onColumnResize.event;
|
||||||
|
|
||||||
|
private _onBlur = new Emitter<void>();
|
||||||
|
public readonly onBlur = this._onBlur.event;
|
||||||
|
|
||||||
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
|
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
|
||||||
super();
|
super();
|
||||||
if (!configuration || !configuration.dataProvider || isArray(configuration.dataProvider)) {
|
if (!configuration || !configuration.dataProvider || isArray(configuration.dataProvider)) {
|
||||||
@@ -84,6 +87,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
|
|||||||
clearTimeout(this._classChangeTimeout);
|
clearTimeout(this._classChangeTimeout);
|
||||||
this._classChangeTimeout = setTimeout(() => {
|
this._classChangeTimeout = setTimeout(() => {
|
||||||
this._container.classList.remove('focused');
|
this._container.classList.remove('focused');
|
||||||
|
this._onBlur.fire();
|
||||||
}, 100);
|
}, 100);
|
||||||
}, true));
|
}, true));
|
||||||
|
|
||||||
|
|||||||
@@ -1022,7 +1022,8 @@ export namespace designers {
|
|||||||
export enum DesignerEditType {
|
export enum DesignerEditType {
|
||||||
Add = 0,
|
Add = 0,
|
||||||
Remove = 1,
|
Remove = 1,
|
||||||
Update = 2
|
Update = 2,
|
||||||
|
Move = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TableIcon {
|
export enum TableIcon {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/spl
|
|||||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||||
import 'vs/css!./media/designer';
|
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 { IThemable } from 'vs/base/common/styler';
|
||||||
import { Checkbox, ICheckboxStyles } from 'sql/base/browser/ui/checkbox/checkbox';
|
import { Checkbox, ICheckboxStyles } from 'sql/base/browser/ui/checkbox/checkbox';
|
||||||
import { Table } from 'sql/base/browser/ui/table/table';
|
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 { Color } from 'vs/base/common/color';
|
||||||
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinner';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { DesignerIssuesTabPanelView } from 'sql/workbench/browser/designer/designerIssuesTabPanelView';
|
import { DesignerIssuesTabPanelView } from 'sql/workbench/browser/designer/designerIssuesTabPanelView';
|
||||||
import { DesignerScriptEditorTabPanelView } from 'sql/workbench/browser/designer/designerScriptEditorTabPanelView';
|
import { DesignerScriptEditorTabPanelView } from 'sql/workbench/browser/designer/designerScriptEditorTabPanelView';
|
||||||
import { DesignerPropertyPathValidator } from 'sql/workbench/browser/designer/designerPropertyPathValidator';
|
import { DesignerPropertyPathValidator } from 'sql/workbench/browser/designer/designerPropertyPathValidator';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry';
|
import { listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||||
import { layoutDesignerTable, TableHeaderRowHeight, TableRowHeight } from 'sql/workbench/browser/designer/designerTableUtil';
|
import { layoutDesignerTable, TableHeaderRowHeight, TableRowHeight } from 'sql/workbench/browser/designer/designerTableUtil';
|
||||||
import { Dropdown, IDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
import { Dropdown, IDropdownStyles } from 'sql/base/browser/ui/editableDropdown/browser/dropdown';
|
||||||
import { IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
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 {
|
export interface IDesignerStyle {
|
||||||
tabbedPanelStyles?: ITabbedPanelStyles;
|
tabbedPanelStyles?: ITabbedPanelStyles;
|
||||||
@@ -90,12 +96,13 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
private _input: DesignerComponentInput;
|
private _input: DesignerComponentInput;
|
||||||
private _tableCellEditorFactory: TableCellEditorFactory;
|
private _tableCellEditorFactory: TableCellEditorFactory;
|
||||||
private _propertiesPane: DesignerPropertiesPane;
|
private _propertiesPane: DesignerPropertiesPane;
|
||||||
private _buttons: Button[] = [];
|
|
||||||
private _inputDisposable: DisposableStore;
|
private _inputDisposable: DisposableStore;
|
||||||
private _loadingTimeoutHandle: any;
|
private _loadingTimeoutHandle: any;
|
||||||
private _groupHeaders: HTMLElement[] = [];
|
private _groupHeaders: HTMLElement[] = [];
|
||||||
private _issuesView: DesignerIssuesTabPanelView;
|
private _issuesView: DesignerIssuesTabPanelView;
|
||||||
private _scriptEditorView: DesignerScriptEditorTabPanelView;
|
private _scriptEditorView: DesignerScriptEditorTabPanelView;
|
||||||
|
private _taskbars: Taskbar[] = [];
|
||||||
|
private _actionsMap: Map<Taskbar, DesignerTableAction[]> = new Map<Taskbar, DesignerTableAction[]>();
|
||||||
private _onStyleChangeEventEmitter = new Emitter<void>();
|
private _onStyleChangeEventEmitter = new Emitter<void>();
|
||||||
|
|
||||||
constructor(private readonly _container: HTMLElement,
|
constructor(private readonly _container: HTMLElement,
|
||||||
@@ -103,7 +110,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
@IContextViewService private readonly _contextViewProvider: IContextViewService,
|
||||||
@INotificationService private readonly _notificationService: INotificationService,
|
@INotificationService private readonly _notificationService: INotificationService,
|
||||||
@IDialogService private readonly _dialogService: IDialogService,
|
@IDialogService private readonly _dialogService: IDialogService,
|
||||||
@IThemeService private readonly _themeService: IThemeService) {
|
@IThemeService private readonly _themeService: IThemeService,
|
||||||
|
@IContextMenuService private readonly _contextMenuService: IContextMenuService,) {
|
||||||
super();
|
super();
|
||||||
this._tableCellEditorFactory = new TableCellEditorFactory(
|
this._tableCellEditorFactory = new TableCellEditorFactory(
|
||||||
{
|
{
|
||||||
@@ -209,6 +217,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
} else if (component instanceof TabbedPanel) {
|
} else if (component instanceof TabbedPanel) {
|
||||||
component.style(this._styles.tabbedPanelStyles);
|
component.style(this._styles.tabbedPanelStyles);
|
||||||
} else if (component instanceof Table) {
|
} else if (component instanceof Table) {
|
||||||
|
this.removeTableSelectionStyles();
|
||||||
component.style(this._styles.tableStyles);
|
component.style(this._styles.tableStyles);
|
||||||
} else if (component instanceof Button) {
|
} else if (component instanceof Button) {
|
||||||
component.style(this._styles.buttonStyles);
|
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 {
|
private styleGroupHeader(header: HTMLElement): void {
|
||||||
if (this._styles.groupHeaderBackground) {
|
if (this._styles.groupHeaderBackground) {
|
||||||
header.style.backgroundColor = this._styles.groupHeaderBackground.toString();
|
header.style.backgroundColor = this._styles.groupHeaderBackground.toString();
|
||||||
@@ -243,10 +263,6 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
separatorBorder: styles.paneSeparator
|
separatorBorder: styles.paneSeparator
|
||||||
});
|
});
|
||||||
|
|
||||||
this._buttons.forEach((button) => {
|
|
||||||
this.styleComponent(button);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._groupHeaders.forEach((header) => {
|
this._groupHeaders.forEach((header) => {
|
||||||
this.styleGroupHeader(header);
|
this.styleGroupHeader(header);
|
||||||
});
|
});
|
||||||
@@ -304,14 +320,13 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private clearUI(): void {
|
private clearUI(): void {
|
||||||
this._buttons.forEach(button => button.dispose());
|
|
||||||
this._buttons = [];
|
|
||||||
this._componentMap.forEach(item => item.component.dispose());
|
this._componentMap.forEach(item => item.component.dispose());
|
||||||
this._componentMap.clear();
|
this._componentMap.clear();
|
||||||
DOM.clearNode(this._topContentContainer);
|
DOM.clearNode(this._topContentContainer);
|
||||||
this._contentTabbedPanel.clearTabs();
|
this._contentTabbedPanel.clearTabs();
|
||||||
this._propertiesPane.clear();
|
this._propertiesPane.clear();
|
||||||
this._groupHeaders = [];
|
this._groupHeaders = [];
|
||||||
|
this._taskbars.map(t => t.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeDesigner(): void {
|
private initializeDesigner(): void {
|
||||||
@@ -328,6 +343,27 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
this.restoreUIState();
|
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 {
|
private handleEditProcessedEvent(args: DesignerEditProcessedEventArgs): void {
|
||||||
const edit = args.edit;
|
const edit = args.edit;
|
||||||
this._supressEditProcessing = true;
|
this._supressEditProcessing = true;
|
||||||
@@ -344,21 +380,8 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
this.updateComponentValues();
|
this.updateComponentValues();
|
||||||
this.layoutTabbedPanel();
|
this.layoutTabbedPanel();
|
||||||
}
|
}
|
||||||
if (edit.type === DesignerEditType.Add) {
|
if (edit.type === DesignerEditType.Add || edit.type === DesignerEditType.Move) {
|
||||||
// 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.
|
this.handleCellFocusAfterAddOrMove(edit);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else if (edit.type === DesignerEditType.Update) {
|
} else if (edit.type === DesignerEditType.Update) {
|
||||||
// for edit, update the properties pane with new values of current object.
|
// for edit, update the properties pane with new values of current object.
|
||||||
this.updatePropertiesPane(this._propertiesPane.objectPath);
|
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) {
|
if (this._supressEditProcessing) {
|
||||||
return;
|
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 ?? '';
|
container.appendChild(DOM.$('.full-row')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? '';
|
||||||
}
|
}
|
||||||
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
|
const tableProperties = componentDefinition.componentProperties as DesignerTableProperties;
|
||||||
if (tableProperties.canAddRows) {
|
const taskbar = this.addTableTaskbar(container, tableProperties);
|
||||||
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 tableContainer = container.appendChild(DOM.$('.full-row'));
|
const tableContainer = container.appendChild(DOM.$('.full-row'));
|
||||||
const table = new Table(tableContainer, {
|
const table = new Table(tableContainer, {
|
||||||
dataProvider: new TableDataView()
|
dataProvider: new TableDataView()
|
||||||
@@ -836,12 +838,55 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
headerRowHeight: TableHeaderRowHeight,
|
headerRowHeight: TableHeaderRowHeight,
|
||||||
editorLock: new Slick.EditorLock()
|
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;
|
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);
|
const propertyDefinition = tableProperties.itemProperties.find(item => item.propertyName === propName);
|
||||||
switch (propertyDefinition.componentType) {
|
switch (propertyDefinition.componentType) {
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
const checkboxColumn = new CheckBoxColumn({
|
const checkboxColumn = new CheckBoxColumn({
|
||||||
|
id: index.toString(),
|
||||||
field: propertyDefinition.propertyName,
|
field: propertyDefinition.propertyName,
|
||||||
name: propertyDefinition.componentProperties.title,
|
name: propertyDefinition.componentProperties.title,
|
||||||
width: propertyDefinition.componentProperties.width as number
|
width: propertyDefinition.componentProperties.width as number
|
||||||
@@ -859,6 +904,7 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
case 'dropdown':
|
case 'dropdown':
|
||||||
const dropdownProperties = propertyDefinition.componentProperties as DropDownProperties;
|
const dropdownProperties = propertyDefinition.componentProperties as DropDownProperties;
|
||||||
return {
|
return {
|
||||||
|
id: index.toString(),
|
||||||
name: dropdownProperties.title,
|
name: dropdownProperties.title,
|
||||||
field: propertyDefinition.propertyName,
|
field: propertyDefinition.propertyName,
|
||||||
editor: this._tableCellEditorFactory.getDropdownEditorClass({ view: view, path: propertyPath }, dropdownProperties.values as string[], dropdownProperties.isEditable),
|
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:
|
default:
|
||||||
const inputProperties = propertyDefinition.componentProperties as InputBoxProperties;
|
const inputProperties = propertyDefinition.componentProperties as InputBoxProperties;
|
||||||
return {
|
return {
|
||||||
|
id: index.toString(),
|
||||||
name: inputProperties.title,
|
name: inputProperties.title,
|
||||||
field: propertyDefinition.propertyName,
|
field: propertyDefinition.propertyName,
|
||||||
editor: this._tableCellEditorFactory.getTextEditorClass({ view: view, path: propertyPath }, inputProperties.inputType),
|
editor: this._tableCellEditorFactory.getTextEditorClass({ view: view, path: propertyPath }, inputProperties.inputType),
|
||||||
width: inputProperties.width as number
|
width: inputProperties.width as number
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
if (tableProperties.canRemoveRows) {
|
if (tableProperties.canRemoveRows) {
|
||||||
const deleteRowColumn = new ButtonColumn({
|
const deleteRowColumn = new ButtonColumn({
|
||||||
id: 'deleteRow',
|
id: 'deleteRow',
|
||||||
@@ -908,7 +955,10 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
table.grid.onBeforeEditCell.subscribe((e, data): boolean => {
|
table.grid.onBeforeEditCell.subscribe((e, data): boolean => {
|
||||||
return data.item[data.column.field].enabled !== false;
|
return data.item[data.column.field].enabled !== false;
|
||||||
});
|
});
|
||||||
|
let currentTableActions = [];
|
||||||
|
if (taskbar) {
|
||||||
|
currentTableActions = this._actionsMap.get(taskbar);
|
||||||
|
}
|
||||||
table.grid.onActiveCellChanged.subscribe((e, data) => {
|
table.grid.onActiveCellChanged.subscribe((e, data) => {
|
||||||
if (view === 'TabsView' || view === 'TopContentView') {
|
if (view === 'TabsView' || view === 'TopContentView') {
|
||||||
if (data.row !== undefined) {
|
if (data.row !== undefined) {
|
||||||
@@ -925,12 +975,20 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
this._propertiesPane.updateDescription(componentDefinition);
|
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;
|
component = table;
|
||||||
break;
|
break;
|
||||||
default:
|
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, {
|
componentMap.set(componentDefinition.propertyName, {
|
||||||
defintion: componentDefinition,
|
defintion: componentDefinition,
|
||||||
@@ -941,6 +999,78 @@ export class Designer extends Disposable implements IThemable {
|
|||||||
return 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>,
|
||||||
|
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 {
|
private startLoading(message: string, timeout: number): void {
|
||||||
this._loadingTimeoutHandle = setTimeout(() => {
|
this._loadingTimeoutHandle = setTimeout(() => {
|
||||||
this._loadingSpinner.loadingMessage = message;
|
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.
|
* Whether user can remove rows from the table. The default value is true.
|
||||||
*/
|
*/
|
||||||
canRemoveRows?: boolean;
|
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.
|
* Whether to show confirmation when user removes a row. The default value is false.
|
||||||
*/
|
*/
|
||||||
@@ -214,7 +222,8 @@ export interface DesignerTableComponentRowData {
|
|||||||
export enum DesignerEditType {
|
export enum DesignerEditType {
|
||||||
Add = 0,
|
Add = 0,
|
||||||
Remove = 1,
|
Remove = 1,
|
||||||
Update = 2
|
Update = 2,
|
||||||
|
Move = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DesignerEdit {
|
export interface DesignerEdit {
|
||||||
|
|||||||
@@ -114,10 +114,13 @@
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: 13px;
|
background-size: 13px;
|
||||||
padding-left: 17px;
|
|
||||||
background-position: 2px center;
|
background-position: 2px center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.designer-component .add-row-button-container .actions-container {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.designer-component .top-content-container .components-grid {
|
.designer-component .top-content-container .components-grid {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
@@ -152,3 +155,7 @@
|
|||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
padding-left: 25px;
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -442,6 +442,8 @@ export class TableDesignerComponentInput implements DesignerComponentInput {
|
|||||||
itemProperties: this.addAdditionalTableProperties(options, columnProperties),
|
itemProperties: this.addAdditionalTableProperties(options, columnProperties),
|
||||||
objectTypeDisplayName: localize('tableDesigner.columnTypeName', "Column"),
|
objectTypeDisplayName: localize('tableDesigner.columnTypeName', "Column"),
|
||||||
canAddRows: options.canAddRows,
|
canAddRows: options.canAddRows,
|
||||||
|
canInsertRows: options.canInsertRows,
|
||||||
|
canMoveRows: options.canMoveRows,
|
||||||
canRemoveRows: options.canRemoveRows,
|
canRemoveRows: options.canRemoveRows,
|
||||||
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
removeRowConfirmationMessage: options.removeRowConfirmationMessage,
|
||||||
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
showRemoveRowConfirmation: options.showRemoveRowConfirmation,
|
||||||
|
|||||||
Reference in New Issue
Block a user