mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
fix(plugin-data-viewer): CB-1243 focus and selection behavior
This commit is contained in:
+4
-3
@@ -196,6 +196,7 @@ export function useGridSelectionContext(
|
||||
|
||||
if (!multiple) {
|
||||
selectionAction.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (temporary) {
|
||||
@@ -229,14 +230,14 @@ export function useGridSelectionContext(
|
||||
return;
|
||||
}
|
||||
|
||||
const column = props.tableData.getColumn(cell.colIdx);
|
||||
|
||||
if (state.range) {
|
||||
return;
|
||||
}
|
||||
const isIndexColumn = props.tableData.isIndexColumn(column.key);
|
||||
|
||||
const column = props.tableData.getColumn(cell.colIdx);
|
||||
const isIndexColumn = props.tableData.isIndexColumn(column.key);
|
||||
const row = props.tableData.getRow(cell.rowIdx);
|
||||
|
||||
if (isIndexColumn) {
|
||||
selectRows(row, row, undefined, multiple, temporary);
|
||||
return;
|
||||
|
||||
@@ -81,6 +81,19 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
|
||||
},
|
||||
});
|
||||
|
||||
function isGridInFocus(): boolean {
|
||||
const gridDiv = gridContainerRef.current;
|
||||
const focusSink = gridDiv?.querySelector('.rdg-focus-sink');
|
||||
|
||||
if (!gridDiv || !focusSink) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const active = document.activeElement;
|
||||
|
||||
return gridDiv === active || focusSink === active;
|
||||
}
|
||||
|
||||
function setContainersRef(element: HTMLDivElement) {
|
||||
gridContainerRef.current = element;
|
||||
|
||||
@@ -93,7 +106,7 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
|
||||
}
|
||||
}
|
||||
|
||||
const { onKeydownHandler } = useGridSelectedCellsCopy(tableData, gridSelectionContext);
|
||||
const { onKeydownHandler } = useGridSelectedCellsCopy(tableData, selectionAction, gridSelectionContext);
|
||||
const { onMouseDownHandler, onMouseMoveHandler } = useGridDragging({
|
||||
onDragStart: startPosition => {
|
||||
dataGridRef.current?.selectCell({ idx: startPosition.colIdx, rowIdx: startPosition.rowIdx });
|
||||
@@ -154,17 +167,19 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
|
||||
tableData.editor.action.addHandler(syncEditor);
|
||||
|
||||
function syncFocus(data: DatabaseDataSelectActionsData<IResultSetPartialKey>) {
|
||||
if (data.type === 'focus') {
|
||||
if (!data.key?.column || !data.key.row) {
|
||||
return;
|
||||
setTimeout(() => { // TODO: update focus after render rows update
|
||||
if (data.type === 'focus') {
|
||||
if (!data.key?.column || !data.key.row) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = tableData.getColumnIndexFromColumnKey(data.key.column);
|
||||
const rowIdx = tableData.getRowIndexFromKey(data.key.row);
|
||||
|
||||
focusSyncRef.current = { idx, rowIdx };
|
||||
dataGridRef.current?.selectCell({ idx, rowIdx });
|
||||
}
|
||||
|
||||
const idx = tableData.getColumnIndexFromColumnKey(data.key.column);
|
||||
const rowIdx = tableData.getRowIndexFromKey(data.key.row);
|
||||
|
||||
focusSyncRef.current = { idx, rowIdx };
|
||||
dataGridRef.current?.selectCell({ idx, rowIdx });
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
selectionAction.actions.addHandler(syncFocus);
|
||||
@@ -229,10 +244,10 @@ export const DataGridTable = observer<IDataPresentationProps<any, IDatabaseResul
|
||||
actions,
|
||||
columnResize,
|
||||
resultIndex,
|
||||
isGridInFocus: () => gridContainerRef.current === document.activeElement,
|
||||
isGridInFocus,
|
||||
getEditorPortal: () => editorRef.current,
|
||||
getDataGridApi: () => dataGridRef.current,
|
||||
}), [model, actions, resultIndex, editorRef, dataGridRef]);
|
||||
}), [model, actions, resultIndex, editorRef, dataGridRef, gridContainerRef]);
|
||||
|
||||
return styled(styles)(
|
||||
<DataGridContext.Provider value={gridContext}>
|
||||
|
||||
+27
-11
@@ -10,7 +10,7 @@ import { useCallback } from 'react';
|
||||
|
||||
import { useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { copyToClipboard } from '@cloudbeaver/core-utils';
|
||||
import { IResultSetColumnKey, IResultSetElementKey, ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { IResultSetColumnKey, IResultSetElementKey, ResultSetDataKeysUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import type { IDataGridSelectionContext } from './DataGridSelection/DataGridSelectionContext';
|
||||
import type { ITableData } from './TableDataContext';
|
||||
@@ -19,7 +19,13 @@ const EVENT_KEY_CODE = {
|
||||
C: 'KeyC',
|
||||
};
|
||||
|
||||
function copyGridSelectedDataToClipboard(
|
||||
function getCellCopyValue(tableData: ITableData, key: IResultSetElementKey): string {
|
||||
const cell = tableData.getCellValue(key);
|
||||
const cellValue = cell !== undefined ? tableData.format.getText(cell) : undefined;
|
||||
return cellValue ?? '';
|
||||
}
|
||||
|
||||
function getSelectedCellsValue(
|
||||
tableData: ITableData,
|
||||
selectedCells: Map<string, IResultSetElementKey[]>
|
||||
) {
|
||||
@@ -48,9 +54,7 @@ function copyGridSelectedDataToClipboard(
|
||||
const cellKey = rowSelection.find(key => ResultSetDataKeysUtils.isEqual(key.column, column));
|
||||
|
||||
if (cellKey) {
|
||||
const cell = tableData.getCellValue(cellKey);
|
||||
const cellValue = cell !== undefined ? tableData.format.getText(cell) : undefined;
|
||||
rowCellsValues.push(cellValue ?? '');
|
||||
rowCellsValues.push(getCellCopyValue(tableData, cellKey));
|
||||
} else {
|
||||
rowCellsValues.push('');
|
||||
}
|
||||
@@ -58,7 +62,7 @@ function copyGridSelectedDataToClipboard(
|
||||
rowsValues.push(rowCellsValues.join('\t'));
|
||||
}
|
||||
|
||||
copyToClipboard(rowsValues.join('\r\n'));
|
||||
return rowsValues.join('\r\n');
|
||||
}
|
||||
|
||||
// needed for event.code
|
||||
@@ -66,16 +70,28 @@ type IKeyboardEvent = React.KeyboardEvent<HTMLDivElement> & KeyboardEvent;
|
||||
|
||||
export function useGridSelectedCellsCopy(
|
||||
tableData: ITableData,
|
||||
resultSetSelectAction: ResultSetSelectAction,
|
||||
selectionContext: IDataGridSelectionContext
|
||||
) {
|
||||
const props = useObjectRef({ tableData, selectionContext });
|
||||
const props = useObjectRef({ tableData, selectionContext, resultSetSelectAction });
|
||||
|
||||
const onKeydownHandler = useCallback((event: IKeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === EVENT_KEY_CODE.C) {
|
||||
copyGridSelectedDataToClipboard(
|
||||
props.tableData,
|
||||
props.selectionContext.selectedCells
|
||||
);
|
||||
const focusedElement = props.resultSetSelectAction.getFocusedElement();
|
||||
let value: string | null = null;
|
||||
|
||||
if (Array.from(props.selectionContext.selectedCells.keys()).length > 0) {
|
||||
value = getSelectedCellsValue(
|
||||
props.tableData,
|
||||
props.selectionContext.selectedCells
|
||||
);
|
||||
} else if (focusedElement) {
|
||||
value = getCellCopyValue(tableData, focusedElement);
|
||||
}
|
||||
|
||||
if (value !== null) {
|
||||
copyToClipboard(value);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ export function useTableData(
|
||||
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
const view = model.source.getAction(resultIndex, ResultSetViewAction);
|
||||
|
||||
return useObservableRef<ITableData>(() => ({
|
||||
return useObservableRef<ITableData & { gridDIVElement: React.RefObject<HTMLDivElement | null> }>(() => ({
|
||||
get gridDiv(): HTMLDivElement | null {
|
||||
return gridDIVElement.current;
|
||||
return this.gridDIVElement.current;
|
||||
},
|
||||
get columnKeys(): IResultSetColumnKey[] {
|
||||
return this.view.columnKeys;
|
||||
@@ -160,10 +160,12 @@ export function useTableData(
|
||||
data: observable.ref,
|
||||
editor: observable.ref,
|
||||
view: observable.ref,
|
||||
gridDIVElement: observable.ref,
|
||||
}, {
|
||||
format,
|
||||
data,
|
||||
editor,
|
||||
view,
|
||||
gridDIVElement,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,6 +149,13 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
|
||||
const response = await this.graphQLService.sdk.updateResultsDataBatch(updateVariables);
|
||||
|
||||
this.requestInfo = {
|
||||
...this.requestInfo,
|
||||
requestDuration: response.result.duration,
|
||||
requestMessage: 'Saved successfully',
|
||||
source: null,
|
||||
};
|
||||
|
||||
if (editor) {
|
||||
const responseResult = this.transformResults(executionContextInfo, response.result.results, 0)
|
||||
.find(newResult => newResult.id === result.id);
|
||||
@@ -156,13 +163,6 @@ export class ContainerDataSource extends DatabaseDataSource<IDataContainerOption
|
||||
if (responseResult) {
|
||||
editor.applyUpdate(responseResult);
|
||||
}
|
||||
|
||||
this.requestInfo = {
|
||||
...this.requestInfo,
|
||||
requestDuration: response.result.duration,
|
||||
requestMessage: 'Saved successfully',
|
||||
source: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
this.clearError();
|
||||
|
||||
+6
-4
@@ -6,14 +6,14 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DatabaseDataAction } from '../DatabaseDataAction';
|
||||
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
|
||||
import type { IDatabaseDataSource } from '../IDatabaseDataSource';
|
||||
import { databaseDataAction } from './DatabaseDataActionDecorator';
|
||||
import type { DatabaseEditChangeType, IDatabaseDataEditAction, IDatabaseDataEditActionData } from './IDatabaseDataEditAction';
|
||||
import type { DatabaseEditChangeType, IDatabaseDataEditAction, IDatabaseDataEditActionData, IDatabaseDataEditApplyActionData } from './IDatabaseDataEditAction';
|
||||
|
||||
@databaseDataAction()
|
||||
export abstract class DatabaseEditAction<TKey, TValue, TResult extends IDatabaseDataResult>
|
||||
@@ -21,12 +21,14 @@ export abstract class DatabaseEditAction<TKey, TValue, TResult extends IDatabase
|
||||
implements IDatabaseDataEditAction<TKey, TValue, TResult> {
|
||||
static dataFormat: ResultDataFormat | null = null;
|
||||
|
||||
readonly action: IExecutor<IDatabaseDataEditActionData<TKey, TValue>>;
|
||||
readonly action: ISyncExecutor<IDatabaseDataEditActionData<TKey, TValue>>;
|
||||
readonly applyAction: ISyncExecutor<IDatabaseDataEditApplyActionData<any>>;
|
||||
protected features: Array<keyof this>;
|
||||
|
||||
constructor(source: IDatabaseDataSource<any, TResult>, result: TResult) {
|
||||
super(source, result);
|
||||
this.action = new Executor();
|
||||
this.action = new SyncExecutor();
|
||||
this.applyAction = new SyncExecutor();
|
||||
this.features = [];
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DatabaseDataAction } from '../DatabaseDataAction';
|
||||
@@ -20,11 +20,11 @@ export abstract class DatabaseSelectAction<TKey, TResult extends IDatabaseDataRe
|
||||
extends DatabaseDataAction<any, TResult>
|
||||
implements IDatabaseDataSelectAction<TKey, TResult> {
|
||||
static dataFormat: ResultDataFormat | null = null;
|
||||
readonly actions: IExecutor<DatabaseDataSelectActionsData<TKey>>;
|
||||
readonly actions: ISyncExecutor<DatabaseDataSelectActionsData<TKey>>;
|
||||
|
||||
constructor(source: IDatabaseDataSource<any, TResult>, result: TResult) {
|
||||
super(source, result);
|
||||
this.actions = new Executor();
|
||||
this.actions = new SyncExecutor();
|
||||
}
|
||||
|
||||
abstract isSelected(): boolean;
|
||||
|
||||
+14
-2
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { ISyncExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { IDatabaseDataAction } from '../IDatabaseDataAction';
|
||||
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
|
||||
@@ -25,6 +25,17 @@ export interface IDatabaseDataEditActionValue<TKey, TValue> {
|
||||
prevValue?: TValue;
|
||||
}
|
||||
|
||||
export interface IDatabaseDataEditApplyActionUpdate<TKey> {
|
||||
type?: DatabaseEditChangeType;
|
||||
row: TKey;
|
||||
newRow: TKey;
|
||||
}
|
||||
|
||||
export interface IDatabaseDataEditApplyActionData<TKey> {
|
||||
resultId: string | null;
|
||||
updates: Array<IDatabaseDataEditApplyActionUpdate<TKey>>;
|
||||
}
|
||||
|
||||
export interface IDatabaseDataEditActionData<TKey, TValue> {
|
||||
revert: boolean;
|
||||
type?: DatabaseEditChangeType;
|
||||
@@ -34,7 +45,8 @@ export interface IDatabaseDataEditActionData<TKey, TValue> {
|
||||
|
||||
export interface IDatabaseDataEditAction<TKey, TValue, TResult extends IDatabaseDataResult>
|
||||
extends IDatabaseDataAction<any, TResult> {
|
||||
readonly action: IExecutor<IDatabaseDataEditActionData<TKey, TValue>>;
|
||||
readonly action: ISyncExecutor<IDatabaseDataEditActionData<TKey, TValue>>;
|
||||
readonly applyAction: ISyncExecutor<IDatabaseDataEditApplyActionData<any>>;
|
||||
isEdited: () => boolean;
|
||||
isElementEdited: (key: TKey) => boolean;
|
||||
hasFeature: (feature: keyof this) => boolean;
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import type { IExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { ISyncExecutor } from '@cloudbeaver/core-executor';
|
||||
|
||||
import type { IDatabaseDataAction } from '../IDatabaseDataAction';
|
||||
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
|
||||
@@ -27,7 +27,7 @@ export type DatabaseDataSelectActionsData<TKey> = {
|
||||
|
||||
export interface IDatabaseDataSelectAction<TKey, TResult extends IDatabaseDataResult>
|
||||
extends IDatabaseDataAction<any, TResult> {
|
||||
readonly actions: IExecutor<DatabaseDataSelectActionsData<TKey>>;
|
||||
readonly actions: ISyncExecutor<DatabaseDataSelectActionsData<TKey>>;
|
||||
isSelected: () => boolean;
|
||||
isElementSelected: (key: TKey) => boolean;
|
||||
getFocusedElement: () => TKey | null;
|
||||
|
||||
+13
-4
@@ -52,16 +52,25 @@ export class ResultSetDataAction extends DatabaseDataAction<any, IDatabaseResult
|
||||
};
|
||||
}
|
||||
|
||||
insertRow(row: IResultSetRowKey, value: IResultSetValue[], shift = 0): void {
|
||||
insertRow(row: IResultSetRowKey, value: IResultSetValue[], shift = 0): IResultSetRowKey | undefined {
|
||||
if (this.result.data?.rows) {
|
||||
this.result.data.rows.splice(row.index + shift, 0, value);
|
||||
const index = row.index + shift;
|
||||
this.result.data.rows.splice(index, 0, value);
|
||||
|
||||
return { index };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
removeRow(row: IResultSetRowKey, shift = 0): void {
|
||||
removeRow(row: IResultSetRowKey, shift = 0): IResultSetRowKey | undefined {
|
||||
if (this.result.data?.rows) {
|
||||
this.result.data.rows.splice(row.index + shift, 1);
|
||||
const index = row.index + shift;
|
||||
this.result.data.rows.splice(index, 1);
|
||||
|
||||
return { index: index - 1 };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setRowValue(row: IResultSetRowKey, value: IResultSetValue[], shift = 0): void {
|
||||
|
||||
+37
-3
@@ -8,6 +8,7 @@
|
||||
|
||||
import { action, computed, makeObservable, observable } from 'mobx';
|
||||
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ResultDataFormat, SqlResultRow, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk';
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
@@ -15,7 +16,7 @@ import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { databaseDataAction } from '../DatabaseDataActionDecorator';
|
||||
import { DatabaseEditAction } from '../DatabaseEditAction';
|
||||
import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditActionValue } from '../IDatabaseDataEditAction';
|
||||
import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditActionValue, IDatabaseDataEditApplyActionData, IDatabaseDataEditApplyActionUpdate } from '../IDatabaseDataEditAction';
|
||||
import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey';
|
||||
import { isResultSetContentValue } from './isResultSetContentValue';
|
||||
import { ResultSetDataAction } from './ResultSetDataAction';
|
||||
@@ -36,6 +37,7 @@ export class ResultSetEditAction
|
||||
extends DatabaseEditAction<IResultSetElementKey, IResultSetValue, IDatabaseResultSet> {
|
||||
static dataFormat = ResultDataFormat.Resultset;
|
||||
|
||||
readonly applyAction: ISyncExecutor<IDatabaseDataEditApplyActionData<IResultSetRowKey>>;
|
||||
private editorData: Map<string, IResultSetUpdate>;
|
||||
private data: ResultSetDataAction;
|
||||
|
||||
@@ -45,6 +47,7 @@ export class ResultSetEditAction
|
||||
data: ResultSetDataAction
|
||||
) {
|
||||
super(source, result);
|
||||
this.applyAction = new SyncExecutor();
|
||||
this.editorData = new Map();
|
||||
this.data = data;
|
||||
this.features = ['add', 'delete'];
|
||||
@@ -286,6 +289,7 @@ export class ResultSetEditAction
|
||||
}
|
||||
|
||||
applyUpdate(result: IDatabaseResultSet): void {
|
||||
const applyUpdate: Array<IDatabaseDataEditApplyActionUpdate<IResultSetRowKey>> = [];
|
||||
let rowIndex = 0;
|
||||
let addShift = 0;
|
||||
let deleteShift = 0;
|
||||
@@ -303,6 +307,11 @@ export class ResultSetEditAction
|
||||
|
||||
if (value !== undefined) {
|
||||
this.data.setRowValue(update.row, value);
|
||||
applyUpdate.push({
|
||||
type: DatabaseEditChangeType.update,
|
||||
row: update.row,
|
||||
newRow: update.row,
|
||||
});
|
||||
}
|
||||
|
||||
rowIndex++;
|
||||
@@ -313,7 +322,15 @@ export class ResultSetEditAction
|
||||
const value = result.data?.rows?.[rowIndex];
|
||||
|
||||
if (value !== undefined) {
|
||||
this.data.insertRow(update.row, value, addShift);
|
||||
const newRow = this.data.insertRow(update.row, value, addShift);
|
||||
|
||||
if (newRow) {
|
||||
applyUpdate.push({
|
||||
type: DatabaseEditChangeType.add,
|
||||
row: update.row,
|
||||
newRow,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
insertedRows.push(update.row);
|
||||
@@ -324,12 +341,29 @@ export class ResultSetEditAction
|
||||
|
||||
case DatabaseEditChangeType.delete: {
|
||||
const insertShift = insertedRows.filter(row => row.index < update.row.index).length;
|
||||
this.data.removeRow(update.row, deleteShift + insertShift);
|
||||
const newRow = this.data.removeRow(update.row, deleteShift + insertShift);
|
||||
|
||||
if (newRow) {
|
||||
applyUpdate.push({
|
||||
type: DatabaseEditChangeType.delete,
|
||||
row: update.row,
|
||||
newRow,
|
||||
});
|
||||
}
|
||||
|
||||
deleteShift--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (applyUpdate.length > 0) {
|
||||
this.applyAction.execute({
|
||||
resultId: result.id,
|
||||
updates: applyUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
|
||||
+43
-6
@@ -8,16 +8,17 @@
|
||||
|
||||
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
|
||||
|
||||
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { databaseDataAction } from '../DatabaseDataActionDecorator';
|
||||
import { DatabaseSelectAction } from '../DatabaseSelectAction';
|
||||
import { DatabaseEditChangeType, IDatabaseDataEditActionData } from '../IDatabaseDataEditAction';
|
||||
import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditApplyActionData } from '../IDatabaseDataEditAction';
|
||||
import type { DatabaseDataSelectActionsData } from '../IDatabaseDataSelectAction';
|
||||
import type { IResultSetColumnKey, IResultSetElementKey, IResultSetPartialKey, IResultSetRowKey } from './IResultSetDataKey';
|
||||
import { ResultSetDataAction } from './ResultSetDataAction';
|
||||
import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils';
|
||||
import { ResultSetEditAction } from './ResultSetEditAction';
|
||||
import type { IResultSetValue } from './ResultSetFormatAction';
|
||||
@@ -31,24 +32,27 @@ export class ResultSetSelectAction extends DatabaseSelectAction<any, IDatabaseRe
|
||||
return Array.from(this.selectedElements.values()).flat();
|
||||
}
|
||||
|
||||
readonly actions: IExecutor<DatabaseDataSelectActionsData<IResultSetPartialKey>>;
|
||||
readonly actions: ISyncExecutor<DatabaseDataSelectActionsData<IResultSetPartialKey>>;
|
||||
readonly selectedElements: Map<string, IResultSetElementKey[]>;
|
||||
|
||||
private focusedElement: IResultSetElementKey | null;
|
||||
private view: ResultSetViewAction;
|
||||
private edit: ResultSetEditAction;
|
||||
private data: ResultSetDataAction;
|
||||
private validationDisposer: IReactionDisposer;
|
||||
|
||||
constructor(
|
||||
source: IDatabaseDataSource<any, IDatabaseResultSet>,
|
||||
result: IDatabaseResultSet,
|
||||
view: ResultSetViewAction,
|
||||
edit: ResultSetEditAction
|
||||
edit: ResultSetEditAction,
|
||||
data: ResultSetDataAction
|
||||
) {
|
||||
super(source, result);
|
||||
this.view = view;
|
||||
this.edit = edit;
|
||||
this.actions = new Executor();
|
||||
this.data = data;
|
||||
this.actions = new SyncExecutor();
|
||||
this.selectedElements = new Map();
|
||||
this.focusedElement = null;
|
||||
|
||||
@@ -64,8 +68,14 @@ export class ResultSetSelectAction extends DatabaseSelectAction<any, IDatabaseRe
|
||||
this.validationDisposer = reaction(() => this.view.rowKeys, (current, previous) => {
|
||||
if (this.focusedElement) {
|
||||
const focus = this.focusedElement;
|
||||
const currentIndex = current.findIndex(key => ResultSetDataKeysUtils.isEqual(key, focus.row));
|
||||
|
||||
const focusIndex = previous.findIndex(key => ResultSetDataKeysUtils.isEqual(key, focus.row));
|
||||
|
||||
if (currentIndex >= 0 && focusIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (focusIndex === -1 || current.length === 0) {
|
||||
this.focus(null);
|
||||
return;
|
||||
@@ -97,6 +107,7 @@ export class ResultSetSelectAction extends DatabaseSelectAction<any, IDatabaseRe
|
||||
});
|
||||
|
||||
this.edit.action.addHandler(this.syncFocus.bind(this));
|
||||
this.edit.applyAction.addHandler(this.syncFocusOnUpdate.bind(this));
|
||||
}
|
||||
|
||||
isSelected(): boolean {
|
||||
@@ -238,7 +249,7 @@ export class ResultSetSelectAction extends DatabaseSelectAction<any, IDatabaseRe
|
||||
});
|
||||
}
|
||||
|
||||
updateResult(): void {
|
||||
afterResultUpdate(): void {
|
||||
this.validateSelection();
|
||||
}
|
||||
|
||||
@@ -270,6 +281,32 @@ export class ResultSetSelectAction extends DatabaseSelectAction<any, IDatabaseRe
|
||||
}
|
||||
}
|
||||
|
||||
private syncFocusOnUpdate(data: IDatabaseDataEditApplyActionData<IResultSetRowKey>) {
|
||||
let nextFocus = {
|
||||
...this.data.getDefaultKey(),
|
||||
...this.focusedElement,
|
||||
};
|
||||
|
||||
for (const update of data.updates) {
|
||||
switch (update.type) {
|
||||
case DatabaseEditChangeType.add:
|
||||
if (nextFocus === null || ResultSetDataKeysUtils.isEqual(update.row, nextFocus.row)) {
|
||||
nextFocus = { ...nextFocus, row: update.newRow };
|
||||
}
|
||||
break;
|
||||
|
||||
case DatabaseEditChangeType.delete:
|
||||
if (nextFocus === null || ResultSetDataKeysUtils.isEqual(update.row, nextFocus.row)) {
|
||||
nextFocus = { ...nextFocus, row: update.newRow };
|
||||
}
|
||||
this.set({ row: update.row }, false, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.focus(nextFocus);
|
||||
}
|
||||
|
||||
private syncFocus(data: IDatabaseDataEditActionData<IResultSetElementKey, IResultSetValue>) {
|
||||
switch (data.type) {
|
||||
case DatabaseEditChangeType.add:
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ export class ResultSetViewAction extends DatabaseDataAction<any, IDatabaseResult
|
||||
return false;
|
||||
}
|
||||
|
||||
return !this.hasRow(cell.row);
|
||||
return this.hasRow(cell.row);
|
||||
}
|
||||
|
||||
hasRow(key: IResultSetRowKey): boolean {
|
||||
|
||||
@@ -35,6 +35,8 @@ implements IDatabaseDataAction<TOptions, TResult> {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
afterResultUpdate(): void { }
|
||||
|
||||
tryGetAction<T extends IDatabaseDataAction<TOptions, TResult>>(
|
||||
action: IDatabaseDataActionClass<TOptions, TResult, T>
|
||||
): T | undefined {
|
||||
|
||||
@@ -87,7 +87,7 @@ implements IDatabaseDataActions<TOptions, TResult> {
|
||||
}
|
||||
|
||||
updateResults(results: TResult[]): void {
|
||||
const actionsMap = Array.from(this.actions.entries());
|
||||
let actionsMap = Array.from(this.actions.entries());
|
||||
|
||||
for (const [key, actions] of actionsMap) {
|
||||
const result = results.find(result => result.uniqueResultId === key);
|
||||
@@ -103,6 +103,14 @@ implements IDatabaseDataActions<TOptions, TResult> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
actionsMap = Array.from(this.actions.entries());
|
||||
|
||||
for (const [, actions] of actionsMap) {
|
||||
for (const action of actions) {
|
||||
action.afterResultUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addActionToList(
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { IDatabaseDataResult } from './IDatabaseDataResult';
|
||||
import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
|
||||
|
||||
export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseDataResult>
|
||||
implements IDatabaseDataSource<TOptions, TResult> {
|
||||
implements IDatabaseDataSource<TOptions, TResult> {
|
||||
access: DatabaseDataAccessMode;
|
||||
dataFormat: ResultDataFormat;
|
||||
supportedDataFormats: ResultDataFormat[];
|
||||
@@ -162,8 +162,8 @@ export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseData
|
||||
}
|
||||
|
||||
setResults(results: TResult[]): this {
|
||||
this.actions.updateResults(results);
|
||||
this.results = results;
|
||||
this.actions.updateResults(results);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ export interface IDatabaseDataAction<TOptions, TResult extends IDatabaseDataResu
|
||||
result: TResult;
|
||||
resultIndex: number;
|
||||
updateResult: (result: TResult) => void;
|
||||
afterResultUpdate: () => void;
|
||||
tryGetAction: <T extends IDatabaseDataAction<TOptions, TResult>>(
|
||||
action: IDatabaseDataActionClass<TOptions, TResult, T>
|
||||
) => T | undefined;
|
||||
|
||||
@@ -96,6 +96,13 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
|
||||
const response = await this.graphQLService.sdk.updateResultsDataBatch(updateVariables);
|
||||
|
||||
this.requestInfo = {
|
||||
...this.requestInfo,
|
||||
requestDuration: response.result.duration,
|
||||
requestMessage: 'Saved successfully',
|
||||
source: this.options.query,
|
||||
};
|
||||
|
||||
if (editor) {
|
||||
const responseResult = this.transformResults(executionContextInfo, response.result.results, 0)
|
||||
.find(newResult => newResult.id === result.id);
|
||||
@@ -103,13 +110,6 @@ export class QueryDataSource extends DatabaseDataSource<IDataQueryOptions, IData
|
||||
if (responseResult) {
|
||||
editor.applyUpdate(responseResult);
|
||||
}
|
||||
|
||||
this.requestInfo = {
|
||||
...this.requestInfo,
|
||||
requestDuration: response.result.duration,
|
||||
requestMessage: 'Saved successfully',
|
||||
source: this.options.query,
|
||||
};
|
||||
}
|
||||
}
|
||||
this.clearError();
|
||||
|
||||
Reference in New Issue
Block a user