From f195ebe3d2efb3b19e5b1e4bc38c8b184ada2268 Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 20 Aug 2021 13:15:06 +0300 Subject: [PATCH] feat: CB-1151 rows adition and deletion --- .../DataGridContextMenuCellEditingService.ts | 70 +++++++++++++------ .../src/locales/en.ts | 1 + .../src/locales/ru.ts | 1 + .../Actions/DatabaseEditAction.ts | 10 ++- .../Actions/Document/DocumentEditAction.ts | 16 +++-- .../Actions/IDatabaseDataEditAction.ts | 5 +- .../Actions/ResultSet/ResultSetEditAction.ts | 65 ++++++++++++----- .../ResultSet/ResultSetFormatAction.ts | 34 +++++---- .../ResultSet/ResultSetSelectAction.ts | 35 ++++++++-- .../Actions/ResultSet/ResultSetViewAction.ts | 13 +++- 10 files changed, 179 insertions(+), 71 deletions(-) diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts index ab88abb5fb..93cc5a6953 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts @@ -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); }, } ); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts b/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts index 17166d3f60..681fb190ec 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts @@ -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'], diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts b/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts index 9140cac1a2..232ef249ac 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts @@ -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', 'Фильтры'], diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts index a9e59f884e..fde0f0d403 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts @@ -22,16 +22,24 @@ export abstract class DatabaseEditAction>; + protected features: Array; constructor(source: IDatabaseDataSource, 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; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/Document/DocumentEditAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/Document/DocumentEditAction.ts index 8767056d3b..dec5d06b7c 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/Document/DocumentEditAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/Document/DocumentEditAction.ts @@ -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; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataEditAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataEditAction.ts index 7daac9f241..f790e25361 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataEditAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataEditAction.ts @@ -28,8 +28,11 @@ export interface IDatabaseDataEditAction>; 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; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts index 48c8ce0e0d..e8fbbfc7b3 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts @@ -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: 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, diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts index df95566ab0..9a5adcd879 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts @@ -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, 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; 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, 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 { + 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)); } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts index 15567816fe..b96d15ebef 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts @@ -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 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;