mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
refactor: CB-751 data edit mode behavior
This commit is contained in:
+15
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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));
|
||||
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
|
||||
+12
@@ -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;
|
||||
}
|
||||
+25
-4
@@ -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) {
|
||||
+13
-17
@@ -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;
|
||||
}
|
||||
|
||||
+10
-7
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user