refactor: CB-751 data edit mode behavior

This commit is contained in:
Wroud
2021-03-17 15:31:45 +03:00
parent b9591cadf1
commit 0136d1d115
11 changed files with 103 additions and 45 deletions
@@ -11,15 +11,19 @@ import { useCallback, useContext } from 'react';
import type { CellRendererProps } from 'react-data-grid';
import { Cell } from 'react-data-grid';
import { ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
import { EditingContext } from '../../Editing/EditingContext';
import { DataGridContext } from '../DataGridContext';
import { DataGridSelectionContext } from '../DataGridSelection/DataGridSelectionContext';
import { TableDataContext } from '../TableDataContext';
export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function CellRenderer(props) {
const context = useContext(DataGridContext);
const dataGridContext = useContext(DataGridContext);
const tableDataContext = useContext(TableDataContext);
const selectionContext = useContext(DataGridSelectionContext);
const editingContext = useContext(EditingContext);
const editor = context?.model.source.getEditor(context.resultIndex);
const editor = dataGridContext?.model.source.getEditor(dataGridContext.resultIndex);
const classes: string[] = [];
const { rowIdx, column } = props;
@@ -64,10 +68,16 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
if (!column.editable) {
return;
}
const format = dataGridContext?.model.source.getAction(dataGridContext.resultIndex, ResultSetFormatAction);
const columnIndex = tableDataContext?.getDataColumnIndexFromKey(column.key);
const cellValue = props.row[column.key];
if (cellValue !== null && typeof cellValue === 'object') {
if (
!columnIndex
|| format?.isReadOnly({
row: rowIdx,
column: columnIndex,
})
) {
return;
}
@@ -10,7 +10,7 @@ import { action, observable } from 'mobx';
import { useState } from 'react';
import { useObjectRef } from '@cloudbeaver/core-blocks';
import type { IResultSetSelectKey, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
import type { IResultSetElementKey, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
import type { ITableData } from '../TableDataContext';
import type { IDraggingPosition } from '../useGridDragging';
@@ -172,7 +172,7 @@ export function useGridSelectionContext(
const { selectionAction } = props;
temporarySelection.clear();
const key: IResultSetSelectKey = {
const key: IResultSetElementKey = {
column: columnIdx,
row: rowIdx,
};
@@ -51,9 +51,9 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
const [columnResize] = useState(() => new Executor<IColumnResizeInfo>());
const selectionAction = model.source.getAction(resultIndex, ResultSetSelectAction);
const modelResultData = model?.getResult(resultIndex);
const modelResultData = model.getResult(resultIndex);
const tableData = useTableData(modelResultData);
const tableData = useTableData(model, resultIndex);
const gridSortingContext = useGridSortingContext(model);
const gridSelectionContext = useGridSelectionContext(tableData, selectionAction);
@@ -19,6 +19,7 @@ export interface ITableData {
getCellValue: (rowIndex: number, key: string | number) => any;
getColumnInfo: (key: string | number) => SqlResultColumn | undefined;
getColumnsInRange: (startIndex: number, endIndex: number) => Array<Column<any[], any>>;
getDataColumnIndexFromKey: (columnKey: string | number) => number | null;
getColumnIndexFromKey: (columnKey: string | number) => number | null;
isIndexColumn: (columnKey: string | number) => boolean;
isIndexColumnInRange: (columnsRange: Array<Column<any[], any>>) => boolean;
@@ -13,7 +13,7 @@ import type { Column } from 'react-data-grid';
import { useObjectRef } from '@cloudbeaver/core-blocks';
import type { SqlResultColumn } from '@cloudbeaver/core-sdk';
import { TextTools } from '@cloudbeaver/core-utils';
import type { IDatabaseResultSet } from '@cloudbeaver/plugin-data-viewer';
import { IDatabaseDataModel, IDatabaseResultSet, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
import { ResultSetTools } from '../ResultSetTools';
import { IndexFormatter } from './Formatters/IndexFormatter';
@@ -32,13 +32,17 @@ export const indexColumn: Column<any[], any> = {
formatter: IndexFormatter,
};
export function useTableData(modelResultData: IDatabaseResultSet | null): ITableData {
const props = useObjectRef({ modelResultData }, undefined, true);
export function useTableData(model: IDatabaseDataModel<any, IDatabaseResultSet>, resultIndex: number): ITableData {
const modelResultData = model.getResult(resultIndex);
const props = useObjectRef({ modelResultData, model, resultIndex }, undefined, true);
const [state] = useState(() => computed(() => {
if (!props.modelResultData?.data) {
return { columns: [], rows: [] };
}
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
// TODO: seems it must be moved to ResultSetFormatAction
const columnNames = ResultSetTools.getHeaders(props.modelResultData.data);
const rowStrings = ResultSetTools.getLongestCells(props.modelResultData.data);
@@ -60,7 +64,7 @@ export function useTableData(modelResultData: IDatabaseResultSet | null): ITable
const columns = props.modelResultData.data?.columns!.map<Column<any[], any>>((col, columnIndex) => ({
key: columnIndex + '',
name: col.label!,
editable: !col.readOnly,
editable: !format.isReadOnly({ column: columnIndex }),
width: Math.min(300, measuredCells[columnIndex]),
headerRenderer: TableColumnHeader,
})) || [];
@@ -84,7 +88,16 @@ export function useTableData(modelResultData: IDatabaseResultSet | null): ITable
return this.rows[rowIndex][key as number];
},
getColumnInfo(key: string | number): SqlResultColumn | undefined {
return props.modelResultData?.data?.columns?.[Number(key)];
return this.dataColumns[Number(key)];
},
getDataColumnIndexFromKey(key: string | number) {
const info = this.getColumnInfo(key);
if (!info) {
return null;
}
return Number(key);
},
getColumnIndexFromKey(key: string | number) {
const index = this.columns.findIndex((column: any) => column.key === String(key));
@@ -9,8 +9,9 @@
import type { IDatabaseDataAction } from '../IDatabaseDataAction';
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
export interface IDatabaseDataFormatAction<TResult extends IDatabaseDataResult>
export interface IDatabaseDataFormatAction<TKey, TResult extends IDatabaseDataResult>
extends IDatabaseDataAction<TResult> {
isReadOnly: (key: TKey) => boolean;
get: (value: any) => any;
toString: (value: any) => string;
}
@@ -0,0 +1,12 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export interface IResultSetElementKey {
readonly row?: number;
readonly column?: number;
}
@@ -8,12 +8,13 @@
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseResultSet } from '../IDatabaseResultSet';
import { databaseDataAction } from './DatabaseDataActionDecorator';
import type { IDatabaseDataFormatAction } from './IDatabaseDataFormatAction';
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { IDatabaseDataFormatAction } from '../IDatabaseDataFormatAction';
import type { IResultSetElementKey } from './IResultSetElementKey';
@databaseDataAction()
export class ResultSetFormatAction implements IDatabaseDataFormatAction<IDatabaseResultSet> {
export class ResultSetFormatAction implements IDatabaseDataFormatAction<IResultSetElementKey, IDatabaseResultSet> {
static dataFormat = ResultDataFormat.Resultset;
result: IDatabaseResultSet;
@@ -21,6 +22,26 @@ export class ResultSetFormatAction implements IDatabaseDataFormatAction<IDatabas
this.result = result;
}
isReadOnly(key: IResultSetElementKey): boolean {
let columnReadonly = true;
let cellReadonly = true;
if (key.column && this.result.data?.columns) {
columnReadonly = this.result.data.columns[key.column].readOnly;
}
if (key.row && key.column && this.result.data?.rows) {
const value = this.result.data.rows[key.row][key.column];
cellReadonly = this.isValueReadonly(value);
}
return columnReadonly || cellReadonly;
}
isValueReadonly(value: any): boolean {
return value !== null && typeof value === 'object';
}
get(value: any): any {
if (value !== null && typeof value === 'object') {
if ('text' in value) {
@@ -11,25 +11,21 @@ import { makeObservable, observable } from 'mobx';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseResultSet } from '../IDatabaseResultSet';
import { databaseDataAction } from './DatabaseDataActionDecorator';
import type { DatabaseDataEditorActionsData, IDatabaseDataSelectAction } from './IDatabaseDataSelectAction';
export interface IResultSetSelectKey {
readonly row?: number;
readonly column?: number;
}
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { DatabaseDataEditorActionsData, IDatabaseDataSelectAction } from '../IDatabaseDataSelectAction';
import type { IResultSetElementKey } from './IResultSetElementKey';
@databaseDataAction()
export class ResultSetSelectAction implements IDatabaseDataSelectAction<IResultSetSelectKey, IDatabaseResultSet> {
export class ResultSetSelectAction implements IDatabaseDataSelectAction<IResultSetElementKey, IDatabaseResultSet> {
static dataFormat = ResultDataFormat.Resultset;
result: IDatabaseResultSet;
readonly actions: IExecutor<DatabaseDataEditorActionsData<IResultSetSelectKey>>;
readonly actions: IExecutor<DatabaseDataEditorActionsData<IResultSetElementKey>>;
readonly selectedElements: Map<number, number[]>;
private focusedElement: IResultSetSelectKey | null;
private focusedElement: IResultSetElementKey | null;
constructor(result: IDatabaseResultSet) {
this.result = result;
@@ -51,7 +47,7 @@ export class ResultSetSelectAction implements IDatabaseDataSelectAction<IResultS
return this.selectedElements.size > 0;
}
isElementSelected(key: IResultSetSelectKey): boolean {
isElementSelected(key: IResultSetElementKey): boolean {
if (key.row === undefined) {
const rows = this.result.data?.rows?.length || 0;
for (let row = 0; row < rows; row++) {
@@ -76,12 +72,12 @@ export class ResultSetSelectAction implements IDatabaseDataSelectAction<IResultS
return row.length === this.result.data?.columns?.length;
}
getFocusedElement(): IResultSetSelectKey | null {
getFocusedElement(): IResultSetElementKey | null {
return this.focusedElement;
}
getSelectedElements(): Array<Required<IResultSetSelectKey>> {
const selectedKeys: Array<Required<IResultSetSelectKey>> = [];
getSelectedElements(): Array<Required<IResultSetElementKey>> {
const selectedKeys: Array<Required<IResultSetElementKey>> = [];
for (const [row, value] of this.selectedElements) {
selectedKeys.push(...value.map(column => ({ row, column })));
@@ -94,7 +90,7 @@ export class ResultSetSelectAction implements IDatabaseDataSelectAction<IResultS
return this.selectedElements.get(row) || [];
}
set(key: IResultSetSelectKey, selected: boolean): void {
set(key: IResultSetElementKey, selected: boolean): void {
if (key.row === undefined) {
for (let row = 0; row < (this.result.data?.rows?.length || 0); row++) {
this.set({ row, column: key.column }, selected);
@@ -145,7 +141,7 @@ export class ResultSetSelectAction implements IDatabaseDataSelectAction<IResultS
}
}
focus(key: IResultSetSelectKey | null): void {
focus(key: IResultSetElementKey | null): void {
this.focusedElement = key;
}
@@ -13,8 +13,9 @@ import { BASE_CONTAINERS_STYLES, TabContainerPanelComponent, TextareaNew } from
import { useStyles } from '@cloudbeaver/core-theming';
import { css } from '@reshadow/react';
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSetFormatAction';
import { IResultSetSelectKey, ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSetSelectAction';
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetElementKey';
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
@@ -35,17 +36,19 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
let value: any;
let stringValue: string | undefined;
let firstSelectedCell: Required<IResultSetSelectKey> | undefined;
let firstSelectedCell: Required<IResultSetElementKey> | undefined;
let readonly = true;
if (result?.data?.rows && selectedCells.length > 0) {
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
firstSelectedCell = selectedCells[0];
value = model.source
.getEditor(resultIndex)
.getCell(firstSelectedCell.row, firstSelectedCell.column);
stringValue = model.source
.getAction(resultIndex, ResultSetFormatAction)
.get(value);
stringValue = format.get(value);
readonly = format.isReadOnly(firstSelectedCell);
}
const handleChange = (value: string) => {
@@ -62,7 +65,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
rows={3}
value={stringValue}
disabled={stringValue === undefined}
readOnly={model.isReadonly() || (value !== null && typeof value === 'object')}
readOnly={model.isReadonly() || readonly}
embedded
onChange={handleChange}
/>
@@ -2,11 +2,12 @@ import { manifest } from './manifest';
export default manifest;
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetElementKey';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
export * from './DatabaseDataModel/Actions/DatabaseDataActionDecorator';
export * from './DatabaseDataModel/Actions/IDatabaseDataFormatAction';
export * from './DatabaseDataModel/Actions/IDatabaseDataSelectAction';
export * from './DatabaseDataModel/Actions/ResultSetFormatAction';
export * from './DatabaseDataModel/Actions/ResultSetSelectAction';
export * from './DatabaseDataModel/DatabaseDataActions';
export * from './DatabaseDataModel/DatabaseDataEditor';
export * from './DatabaseDataModel/DatabaseDataFormat';