mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
refactor: CB-1151 grid data presentation
This commit is contained in:
+16
-11
@@ -40,9 +40,8 @@ export interface IEditorRef {
|
||||
focus: () => void;
|
||||
}
|
||||
|
||||
export const CellEditor = observer<Pick<EditorProps<any, any>, 'rowIdx' | 'row' | 'column' | 'onClose'>, IEditorRef>(function CellEditor({
|
||||
export const CellEditor = observer<Pick<EditorProps<any, any>, 'rowIdx' | 'column' | 'onClose'>, IEditorRef>(function CellEditor({
|
||||
rowIdx,
|
||||
row,
|
||||
column,
|
||||
onClose,
|
||||
}, ref) {
|
||||
@@ -51,12 +50,11 @@ export const CellEditor = observer<Pick<EditorProps<any, any>, 'rowIdx' | 'row'
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [elementRef, setElementRef] = useState<HTMLDivElement | null>(null);
|
||||
const [popperRef, setPopperRef] = useState<HTMLDivElement | null>(null);
|
||||
const formatter = dataGridContext?.model.source.getAction(dataGridContext.resultIndex, ResultSetFormatAction);
|
||||
const popper = usePopper(elementRef, popperRef, {
|
||||
placement: 'right',
|
||||
});
|
||||
|
||||
if (!dataGridContext || !tableDataContext) {
|
||||
if (!dataGridContext || !tableDataContext || column.columnDataIndex === null) {
|
||||
throw new Error('DataGridContext should be provided');
|
||||
}
|
||||
|
||||
@@ -87,21 +85,28 @@ export const CellEditor = observer<Pick<EditorProps<any, any>, 'rowIdx' | 'row'
|
||||
}
|
||||
});
|
||||
|
||||
const value = formatter?.getText(row[column.key]) ?? '';
|
||||
const value = tableDataContext.format
|
||||
.getText(tableDataContext.getCellValue(rowIdx, column.columnDataIndex)!) ?? '';
|
||||
|
||||
const handleSave = () => onClose(false);
|
||||
const handleReject = () => {
|
||||
dataGridContext.model.source.getEditor(dataGridContext.resultIndex)
|
||||
.revertCell(rowIdx, Number(column.key));
|
||||
if (column.columnDataIndex !== null) {
|
||||
tableDataContext.editor
|
||||
.revertCell(rowIdx, column.columnDataIndex);
|
||||
}
|
||||
onClose(false);
|
||||
};
|
||||
const handleChange = (value: string) => {
|
||||
dataGridContext.model.source.getEditor(dataGridContext.resultIndex)
|
||||
.setCell(rowIdx, Number(column.key), value);
|
||||
if (column.columnDataIndex !== null) {
|
||||
tableDataContext.editor
|
||||
.setCell(rowIdx, column.columnDataIndex, value);
|
||||
}
|
||||
};
|
||||
const handleUndo = () => {
|
||||
dataGridContext.model.source.getEditor(dataGridContext.resultIndex)
|
||||
.revertCell(rowIdx, Number(column.key));
|
||||
if (column.columnDataIndex !== null) {
|
||||
tableDataContext.editor
|
||||
.revertCell(rowIdx, column.columnDataIndex);
|
||||
}
|
||||
onClose(false);
|
||||
};
|
||||
|
||||
|
||||
+35
-41
@@ -6,7 +6,6 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import type { CellRendererProps } from 'react-data-grid';
|
||||
@@ -14,7 +13,7 @@ import { Cell } from 'react-data-grid';
|
||||
|
||||
import { useMouse, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { isBooleanValuePresentationAvailable, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { isBooleanValuePresentationAvailable } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { EditingContext } from '../../Editing/EditingContext';
|
||||
import { DataGridContext } from '../DataGridContext';
|
||||
@@ -30,37 +29,29 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
const editingContext = useContext(EditingContext);
|
||||
const mouse = useMouse<HTMLDivElement>({});
|
||||
const cellContext = useMemo<ICellContext>(() => ({ mouse }), [mouse]);
|
||||
const resultColumn = tableDataContext?.getColumnInfo(column.key);
|
||||
const editor = dataGridContext?.model.source.getEditor(dataGridContext.resultIndex);
|
||||
|
||||
const classes: string[] = [];
|
||||
let classes = '';
|
||||
|
||||
if (selectionContext?.isSelected(rowIdx, column.idx)) {
|
||||
classes.push('rdg-cell-custom-selected');
|
||||
classes += ' rdg-cell-custom-selected';
|
||||
}
|
||||
|
||||
if (editingContext?.isEditing({ idx: column.idx, rowIdx })) {
|
||||
classes.push('rdg-cell-custom-editing');
|
||||
classes += ' rdg-cell-custom-editing';
|
||||
}
|
||||
|
||||
if (!tableDataContext?.isIndexColumn(column.key) && editor?.isCellEdited(rowIdx, Number(column.key))) {
|
||||
classes.push('rdg-cell-custom-edited');
|
||||
if (tableDataContext?.isCellEdited(rowIdx, column)) {
|
||||
classes += ' rdg-cell-custom-edited';
|
||||
}
|
||||
|
||||
const state = useObjectRef({
|
||||
row: props.row,
|
||||
column,
|
||||
rowIdx,
|
||||
resultColumn,
|
||||
isCellSelected,
|
||||
editor,
|
||||
selectionContext,
|
||||
dataGridContext,
|
||||
editingContext,
|
||||
tableDataContext,
|
||||
get immutableRow() {
|
||||
return this.editor?.get(this.rowIdx) || this.row; // performance heavy
|
||||
},
|
||||
mouseDown(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
if (EventContext.has(event, EventStopPropagationFlag)) {
|
||||
return;
|
||||
@@ -83,11 +74,15 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
);
|
||||
},
|
||||
mouseUp(event: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||
if (!this.dataGridContext?.isGridInFocus() || EventContext.has(event, EventStopPropagationFlag)) {
|
||||
if (
|
||||
!this.selectionContext
|
||||
|| !this.dataGridContext?.isGridInFocus()
|
||||
|| EventContext.has(event, EventStopPropagationFlag)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectionContext?.select(
|
||||
this.selectionContext.select(
|
||||
{
|
||||
colIdx: this.column.idx,
|
||||
rowIdx: this.rowIdx,
|
||||
@@ -98,25 +93,30 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
);
|
||||
},
|
||||
doubleClick(event: React.MouseEvent<HTMLDivElement>) {
|
||||
if (EventContext.has(event, EventStopPropagationFlag)) {
|
||||
const columnIndex = this.column.columnDataIndex;
|
||||
|
||||
if (
|
||||
!this.editingContext
|
||||
|| !this.tableDataContext
|
||||
|| EventContext.has(event, EventStopPropagationFlag)
|
||||
|| columnIndex === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const format = this.dataGridContext?.model.source.getAction(
|
||||
this.dataGridContext.resultIndex,
|
||||
ResultSetFormatAction
|
||||
);
|
||||
const columnIndex = this.tableDataContext?.getDataColumnIndexFromKey(this.column.key) ?? null;
|
||||
const handleByBooleanFormatter = this.resultColumn && isBooleanValuePresentationAvailable(
|
||||
this.editor?.getCell(this.rowIdx, Number(this.column.key)),
|
||||
this.resultColumn
|
||||
);
|
||||
const resultColumn = this.tableDataContext.getColumnInfo(columnIndex);
|
||||
const value = this.tableDataContext.getCellValue(this.rowIdx, columnIndex);
|
||||
|
||||
if (!resultColumn || value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleByBooleanFormatter = isBooleanValuePresentationAvailable(value, resultColumn);
|
||||
|
||||
if (
|
||||
!this.column.editable
|
||||
|| handleByBooleanFormatter
|
||||
|| columnIndex === null
|
||||
|| format?.isReadOnly({
|
||||
|| this.tableDataContext.format.isReadOnly({
|
||||
row: this.rowIdx,
|
||||
column: columnIndex,
|
||||
})
|
||||
@@ -124,38 +124,32 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
return;
|
||||
}
|
||||
|
||||
this.editingContext?.edit({ idx: this.column.idx, rowIdx: this.rowIdx });
|
||||
this.editingContext.edit({
|
||||
idx: this.column.idx,
|
||||
rowIdx: this.rowIdx,
|
||||
});
|
||||
},
|
||||
}, {
|
||||
row: props.row,
|
||||
column,
|
||||
rowIdx,
|
||||
resultColumn,
|
||||
isCellSelected,
|
||||
editor,
|
||||
selectionContext,
|
||||
dataGridContext,
|
||||
editingContext,
|
||||
tableDataContext,
|
||||
}, {
|
||||
rowIdx: observable.ref,
|
||||
row: observable.ref,
|
||||
editor: observable.ref,
|
||||
immutableRow: computed,
|
||||
}, ['doubleClick', 'mouseUp', 'mouseDown']);
|
||||
}, undefined, ['doubleClick', 'mouseUp', 'mouseDown']);
|
||||
|
||||
return (
|
||||
<CellContext.Provider value={cellContext}>{/** performance super heavy */}
|
||||
<Cell
|
||||
ref={mouse.reference}
|
||||
className={classes.join(' ')}
|
||||
className={classes}
|
||||
data-row-index={rowIdx}
|
||||
data-column-index={column.idx}
|
||||
onMouseDown={state.mouseDown}
|
||||
onMouseUp={state.mouseUp}
|
||||
onDoubleClick={state.doubleClick}
|
||||
{...props}
|
||||
row={state.immutableRow}
|
||||
/>
|
||||
</CellContext.Provider>
|
||||
);
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ export class DataGridContextMenuCellEditingService {
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const cellValue = data.getCellValue({ column, row });
|
||||
|
||||
return data.getColumn(column)?.required || format.isNull(cellValue);
|
||||
return cellValue === undefined || data.getColumn(column)?.required || format.isNull(cellValue);
|
||||
},
|
||||
order: 1,
|
||||
title: 'data_grid_table_editing_set_to_null',
|
||||
|
||||
+11
-4
@@ -216,8 +216,9 @@ export class DataGridContextMenuFilterService {
|
||||
const data = model.source.getAction(resultIndex, ResultSetDataAction);
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const supportedOperations = data.getColumnOperations(column);
|
||||
const value = data.getCellValue({ column, row });
|
||||
|
||||
return supportedOperations.length === 0 || format.isNull(data.getCellValue({ column, row }));
|
||||
return value === undefined || supportedOperations.length === 0 || format.isNull(value);
|
||||
},
|
||||
order: 1,
|
||||
title: 'data_grid_table_filter_cell_value',
|
||||
@@ -242,10 +243,12 @@ export class DataGridContextMenuFilterService {
|
||||
return context.contextType === DataGridContextMenuService.cellContext;
|
||||
},
|
||||
isHidden: context => {
|
||||
const data = context.data.model.source.getAction(context.data.resultIndex, ResultSetDataAction);
|
||||
const supportedOperations = data.getColumnOperations(context.data.column);
|
||||
const { model, resultIndex, column, row } = context.data;
|
||||
const data = model.source.getAction(resultIndex, ResultSetDataAction);
|
||||
const cellValue = data.getCellValue({ column, row });
|
||||
const supportedOperations = data.getColumnOperations(column);
|
||||
|
||||
return supportedOperations.length === 0;
|
||||
return cellValue === undefined || supportedOperations.length === 0;
|
||||
},
|
||||
order: 2,
|
||||
title: 'data_grid_table_filter_custom_value',
|
||||
@@ -260,6 +263,10 @@ export class DataGridContextMenuFilterService {
|
||||
const cellValue = data.getCellValue({ column, row });
|
||||
const columnLabel = data.getColumn(column)?.label || '';
|
||||
|
||||
if (!cellValue) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return supportedOperations
|
||||
.filter(operation => !nullOperationsFilter(operation))
|
||||
.map(operation => {
|
||||
|
||||
+19
-17
@@ -34,16 +34,6 @@ export function useGridSelectionContext(
|
||||
lastSelectedCell: null,
|
||||
}));
|
||||
|
||||
function getRealColumnIndex(colIdx: number) {
|
||||
let column: number | undefined = props.tableData.getColumnKeyFromColumnIndex(colIdx);
|
||||
|
||||
if (props.tableData.isIndexColumn(column)) {
|
||||
column = undefined;
|
||||
}
|
||||
|
||||
return column;
|
||||
}
|
||||
|
||||
const selectRows = action(function selectRows(
|
||||
startPosition: number,
|
||||
lastPosition: number,
|
||||
@@ -124,7 +114,11 @@ export function useGridSelectionContext(
|
||||
selectRows(
|
||||
startPosition.rowIdx,
|
||||
lastPosition.rowIdx,
|
||||
isIndexColumnInRange ? undefined : columnsInRange.map(column => Number(column.key)),
|
||||
isIndexColumnInRange
|
||||
? undefined
|
||||
: (columnsInRange
|
||||
.filter(column => column.columnDataIndex !== null)
|
||||
.map(column => column.columnDataIndex!)),
|
||||
multiple,
|
||||
temporary
|
||||
);
|
||||
@@ -134,10 +128,14 @@ export function useGridSelectionContext(
|
||||
colIdx: number,
|
||||
multiple: boolean
|
||||
) {
|
||||
const { selectionAction, tableData } = props;
|
||||
|
||||
state.temporarySelection.clear();
|
||||
const column = getRealColumnIndex(colIdx);
|
||||
|
||||
const column = tableData
|
||||
.getColumn(colIdx)
|
||||
.columnDataIndex ?? undefined;
|
||||
|
||||
const { selectionAction } = props;
|
||||
const selected = selectionAction.isElementSelected({ column });
|
||||
|
||||
if (!multiple) {
|
||||
@@ -153,7 +151,9 @@ export function useGridSelectionContext(
|
||||
}
|
||||
|
||||
function isSelected(rowIdx: number, colIdx: number) {
|
||||
const column = getRealColumnIndex(colIdx);
|
||||
const column = props.tableData
|
||||
.getColumn(colIdx)
|
||||
.columnDataIndex ?? undefined;
|
||||
|
||||
const temporaryRowSelection = state.temporarySelection.get(rowIdx);
|
||||
|
||||
@@ -205,8 +205,8 @@ export function useGridSelectionContext(
|
||||
|
||||
function select(cell: IDraggingPosition, multiple: boolean, range: boolean, temporary: boolean) {
|
||||
const { lastSelectedCell } = state;
|
||||
const columnKey = props.tableData.getColumnKeyFromColumnIndex(cell.colIdx);
|
||||
const isIndexColumn = props.tableData.isIndexColumn(columnKey);
|
||||
const column = props.tableData.getColumn(cell.colIdx);
|
||||
const isIndexColumn = props.tableData.isIndexColumn(column.key);
|
||||
|
||||
if (!temporary) {
|
||||
state.lastSelectedCell = cell;
|
||||
@@ -226,7 +226,9 @@ export function useGridSelectionContext(
|
||||
return;
|
||||
}
|
||||
|
||||
selectCell(cell.rowIdx, columnKey, multiple, temporary);
|
||||
if (column.columnDataIndex !== null) {
|
||||
selectCell(cell.rowIdx, column.columnDataIndex, multiple, temporary);
|
||||
}
|
||||
}
|
||||
|
||||
return useObjectRef<IDataGridSelectionContext>({
|
||||
|
||||
@@ -88,11 +88,7 @@ export const DataGridTable: React.FC<IDataPresentationProps<any, IDatabaseResult
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = tableData.getColumnIndexFromKey(data.column);
|
||||
|
||||
if (idx === null) {
|
||||
return;
|
||||
}
|
||||
const idx = tableData.getColumnIndexFromKey(tableData.getColumnByDataIndex(data.column).key);
|
||||
|
||||
editingContext.closeEditor({
|
||||
rowIdx: data.row,
|
||||
@@ -106,12 +102,11 @@ export const DataGridTable: React.FC<IDataPresentationProps<any, IDatabaseResult
|
||||
}, [model.source, resultIndex]);
|
||||
|
||||
const handleFocusChange = (position: CellPosition) => {
|
||||
const key = tableData.getColumnKeyFromColumnIndex(position.idx);
|
||||
const column = tableData.getColumnInfo(key);
|
||||
const column = tableData.getColumn(position.idx);
|
||||
|
||||
selectionAction.focus({
|
||||
row: position.rowIdx,
|
||||
column: column ? key : 0,
|
||||
column: column.columnDataIndex ?? 0,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -157,13 +152,13 @@ export const DataGridTable: React.FC<IDataPresentationProps<any, IDatabaseResult
|
||||
<DataGrid
|
||||
ref={dataGridRef}
|
||||
className={`cb-react-grid-theme ${className}`}
|
||||
columns={tableData.data.columns}
|
||||
columns={tableData.columns}
|
||||
defaultColumnOptions={{
|
||||
minWidth: 50,
|
||||
resizable: true,
|
||||
formatter: CellFormatter,
|
||||
}}
|
||||
rows={tableData.data.rows}
|
||||
rows={tableData.dataRows}
|
||||
headerRowHeight={28}
|
||||
rowHeight={25}
|
||||
rowRenderer={RowRenderer}
|
||||
|
||||
+11
-8
@@ -44,19 +44,22 @@ export const CellFormatter: React.FC<Props> = observer(function CellFormatter({
|
||||
const editingContext = useContext(EditingContext);
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const isEditing = editingContext?.isEditing({ idx: rest.column.idx, rowIdx: rest.rowIdx }) ?? false;
|
||||
const showCellMenu = !isEditing
|
||||
&& (rest.isCellSelected || cellContext?.mouse.state.mouseEnter || menuVisible);
|
||||
const showCellMenu = !isEditing && (
|
||||
rest.isCellSelected
|
||||
|| cellContext?.mouse.state.mouseEnter
|
||||
|| menuVisible
|
||||
);
|
||||
|
||||
const spreadsheetActions = useObjectRef<IDataPresentationActions<IResultSetElementKey>>({
|
||||
edit(position) {
|
||||
if (position.column === undefined || position.row === undefined) {
|
||||
if (position.column === undefined || position.row === undefined || !tableDataContext || !editingContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = tableDataContext?.getColumnIndexFromKey(position.column);
|
||||
const idx = tableDataContext.getColumnIndexFromKey(tableDataContext.getColumnByDataIndex(position.column).key);
|
||||
|
||||
if (idx !== undefined && idx !== null) {
|
||||
editingContext?.edit({ idx, rowIdx: position.row });
|
||||
if (idx !== -1) {
|
||||
editingContext.edit({ idx, rowIdx: position.row });
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -66,7 +69,7 @@ export const CellFormatter: React.FC<Props> = observer(function CellFormatter({
|
||||
<formatter-container>
|
||||
<CellFormatterFactory {...rest} isEditing={isEditing} />
|
||||
</formatter-container>
|
||||
{showCellMenu && context && (
|
||||
{showCellMenu && context && rest.column.columnDataIndex !== null && (
|
||||
<menu-container>
|
||||
<CellMenu
|
||||
model={context.model}
|
||||
@@ -74,7 +77,7 @@ export const CellFormatter: React.FC<Props> = observer(function CellFormatter({
|
||||
spreadsheetActions={spreadsheetActions}
|
||||
resultIndex={context.resultIndex}
|
||||
row={rest.rowIdx}
|
||||
column={Number(rest.column.key)}
|
||||
column={rest.column.columnDataIndex}
|
||||
onStateSwitch={setMenuVisible}
|
||||
/>
|
||||
</menu-container>
|
||||
|
||||
+5
-5
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useRef } from 'react';
|
||||
import type { FormatterProps } from 'react-data-grid';
|
||||
|
||||
import { isBooleanValuePresentationAvailable, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { isBooleanValuePresentationAvailable } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridContext } from '../DataGridContext';
|
||||
import { TableDataContext } from '../TableDataContext';
|
||||
@@ -29,10 +29,10 @@ export const CellFormatterFactory: React.FC<IProps> = observer(function CellForm
|
||||
if (!props.isEditing || formatterRef === null) {
|
||||
formatterRef.current = TextFormatter;
|
||||
|
||||
if (tableDataContext && context) {
|
||||
const resultColumn = tableDataContext.getColumnInfo(props.column.key);
|
||||
const formatter = context.model.source.getAction(context.resultIndex, ResultSetFormatAction);
|
||||
const rawValue = formatter.get(props.row[props.column.key]);
|
||||
if (tableDataContext && context && props.column.columnDataIndex !== null) {
|
||||
const resultColumn = tableDataContext.getColumnInfo(props.column.columnDataIndex);
|
||||
const rawValue = tableDataContext.format
|
||||
.get(tableDataContext.getCellValue(props.rowIdx, props.column.columnDataIndex)!);
|
||||
|
||||
if (resultColumn && isBooleanValuePresentationAvailable(rawValue, resultColumn)) {
|
||||
formatterRef.current = BooleanFormatter;
|
||||
|
||||
+23
-12
@@ -11,8 +11,6 @@ import { useCallback, useContext } from 'react';
|
||||
import type { FormatterProps } from 'react-data-grid';
|
||||
import styled, { use, css } from 'reshadow';
|
||||
|
||||
import { ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { EditingContext } from '../../../Editing/EditingContext';
|
||||
import { DataGridContext } from '../../DataGridContext';
|
||||
import { TableDataContext } from '../../TableDataContext';
|
||||
@@ -32,28 +30,41 @@ const styles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const BooleanFormatter: React.FC<FormatterProps> = observer(function BooleanFormatter({ column, row, rowIdx }) {
|
||||
export const BooleanFormatter: React.FC<FormatterProps> = observer(function BooleanFormatter({ column, rowIdx }) {
|
||||
const context = useContext(DataGridContext);
|
||||
const tableDataContext = useContext(TableDataContext);
|
||||
const editingContext = useContext(EditingContext);
|
||||
|
||||
const formatter = context?.model.source.getAction(context.resultIndex, ResultSetFormatAction);
|
||||
const resultColumn = tableDataContext?.getColumnInfo(column.key);
|
||||
const rawValue = formatter?.get(row[column.key]) ?? row[column.key];
|
||||
if (!context || !tableDataContext || !editingContext || column.columnDataIndex === null) {
|
||||
throw new Error('Contexts required');
|
||||
}
|
||||
|
||||
const formatter = tableDataContext.format;
|
||||
const rawValue = formatter.get(tableDataContext.getCellValue(rowIdx, column.columnDataIndex)!);
|
||||
const value = typeof rawValue === 'string' ? rawValue.toLowerCase() === 'true' : rawValue;
|
||||
const stringifiedValue = formatter?.toDisplayString(value) ?? String(value);
|
||||
const stringifiedValue = formatter.toDisplayString(value);
|
||||
const valueRepresentation = value === null ? stringifiedValue : `[${value ? 'v' : ' '}]`;
|
||||
const disabled = !column.editable || !!editingContext?.readonly;
|
||||
const disabled = (
|
||||
!column.editable
|
||||
|| !!editingContext.readonly
|
||||
|| formatter.isReadOnly({ row: rowIdx, column: column.columnDataIndex })
|
||||
);
|
||||
|
||||
const toggleValue = useCallback(() => {
|
||||
if (disabled) {
|
||||
if (disabled || column.columnDataIndex === null) {
|
||||
return;
|
||||
}
|
||||
const resultColumn = tableDataContext.getColumnInfo(column.columnDataIndex);
|
||||
|
||||
if (!resultColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextValue = !resultColumn?.required && value === false ? null : !value;
|
||||
const nextValue = !resultColumn.required && value === false ? null : !value;
|
||||
|
||||
context?.model.source.getEditor(context.resultIndex).setCell(rowIdx, Number(column.key), nextValue);
|
||||
}, [context, resultColumn, column.key, rowIdx, value, disabled]);
|
||||
tableDataContext.editor
|
||||
.setCell(rowIdx, column.columnDataIndex, nextValue);
|
||||
}, [tableDataContext, column, rowIdx, value, disabled]);
|
||||
|
||||
return styled(styles)(
|
||||
<boolean-formatter
|
||||
|
||||
+14
-9
@@ -12,11 +12,11 @@ import type { FormatterProps } from 'react-data-grid';
|
||||
|
||||
import { IconOrImage } from '@cloudbeaver/core-blocks';
|
||||
import { isValidUrl } from '@cloudbeaver/core-utils';
|
||||
import { ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { EditingContext } from '../../../Editing/EditingContext';
|
||||
import { CellEditor, IEditorRef } from '../../CellEditor/CellEditor';
|
||||
import { DataGridContext } from '../../DataGridContext';
|
||||
import { TableDataContext } from '../../TableDataContext';
|
||||
|
||||
function getClasses(rawValue: any) {
|
||||
const classes = ['text-formatter'];
|
||||
@@ -26,22 +26,28 @@ function getClasses(rawValue: any) {
|
||||
return classes.join(' '); // performance heavy
|
||||
}
|
||||
|
||||
export const TextFormatter: React.FC<FormatterProps> = observer(function TextFormatter({ rowIdx, row, column, isCellSelected }) {
|
||||
export const TextFormatter: React.FC<FormatterProps> = observer(function TextFormatter({ rowIdx, column, isCellSelected }) {
|
||||
const editorRef = useRef<IEditorRef>(null);
|
||||
const context = useContext(DataGridContext);
|
||||
const editingContext = useContext(EditingContext);
|
||||
const formatter = context?.model.source.getAction(context.resultIndex, ResultSetFormatAction);
|
||||
const rawValue = formatter?.get(row[column.key]) ?? row[column.key];
|
||||
const tableDataContext = useContext(TableDataContext);
|
||||
|
||||
if (!context || !tableDataContext || !editingContext || column.columnDataIndex === null) {
|
||||
throw new Error('Contexts required');
|
||||
}
|
||||
|
||||
const formatter = tableDataContext.format;
|
||||
const rawValue = formatter.get(tableDataContext.getCellValue(rowIdx, column.columnDataIndex)!);
|
||||
const classes = getClasses(rawValue);
|
||||
const value = formatter?.toDisplayString(rawValue) ?? String(rawValue);
|
||||
const value = formatter.toDisplayString(rawValue);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
editingContext?.closeEditor({ idx: column.idx, rowIdx });
|
||||
editingContext.closeEditor({ idx: column.idx, rowIdx });
|
||||
}, [column, rowIdx]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isCellSelected) {
|
||||
if (editingContext?.isEditing({ idx: column.idx, rowIdx })) {
|
||||
if (editingContext.isEditing({ idx: column.idx, rowIdx })) {
|
||||
editorRef.current?.focus();
|
||||
}
|
||||
}
|
||||
@@ -53,7 +59,6 @@ export const TextFormatter: React.FC<FormatterProps> = observer(function TextFor
|
||||
<CellEditor
|
||||
ref={editorRef}
|
||||
rowIdx={rowIdx}
|
||||
row={row}
|
||||
column={column}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
@@ -66,7 +71,7 @@ export const TextFormatter: React.FC<FormatterProps> = observer(function TextFor
|
||||
return (
|
||||
<div title={value} className={classes}>
|
||||
{isUrl && (
|
||||
<a href={rawValue} target='_blank' rel='noreferrer' draggable={false}>
|
||||
<a href={rawValue as string} target='_blank' rel='noreferrer' draggable={false}>
|
||||
<IconOrImage icon='external-link' viewBox='0 0 24 24' />
|
||||
</a>
|
||||
)}
|
||||
|
||||
+27
-13
@@ -13,7 +13,6 @@ import styled, { css } from 'reshadow';
|
||||
|
||||
import { StaticImage } from '@cloudbeaver/core-blocks';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { ResultSetDataAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { DataGridContext } from '../DataGridContext';
|
||||
import { DataGridSelectionContext } from '../DataGridSelection/DataGridSelectionContext';
|
||||
@@ -73,16 +72,31 @@ export const TableColumnHeader: React.FC<HeaderRendererProps<any>> = observer(fu
|
||||
|
||||
const resultIndex = dataGridContext.resultIndex;
|
||||
const model = dataGridContext.model;
|
||||
const data = model.source.getAction(resultIndex, ResultSetDataAction);
|
||||
const column = data.getColumn(Number(calculatedColumn.key));
|
||||
const columnName = calculatedColumn.name as string;
|
||||
let icon = calculatedColumn.icon;
|
||||
let columnName = calculatedColumn.name as string;
|
||||
const dataReadonly = tableDataContext.isReadOnly() || model.isReadonly();
|
||||
let columnReadOnly = !calculatedColumn.editable;
|
||||
let columnTooltip: string = columnName;
|
||||
|
||||
// TODO we want to get "sortable" property from SqlResultColumn data
|
||||
const sortable = model.source.results.length === 1 && !model.source.isDisabled(resultIndex);
|
||||
const readOnly = !tableDataContext.isReadOnly() && column?.readOnly;
|
||||
const readOnlyStatus = column?.readOnlyStatus ? `(Read-only: ${column.readOnlyStatus})` : '';
|
||||
const columnTooltip = `${columnName}${column?.fullTypeName ? ': ' + column.fullTypeName : ''} ${readOnlyStatus}`;
|
||||
if (calculatedColumn.columnDataIndex !== null) {
|
||||
const column = tableDataContext.data.getColumn(calculatedColumn.columnDataIndex);
|
||||
|
||||
if (column) {
|
||||
columnName = column.label!;
|
||||
icon = column.icon;
|
||||
columnReadOnly ||= tableDataContext.format.isReadOnly({ column: calculatedColumn.columnDataIndex });
|
||||
|
||||
columnTooltip = columnName;
|
||||
|
||||
if (column.fullTypeName) {
|
||||
columnTooltip += `: ${column.fullTypeName}`;
|
||||
}
|
||||
|
||||
if (column.readOnlyStatus) {
|
||||
columnTooltip += ` (Read-only: ${column.readOnlyStatus})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleColumnSelection = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
gridSelectionContext.selectColumn(calculatedColumn.idx, e.ctrlKey || e.metaKey);
|
||||
};
|
||||
@@ -91,13 +105,13 @@ export const TableColumnHeader: React.FC<HeaderRendererProps<any>> = observer(fu
|
||||
<table-header>
|
||||
<shrink-container as='div' title={columnTooltip} onClick={handleColumnSelection}>
|
||||
<icon>
|
||||
<StaticImage icon={column?.icon} />
|
||||
{readOnly && <readonly-status className='rdg-table-header__readonly-status' />}
|
||||
{icon && <StaticImage icon={icon} />}
|
||||
{!dataReadonly && columnReadOnly && <readonly-status className='rdg-table-header__readonly-status' />}
|
||||
</icon>
|
||||
<name>{columnName}</name>
|
||||
</shrink-container>
|
||||
{sortable && column?.label && (
|
||||
<OrderButton model={model} resultIndex={resultIndex} attribute={column.label} />
|
||||
{!(dataReadonly || columnReadOnly) && calculatedColumn.columnDataIndex !== null && (
|
||||
<OrderButton model={model} resultIndex={resultIndex} attribute={columnName} />
|
||||
)}
|
||||
</table-header>
|
||||
);
|
||||
|
||||
@@ -10,23 +10,32 @@ import { createContext } from 'react';
|
||||
import type { Column } from 'react-data-grid';
|
||||
|
||||
import type { SqlResultColumn } from '@cloudbeaver/core-sdk';
|
||||
import type { IDatabaseDataResultEditor, IDatabaseResultSet, IResultSetValue, ResultSetDataAction, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
declare module 'react-data-grid' {
|
||||
interface Column<TRow, TSummaryRow = unknown> {
|
||||
columnDataIndex: null | number;
|
||||
icon?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITableData {
|
||||
data: {
|
||||
columns: Array<Column<any[], any>>;
|
||||
rows: any[][];
|
||||
};
|
||||
format: ResultSetFormatAction;
|
||||
data: ResultSetDataAction;
|
||||
editor: IDatabaseDataResultEditor<IDatabaseResultSet>;
|
||||
columns: Array<Column<any[], any>>;
|
||||
dataColumns: SqlResultColumn[];
|
||||
dataRows: any[][];
|
||||
getCellValue: (rowIndex: number, key: string | number) => any;
|
||||
getColumnInfo: (key: string | number) => SqlResultColumn | undefined;
|
||||
dataRows: IResultSetValue[][];
|
||||
getColumn: (columnIndex: number) => Column<any[], any>;
|
||||
getColumnByDataIndex: (columnDataIndex: number) => Column<any[], any>;
|
||||
getCellValue: (rowIndex: number, columnIndex: number) => IResultSetValue | undefined;
|
||||
getColumnInfo: (columnDataIndex: 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;
|
||||
getColumnIndexFromKey: (columnKey: string) => number;
|
||||
isCellEdited: (rowIndex: number, column: Column<any[], any>) => boolean;
|
||||
isIndexColumn: (columnKey: string) => boolean;
|
||||
isIndexColumnInRange: (columnsRange: Array<Column<any[], any>>) => boolean;
|
||||
isReadOnly: () => boolean;
|
||||
getColumnKeyFromColumnIndex: (columnIndex: number) => number;
|
||||
}
|
||||
|
||||
export const TableDataContext = createContext<ITableData | null>(null);
|
||||
|
||||
+5
-7
@@ -26,7 +26,6 @@ function copyGridSelectedDataToClipboard(
|
||||
selectedCells: Map<number, number[]>
|
||||
) {
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const editor = model.source.getEditor(resultIndex);
|
||||
|
||||
const orderedSelectedCells: Map<number, number[]> = new Map([...selectedCells].sort((a, b) => a[0] - b[0]));
|
||||
|
||||
@@ -40,15 +39,14 @@ function copyGridSelectedDataToClipboard(
|
||||
const rowsValues: string[] = [];
|
||||
for (const [rowIdx, colIndexes] of orderedSelectedCells) {
|
||||
const rowCellsValues: string[] = [];
|
||||
for (const column of tableData.data.columns) {
|
||||
const columnIdx = tableData.getDataColumnIndexFromKey(column.key);
|
||||
if (columnIdx === null || !selectedColumns.has(columnIdx)) {
|
||||
for (const column of tableData.columns) {
|
||||
if (column.columnDataIndex === null || !selectedColumns.has(column.columnDataIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (colIndexes.includes(columnIdx)) {
|
||||
const cell = editor.getCell(rowIdx, columnIdx);
|
||||
const cellValue = format.getText(cell);
|
||||
if (colIndexes.includes(column.columnDataIndex)) {
|
||||
const cell = tableData.getCellValue(rowIdx, column.columnDataIndex);
|
||||
const cellValue = cell !== undefined ? format.getText(cell) : undefined;
|
||||
rowCellsValues.push(cellValue ?? '');
|
||||
} else {
|
||||
rowCellsValues.push('');
|
||||
|
||||
@@ -11,17 +11,17 @@ 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 { IDatabaseDataModel, IDatabaseResultSet, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
import { TextTools, uuid } from '@cloudbeaver/core-utils';
|
||||
import { IDatabaseDataModel, IDatabaseResultSet, ResultSetDataAction, ResultSetFormatAction } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
import { ResultSetTools } from '../ResultSetTools';
|
||||
import { IndexFormatter } from './Formatters/IndexFormatter';
|
||||
import { TableColumnHeader } from './TableColumnHeader/TableColumnHeader';
|
||||
import { TableIndexColumnHeader } from './TableColumnHeader/TableIndexColumnHeader';
|
||||
import type { ITableData } from './TableDataContext';
|
||||
|
||||
export const indexColumn: Column<any[], any> = {
|
||||
key: String(Number.MAX_SAFE_INTEGER),
|
||||
key: '#',
|
||||
columnDataIndex: null,
|
||||
name: '#',
|
||||
minWidth: 60,
|
||||
width: 60,
|
||||
@@ -32,36 +32,44 @@ export const indexColumn: Column<any[], any> = {
|
||||
};
|
||||
|
||||
export function useTableData(model: IDatabaseDataModel<any, IDatabaseResultSet>, resultIndex: number): ITableData {
|
||||
const modelResultData = model.getResult(resultIndex);
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const data = model.source.getAction(resultIndex, ResultSetDataAction);
|
||||
const editor = model.source.getEditor(resultIndex);
|
||||
|
||||
const props = useObjectRef({
|
||||
modelResultData,
|
||||
model,
|
||||
resultIndex,
|
||||
format,
|
||||
data,
|
||||
editor,
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
modelResultData: observable.ref,
|
||||
model: observable.ref,
|
||||
resultIndex: observable.ref,
|
||||
format: observable.ref,
|
||||
data: observable.ref,
|
||||
editor: observable.ref,
|
||||
});
|
||||
|
||||
return useObjectRef({
|
||||
get dataColumns() {
|
||||
return props.modelResultData?.data?.columns || [];
|
||||
},
|
||||
get dataRows() {
|
||||
return props.modelResultData?.data?.rows || [];
|
||||
get format() {
|
||||
return props.format;
|
||||
},
|
||||
get data() {
|
||||
if (!props.modelResultData?.data) {
|
||||
return { columns: [], rows: [] };
|
||||
return props.data;
|
||||
},
|
||||
get editor() {
|
||||
return props.editor;
|
||||
},
|
||||
get dataColumns() {
|
||||
return this.data.columns;
|
||||
},
|
||||
get dataRows() {
|
||||
return this.data.rows;
|
||||
},
|
||||
get columns() {
|
||||
if (this.dataColumns.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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);
|
||||
const columnNames = this.format.getHeaders();
|
||||
const rowStrings = this.format.getLongestCells();
|
||||
|
||||
// TODO: seems better to do not measure container size
|
||||
// for detecting max columns size, better to use configurable variable
|
||||
@@ -75,54 +83,51 @@ export function useTableData(model: IDatabaseDataModel<any, IDatabaseResultSet>,
|
||||
}),
|
||||
}).map(v => v + 16 + 32 + 20);
|
||||
|
||||
// TODO: we need some result type specified formatter to common actions with data
|
||||
const rows = props.modelResultData.data?.rows || [];
|
||||
|
||||
const columns = props.modelResultData.data?.columns!.map<Column<any[], any>>((col, columnIndex) => ({
|
||||
key: columnIndex + '',
|
||||
const columns: Array<Column<any[], any>> = this.dataColumns.map<Column<any[], any>>((col, columnIndex) => ({
|
||||
key: uuid(),
|
||||
columnDataIndex: columnIndex,
|
||||
name: col.label!,
|
||||
editable: !format.isReadOnly({ column: columnIndex }),
|
||||
editable: true,
|
||||
width: Math.min(300, measuredCells[columnIndex]),
|
||||
headerRenderer: TableColumnHeader,
|
||||
})) || [];
|
||||
}));
|
||||
columns.unshift(indexColumn);
|
||||
|
||||
return { rows, columns };
|
||||
return columns;
|
||||
},
|
||||
getCellValue(rowIndex: number, key: string | number): any {
|
||||
return this.data.rows[rowIndex][key as number];
|
||||
getColumn(columnIndex: number) {
|
||||
return this.columns[columnIndex];
|
||||
},
|
||||
getColumnInfo(key: string | number): SqlResultColumn | undefined {
|
||||
if (this.isIndexColumn(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.dataColumns[Number(key)]; // performance heavy
|
||||
getColumnByDataIndex(columnDataIndex: number) {
|
||||
return this.columns.find(column => column.columnDataIndex === columnDataIndex)!;
|
||||
},
|
||||
getDataColumnIndexFromKey(key: string | number) {
|
||||
const info = this.getColumnInfo(key);
|
||||
|
||||
if (!info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Number(key);
|
||||
getColumnInfo(columnDataIndex: number): SqlResultColumn | undefined {
|
||||
return this.data.getColumn(columnDataIndex);
|
||||
},
|
||||
getColumnIndexFromKey(key: string | number) {
|
||||
const index = this.data.columns.findIndex((column: any) => column.key === String(key));
|
||||
return index === -1 ? null : index;
|
||||
getCellValue(row: number, column: number) {
|
||||
return this.editor.getCell(row, column);
|
||||
// return props.data.getCellValue({ column, row });
|
||||
},
|
||||
getColumnIndexFromKey(key: string) {
|
||||
return this.columns.findIndex(column => column.key === key);
|
||||
},
|
||||
getColumnsInRange(startIndex: number, endIndex: number) {
|
||||
if (startIndex === endIndex) {
|
||||
return [this.data.columns[startIndex]];
|
||||
return [this.columns[startIndex]];
|
||||
}
|
||||
|
||||
const firstIndex = Math.min(startIndex, endIndex);
|
||||
const lastIndex = Math.max(startIndex, endIndex);
|
||||
return this.data.columns.slice(firstIndex, lastIndex + 1);
|
||||
return this.columns.slice(firstIndex, lastIndex + 1);
|
||||
},
|
||||
isIndexColumn(columnKey: string | number) {
|
||||
return String(columnKey) === indexColumn.key;
|
||||
isCellEdited(rowIndex: number, column: Column<any[], any>) {
|
||||
if (column.columnDataIndex === null) {
|
||||
return false;
|
||||
}
|
||||
return this.editor.isCellEdited(rowIndex, column.columnDataIndex);
|
||||
},
|
||||
isIndexColumn(columnKey: string) {
|
||||
return columnKey === indexColumn.key;
|
||||
},
|
||||
isIndexColumnInRange(columnsRange: Array<Column<any[], any>>) {
|
||||
return columnsRange.some(column => this.isIndexColumn(column.key));
|
||||
@@ -130,11 +135,8 @@ export function useTableData(model: IDatabaseDataModel<any, IDatabaseResultSet>,
|
||||
isReadOnly() {
|
||||
return this.dataColumns.every(column => column.readOnly);
|
||||
},
|
||||
getColumnKeyFromColumnIndex(columnIndex: number) {
|
||||
return Number(this.data.columns[columnIndex].key);
|
||||
},
|
||||
}, null, {
|
||||
data: computed,
|
||||
columns: computed,
|
||||
dataColumns: computed,
|
||||
dataRows: computed,
|
||||
});
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import type { SqlResultSet } from '@cloudbeaver/core-sdk';
|
||||
import { isResultSetContentValue } from '@cloudbeaver/plugin-data-viewer';
|
||||
|
||||
export const ResultSetTools = {
|
||||
getHeaders(resultSet: SqlResultSet): string[] {
|
||||
return resultSet.columns?.map(column => column.name!).filter(Boolean) || [];
|
||||
},
|
||||
getRows(resultSet: SqlResultSet, offset = 0, count?: number): any[][] {
|
||||
return resultSet.rows?.slice(offset, count) || [];
|
||||
},
|
||||
getLongestCells(resultSet: SqlResultSet, offset = 0, count?: number): string[] {
|
||||
const rows = this.getRows(resultSet, offset, count);
|
||||
let cells: string[] = [];
|
||||
|
||||
function getStringValue(value: any): string {
|
||||
if (isResultSetContentValue(value) && value.text !== undefined) {
|
||||
return value.text;
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
if (cells.length === 0) {
|
||||
cells = row.map(v => getStringValue(v));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const value = getStringValue(row[i]);
|
||||
|
||||
if (value.length > cells[i].length) {
|
||||
cells[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
},
|
||||
};
|
||||
+34
-8
@@ -6,27 +6,52 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
import { computed, makeObservable } from 'mobx';
|
||||
|
||||
import { DataTypeLogicalOperation, ResultDataFormat, SqlResultColumn } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DatabaseDataAction } from '../../DatabaseDataAction';
|
||||
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { databaseDataAction } from '../DatabaseDataActionDecorator';
|
||||
import type { IDatabaseDataResultAction } from '../IDatabaseDataResultAction';
|
||||
import type { IResultSetContentValue } from './IResultSetContentValue';
|
||||
import type { IResultSetElementKey } from './IResultSetElementKey';
|
||||
import { isResultSetContentValue } from './isResultSetContentValue';
|
||||
import type { IResultSetValue } from './ResultSetFormatAction';
|
||||
|
||||
@databaseDataAction()
|
||||
export class ResultSetDataAction extends DatabaseDataAction<any, IDatabaseResultSet>
|
||||
implements IDatabaseDataResultAction<IDatabaseResultSet> {
|
||||
static dataFormat = ResultDataFormat.Resultset;
|
||||
|
||||
getCellValue(cell: IResultSetElementKey): any {
|
||||
if (cell.row === undefined || cell.column === undefined || !this.result.data?.rows) {
|
||||
get rows(): IResultSetValue[][] {
|
||||
return this.result.data?.rows || [];
|
||||
}
|
||||
|
||||
get columns(): SqlResultColumn[] {
|
||||
return this.result.data?.columns || [];
|
||||
}
|
||||
|
||||
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>, result: IDatabaseResultSet) {
|
||||
super(source, result);
|
||||
makeObservable(this, {
|
||||
rows: computed,
|
||||
columns: computed,
|
||||
});
|
||||
}
|
||||
|
||||
getCellValue(cell: IResultSetElementKey): IResultSetValue | undefined {
|
||||
if (
|
||||
cell.row === undefined
|
||||
|| cell.column === undefined
|
||||
|| cell.row >= this.rows.length
|
||||
|| cell.column >= this.columns.length
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.result.data.rows?.[cell.row]?.[cell.column];
|
||||
return this.rows[cell.row][cell.column];
|
||||
}
|
||||
|
||||
getContent(cell: IResultSetElementKey): IResultSetContentValue | null {
|
||||
@@ -39,16 +64,17 @@ export class ResultSetDataAction extends DatabaseDataAction<any, IDatabaseResult
|
||||
return null;
|
||||
}
|
||||
|
||||
getColumn(columnIndex: number) {
|
||||
if (!this.result.data?.columns) {
|
||||
getColumn(columnIndex: number): SqlResultColumn | undefined {
|
||||
if (columnIndex >= this.columns.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.result.data.columns[columnIndex];
|
||||
return this.columns[columnIndex];
|
||||
}
|
||||
|
||||
getColumnOperations(columnIndex: number) {
|
||||
getColumnOperations(columnIndex: number): DataTypeLogicalOperation[] {
|
||||
const column = this.getColumn(columnIndex);
|
||||
|
||||
if (!column) {
|
||||
return [];
|
||||
}
|
||||
|
||||
+36
-4
@@ -9,6 +9,7 @@
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DatabaseDataAction } from '../../DatabaseDataAction';
|
||||
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { databaseDataAction } from '../DatabaseDataActionDecorator';
|
||||
import type { IDatabaseDataFormatAction } from '../IDatabaseDataFormatAction';
|
||||
@@ -24,17 +25,48 @@ export class ResultSetFormatAction extends DatabaseDataAction<any, IDatabaseResu
|
||||
implements IDatabaseDataFormatAction<IResultSetElementKey, IDatabaseResultSet> {
|
||||
static dataFormat = ResultDataFormat.Resultset;
|
||||
|
||||
private data: ResultSetDataAction;
|
||||
|
||||
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>, result: IDatabaseResultSet) {
|
||||
super(source, result);
|
||||
this.data = this.getAction(ResultSetDataAction);
|
||||
}
|
||||
|
||||
getHeaders(): string[] {
|
||||
return this.data.columns.map(column => column.name!).filter(Boolean);
|
||||
}
|
||||
|
||||
getLongestCells(offset = 0, count?: number): string[] {
|
||||
const rows = this.data.rows.slice(offset, count);
|
||||
let cells: string[] = [];
|
||||
|
||||
for (const row of rows) {
|
||||
if (cells.length === 0) {
|
||||
cells = row.map(v => this.toDisplayString(v));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < row.length; i++) {
|
||||
const value = this.toDisplayString(row[i]);
|
||||
|
||||
if (value.length > cells[i].length) {
|
||||
cells[i] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
isReadOnly(key: IResultSetElementKey): boolean {
|
||||
let columnReadonly = false;
|
||||
let cellReadonly = false;
|
||||
|
||||
const data = this.getAction(ResultSetDataAction);
|
||||
|
||||
if (key.column !== undefined) {
|
||||
columnReadonly = data.getColumn(key.column)?.readOnly || false;
|
||||
columnReadonly = this.data.getColumn(key.column)?.readOnly || false;
|
||||
}
|
||||
|
||||
const value = data.getCellValue(key);
|
||||
const value = this.data.getCellValue(key);
|
||||
|
||||
if (isResultSetContentValue(value)) {
|
||||
cellReadonly = (
|
||||
|
||||
+7
-4
@@ -17,6 +17,7 @@ import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { databaseDataAction } from '../DatabaseDataActionDecorator';
|
||||
import type { DatabaseDataSelectActionsData, IDatabaseDataSelectAction } from '../IDatabaseDataSelectAction';
|
||||
import type { IResultSetElementKey } from './IResultSetElementKey';
|
||||
import { ResultSetDataAction } from './ResultSetDataAction';
|
||||
|
||||
@databaseDataAction()
|
||||
export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResultSet>
|
||||
@@ -27,9 +28,11 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
|
||||
readonly selectedElements: Map<number, number[]>;
|
||||
|
||||
private focusedElement: IResultSetElementKey | null;
|
||||
private data: ResultSetDataAction;
|
||||
|
||||
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>, result: IDatabaseResultSet) {
|
||||
super(source, result);
|
||||
this.data = this.getAction(ResultSetDataAction);
|
||||
this.actions = new Executor();
|
||||
this.selectedElements = new Map();
|
||||
this.focusedElement = null;
|
||||
@@ -46,7 +49,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
|
||||
|
||||
isElementSelected(key: IResultSetElementKey): boolean {
|
||||
if (key.row === undefined) {
|
||||
const rows = this.result.data?.rows?.length || 0;
|
||||
const rows = this.data.rows.length;
|
||||
for (let row = 0; row < rows; row++) {
|
||||
if (!this.isElementSelected({ row, column: key.column })) {
|
||||
return false;
|
||||
@@ -66,7 +69,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
|
||||
return row.includes(key.column);
|
||||
}
|
||||
|
||||
return row.length === this.result.data?.columns?.length;
|
||||
return row.length === this.data.columns.length;
|
||||
}
|
||||
|
||||
getFocusedElement(): IResultSetElementKey | null {
|
||||
@@ -89,7 +92,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
|
||||
|
||||
set(key: IResultSetElementKey, selected: boolean): void {
|
||||
if (key.row === undefined) {
|
||||
for (let row = 0; row < (this.result.data?.rows?.length || 0); row++) {
|
||||
for (let row = 0; row < this.data.rows.length; row++) {
|
||||
this.set({ row, column: key.column }, selected);
|
||||
}
|
||||
|
||||
@@ -97,7 +100,7 @@ export class ResultSetSelectAction extends DatabaseDataAction<any, IDatabaseResu
|
||||
}
|
||||
|
||||
if (key.column === undefined) {
|
||||
for (let column = 0; column < (this.result.data?.columns?.length || 0); column++) {
|
||||
for (let column = 0; column < this.data.columns.length; column++) {
|
||||
this.set({ row: key.row, column }, selected);
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -34,7 +34,7 @@ export class DatabaseDataEditor<TResult extends IDatabaseDataResult> implements
|
||||
const update = this.editedResults.get(result.id);
|
||||
const diff = update?.diff.get(row);
|
||||
|
||||
return !!diff?.source.some((value, index) => !this.compareCellValue(value, diff.update[index]));
|
||||
return diff !== undefined;
|
||||
}
|
||||
|
||||
isResultEdited(result: TResult): boolean {
|
||||
|
||||
Reference in New Issue
Block a user