feat: CB-1151 rows adition and deletion

This commit is contained in:
Wroud
2021-08-20 13:15:06 +03:00
parent dab988cc0b
commit f195ebe3d2
10 changed files with 179 additions and 71 deletions
@@ -7,7 +7,7 @@
*/
import { injectable } from '@cloudbeaver/core-di';
import { isBooleanValuePresentationAvailable, ResultSetDataAction, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction } from '@cloudbeaver/plugin-data-viewer';
import { isBooleanValuePresentationAvailable, ResultSetChangeType, ResultSetDataAction, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction } from '@cloudbeaver/plugin-data-viewer';
import { DataGridContextMenuService } from './DataGridContextMenuService';
@@ -28,42 +28,41 @@ export class DataGridContextMenuCellEditingService {
this.dataGridContextMenuService.getMenuToken(),
{
id: this.getMenuEditingToken(),
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
isHidden(context) {
const format = context.data.model.source.getAction(context.data.resultIndex, ResultSetFormatAction);
return format.isReadOnly(context.data.key)
|| context.data.model.isDisabled(context.data.resultIndex)
|| context.data.model.isReadonly();
},
order: 4,
title: 'data_grid_table_editing',
icon: 'edit',
isPanel: true,
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
isHidden(context) {
return context.data.model.isDisabled(context.data.resultIndex)
|| context.data.model.isReadonly();
},
}
);
this.dataGridContextMenuService.add(
this.getMenuEditingToken(),
{
id: 'open_inline_editor',
order: 0,
title: 'data_grid_table_editing_open_inline_editor',
icon: 'edit',
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
isHidden(context) {
const format = context.data.model.source.getAction(context.data.resultIndex, ResultSetFormatAction);
const view = context.data.model.source.getAction(context.data.resultIndex, ResultSetViewAction);
const cellValue = view.getCellValue(context.data.key);
const column = view.getColumn(context.data.key.column);
if (!column || cellValue === undefined) {
if (!column || cellValue === undefined || format.isReadOnly(context.data.key)) {
return true;
}
return isBooleanValuePresentationAvailable(cellValue, column);
},
order: 0,
title: 'data_grid_table_editing_open_inline_editor',
icon: 'edit',
onClick(context) {
context.data.spreadsheetActions.edit(context.data.key);
},
@@ -73,6 +72,8 @@ export class DataGridContextMenuCellEditingService {
this.getMenuEditingToken(),
{
id: 'set_to_null',
order: 1,
title: 'data_grid_table_editing_set_to_null',
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
@@ -84,8 +85,6 @@ export class DataGridContextMenuCellEditingService {
return cellValue === undefined || data.getColumn(key.column)?.required || format.isNull(cellValue);
},
order: 1,
title: 'data_grid_table_editing_set_to_null',
onClick(context) {
context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction)
.set(context.data.key, null);
@@ -96,14 +95,14 @@ export class DataGridContextMenuCellEditingService {
this.getMenuEditingToken(),
{
id: 'row_add',
order: 5,
title: 'data_grid_table_editing_row_add',
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
order: 5,
title: 'data_grid_table_editing_row_add',
onClick(context) {
const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction);
editor.add(context.data.key.row);
editor.addRow(context.data.key.row);
},
}
);
@@ -111,14 +110,41 @@ export class DataGridContextMenuCellEditingService {
this.getMenuEditingToken(),
{
id: 'row_delete',
order: 6,
title: 'data_grid_table_editing_row_delete',
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
order: 6,
title: 'data_grid_table_editing_row_delete',
isHidden(context) {
const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction);
const format = context.data.model.source.getAction(context.data.resultIndex, ResultSetFormatAction);
return (
format.isReadOnly(context.data.key)
|| editor.getElementState(context.data.key) === ResultSetChangeType.delete
);
},
onClick(context) {
const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction);
editor.delete(context.data.key.row);
editor.deleteRow(context.data.key.row);
},
}
);
this.dataGridContextMenuService.add(
this.getMenuEditingToken(),
{
id: 'row_revert',
order: 7,
title: 'data_grid_table_editing_row_revert',
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
isHidden(context) {
const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction);
return editor.getElementState(context.data.key) === null;
},
onClick(context) {
const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction);
editor.revert(context.data.key);
},
}
);
@@ -4,6 +4,7 @@ export default [
['data_grid_table_editing_open_inline_editor', 'Open inline editor'],
['data_grid_table_editing_row_add', 'Add row'],
['data_grid_table_editing_row_delete', 'Delete current row'],
['data_grid_table_editing_row_revert', 'Revert value'],
['data_grid_table_order', 'Sorting'],
['data_grid_table_open_value_panel', 'Show in value panel'],
['data_grid_table_filter', 'Filters'],
@@ -4,6 +4,7 @@ export default [
['data_grid_table_editing_open_inline_editor', 'Открыть встроенный редактор'],
['data_grid_table_editing_row_add', 'Добавить строку'],
['data_grid_table_editing_row_delete', 'Удалить текущую строку'],
['data_grid_table_editing_row_revert', 'Отменить изменение'],
['data_grid_table_order', 'Сортировка'],
['data_grid_table_open_value_panel', 'Показать в панели значений'],
['data_grid_table_filter', 'Фильтры'],
@@ -22,16 +22,24 @@ export abstract class DatabaseEditAction<TKey, TValue, TResult extends IDatabase
static dataFormat: ResultDataFormat | null = null;
readonly action: IExecutor<IDatabaseDataEditActionData<TKey, TValue>>;
protected features: Array<keyof this>;
constructor(source: IDatabaseDataSource<any, TResult>, result: TResult) {
super(source, result);
this.action = new Executor();
this.features = [];
}
hasFeature(feature: keyof this): boolean {
return this.features.includes(feature);
}
abstract isEdited(): boolean;
abstract isElementEdited(key: TKey): boolean;
abstract set(key: TKey, value: TValue): void;
abstract get(key: TKey): TValue | undefined;
abstract set(key: TKey, value: TValue): void;
abstract add(key: TKey): void;
abstract delete(key: TKey): void;
abstract applyUpdate(result: TResult): void;
abstract revert(key: TKey): void;
abstract clear(): void;
@@ -50,6 +50,10 @@ export class DocumentEditAction
return !this.compare(value, this.get(key));
}
get(key: IDocumentElementKey): IDatabaseDataDocument | undefined {
return this.editedElements.get(key.index);
}
set(key: IDocumentElementKey, value: IDatabaseDataDocument, prevValue?: IDatabaseDataDocument): void {
if (!prevValue) {
prevValue = this.get(key);
@@ -74,6 +78,14 @@ export class DocumentEditAction
this.removeUnchanged(key);
}
add(key: IDocumentElementKey): void {
throw new Error('Not implemented');
}
delete(key: IDocumentElementKey): void {
throw new Error('Not implemented');
}
setData(key: IDocumentElementKey, value: string): void {
let previousValue = this.get(key);
@@ -95,10 +107,6 @@ export class DocumentEditAction
);
}
get(key: IDocumentElementKey): IDatabaseDataDocument | undefined {
return this.editedElements.get(key.index);
}
applyUpdate(result: IDatabaseResultSet): void {
let rowIndex = 0;
@@ -28,8 +28,11 @@ export interface IDatabaseDataEditAction<TKey, TValue, TResult extends IDatabase
readonly action: IExecutor<IDatabaseDataEditActionData<TKey, TValue>>;
isEdited: () => boolean;
isElementEdited: (key: TKey) => boolean;
set: (key: TKey, value: TValue) => void;
hasFeature: (feature: keyof this) => boolean;
get: (key: TKey) => TValue | undefined;
set: (key: TKey, value: TValue) => void;
add: (key: TKey) => void;
delete: (key: TKey) => void;
applyUpdate: (result: TResult) => void;
revert: (key: TKey) => void;
clear: () => void;
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable, observable } from 'mobx';
import { computed, makeObservable, observable, toJS } from 'mobx';
import { ResultDataFormat, SqlResultRow, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';
import { uuid } from '@cloudbeaver/core-utils';
@@ -50,6 +50,7 @@ export class ResultSetEditAction
super(source, result);
this.editorData = new Map();
this.data = this.getAction(ResultSetDataAction);
this.features = ['add', 'delete'];
makeObservable<this, 'editorData'>(this, {
editorData: observable.shallow,
@@ -67,8 +68,14 @@ export class ResultSetEditAction
get updates(): IResultSetUpdate[] {
return Array.from(this.editorData.values())
.sort((a, b) => {
if (a.row.index === b.row.index) {
return a.type - b.type;
if (a.type !== b.type) {
if (a.type === ResultSetChangeType.update) {
return -1;
}
if (b.type === ResultSetChangeType.update) {
return 1;
}
}
return a.row.index - b.row.index;
@@ -110,7 +117,7 @@ export class ResultSetEditAction
return null;
}
if (update.source === undefined || update.type === ResultSetChangeType.delete) {
if (update.source === undefined || update.type !== ResultSetChangeType.update) {
return update.type;
}
@@ -121,6 +128,12 @@ export class ResultSetEditAction
return null;
}
get(key: IResultSetElementKey): IResultSetValue | undefined {
return this.editorData
.get(ResultSetDataKeysUtils.serialize(key.row))
?.update[key.column.index];
}
set(key: IResultSetElementKey, value: IResultSetValue): void {
const update = this.getOrCreateUpdate(key.row, ResultSetChangeType.update);
const prevValue = update.source?.[key.column.index] as any;
@@ -150,13 +163,11 @@ export class ResultSetEditAction
this.removeEmptyUpdate(update);
}
get(key: IResultSetElementKey): IResultSetValue | undefined {
return this.editorData
.get(ResultSetDataKeysUtils.serialize(key.row))
?.update[key.column.index];
add(key: IResultSetElementKey): void {
this.addRow(key.row);
}
add(key: IResultSetRowKey, value?: IResultSetValue[]): void {
addRow(key: IResultSetRowKey, value?: IResultSetValue[]): void {
if (value === undefined) {
value = this.data.columns.map(() => null);
}
@@ -164,7 +175,11 @@ export class ResultSetEditAction
this.getOrCreateUpdate({ ...key, key: uuid() }, ResultSetChangeType.add, value);
}
delete(key: IResultSetRowKey): void {
delete(key: IResultSetElementKey): void {
this.deleteRow(key.row);
}
deleteRow(key: IResultSetRowKey): void {
const serializedKey = ResultSetDataKeysUtils.serialize(key);
const update = this.editorData.get(serializedKey);
@@ -181,25 +196,30 @@ export class ResultSetEditAction
let rowIndex = 0;
let shift = 0;
if (result.data?.rows?.length !== this.updates.length) {
console.warn('ResultSetEditAction: returned data differs from performed update');
}
for (const update of this.updates) {
switch (update.type) {
case ResultSetChangeType.update: {
if (update.source) {
const value = result.data?.rows?.[rowIndex];
const value = result.data?.rows?.[rowIndex];
if (value !== undefined) {
this.data.setRowValue(update.row, value, shift);
}
rowIndex++;
if (value !== undefined) {
this.data.setRowValue(update.row, value, shift);
}
rowIndex++;
break;
}
case ResultSetChangeType.add: {
const value = result.data?.rows?.[rowIndex];
if (value !== undefined) {
this.data.insertRow(update.row, value, shift);
}
rowIndex++;
shift++;
break;
@@ -223,9 +243,16 @@ export class ResultSetEditAction
return;
}
const prevValue = update.update[key.column.index];
const value = update.source?.[key.column.index] ?? null;
update.update[key.column.index] = value;
let prevValue: IResultSetValue | undefined;
let value: IResultSetValue | undefined;
if (update.type === ResultSetChangeType.delete) {
this.editorData.delete(row);
} else {
prevValue = update.update[key.column.index];
value = update.source?.[key.column.index] ?? null;
update.update[key.column.index] = value;
}
this.action.execute({
resultId: this.result.id,
@@ -15,6 +15,7 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { IDatabaseDataFormatAction } from '../IDatabaseDataFormatAction';
import type { IResultSetElementKey, IResultSetPartialKey } from './IResultSetDataKey';
import { isResultSetContentValue } from './isResultSetContentValue';
import { ResultSetChangeType, ResultSetEditAction } from './ResultSetEditAction';
import { ResultSetViewAction } from './ResultSetViewAction';
export type IResultSetValue =
@@ -26,10 +27,12 @@ export class ResultSetFormatAction extends DatabaseDataAction<any, IDatabaseResu
static dataFormat = ResultDataFormat.Resultset;
private view: ResultSetViewAction;
private edit: ResultSetEditAction;
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>, result: IDatabaseResultSet) {
super(source, result);
this.view = this.getAction(ResultSetViewAction);
this.edit = this.getAction(ResultSetEditAction);
}
getHeaders(): string[] {
@@ -59,27 +62,30 @@ export class ResultSetFormatAction extends DatabaseDataAction<any, IDatabaseResu
}
isReadOnly(key: IResultSetPartialKey): boolean {
let columnReadonly = false;
let cellReadonly = false;
let readonly = false;
if (key.column !== undefined) {
columnReadonly = this.view.getColumn(key.column)?.readOnly || false;
if (key.column) {
readonly = this.view.getColumn(key.column)?.readOnly || false;
}
if (key.row) {
const value = this.view.getCellValue(key as IResultSetElementKey);
if (!readonly && key.row) {
const value = this.view.getCellValue(key as IResultSetElementKey);
if (isResultSetContentValue(value)) {
cellReadonly = (
value.binary !== undefined
if (isResultSetContentValue(value)) {
readonly = (
value.binary !== undefined
|| value.contentLength !== value.text?.length
);
} else if (value !== null && typeof value === 'object') {
cellReadonly = true;
}
);
} else if (value !== null && typeof value === 'object') {
readonly = true;
}
}
return columnReadonly || cellReadonly;
if (!readonly && key.column && key.row) {
return this.edit.getElementState(key as IResultSetElementKey) === ResultSetChangeType.delete;
}
return readonly;
}
isNull(value: IResultSetValue): boolean {
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable, observable } from 'mobx';
import { action, computed, makeObservable, observable } from 'mobx';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
@@ -29,7 +29,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
readonly selectedElements: Map<string, IResultSetElementKey[]>;
private focusedElement: IResultSetElementKey | null;
private data: ResultSetViewAction;
private view: ResultSetViewAction;
get elements(): IResultSetElementKey[] {
return Array.from(this.selectedElements.values()).flat();
@@ -37,7 +37,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>, result: IDatabaseResultSet) {
super(source, result);
this.data = this.getAction(ResultSetViewAction);
this.view = this.getAction(ResultSetViewAction);
this.actions = new Executor();
this.selectedElements = new Map();
this.focusedElement = null;
@@ -55,7 +55,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
isElementSelected(key: IResultSetPartialKey): boolean {
if (key.row === undefined) {
for (const row of this.data.rowKeys) {
for (const row of this.view.rowKeys) {
if (!this.isElementSelected({ row, column: key.column })) {
return false;
}
@@ -74,7 +74,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
return this.isColumnSelected(row, key.column);
}
return row.length === this.data.columnKeys.length;
return row.length === this.view.columnKeys.length;
}
getFocusedElement(): IResultSetElementKey | null {
@@ -87,7 +87,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
set(key: IResultSetPartialKey, selected: boolean, silent?: boolean): void {
if (key.row === undefined) {
for (const row of this.data.rowKeys) {
for (const row of this.view.rowKeys) {
this.set({ row, column: key.column }, selected, true);
}
@@ -103,7 +103,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
}
if (key.column === undefined) {
for (const column of this.data.columnKeys) {
for (const column of this.view.columnKeys) {
this.set({ row: key.row, column }, selected, true);
}
if (!silent) {
@@ -162,6 +162,27 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
});
}
updateResult(): void {
action(() => {
if (this.focusedElement && !this.view.has(this.focusedElement)) {
this.focusedElement = null;
}
const removeKeys: string[] = [];
for (const [key, rowSelection] of this.selectedElements) {
const element = rowSelection[0];
if (element && !this.view.has(element)) {
removeKeys.push(key);
}
}
for (const key of removeKeys) {
this.selectedElements.delete(key);
}
});
}
private isColumnSelected(list: IResultSetElementKey[], key: IResultSetColumnKey) {
return list.some(selected => ResultSetDataKeysUtils.isEqual(selected.column, key));
}
@@ -19,6 +19,7 @@ import type { IResultSetContentValue } from './IResultSetContentValue';
import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey';
import { isResultSetContentValue } from './isResultSetContentValue';
import { ResultSetDataAction } from './ResultSetDataAction';
import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils';
import { ResultSetEditAction } from './ResultSetEditAction';
import type { IResultSetValue } from './ResultSetFormatAction';
@@ -62,11 +63,17 @@ export class ResultSetViewAction extends DatabaseDataAction<any, IDatabaseResult
});
}
has(cell: IResultSetElementKey): boolean {
if (!this.columnKeys.some(column => ResultSetDataKeysUtils.isEqual(column, cell.column))) {
return false;
}
return !this.rowKeys.some(row => ResultSetDataKeysUtils.isEqual(row, cell.row));
}
getCellValue(cell: IResultSetElementKey): IResultSetValue | undefined {
if (
cell.row === undefined
|| cell.column === undefined
|| cell.row.index >= this.rows.length
cell.row.index >= this.rows.length
|| cell.column.index >= this.columns.length
) {
return undefined;