mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat: CB-675 table states
This commit is contained in:
@@ -41,6 +41,10 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
|
||||
}, [column, rowIdx, selectionContext]);
|
||||
|
||||
const handleDoubleClick = useCallback(() => {
|
||||
if (!column.editable) {
|
||||
return;
|
||||
}
|
||||
|
||||
editingContext?.edit({ idx: column.idx, rowIdx });
|
||||
}, [column, rowIdx]);
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
import { action, observable } from 'mobx';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import type { ITableData } from '../useTableData';
|
||||
import type { ITableData } from '../TableDataContext';
|
||||
import type { IDataGridSelectionContext } from './DataGridSelectionContext';
|
||||
|
||||
export interface IPosition {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { DataGridSortingContext } from './DataGridSorting/DataGridSortingContext
|
||||
import { useGridSortingContext } from './DataGridSorting/useGridSortingContext';
|
||||
import { CellFormatter } from './Formatters/CellFormatter';
|
||||
import { RowRenderer } from './RowRenderer/RowRenderer';
|
||||
import { TableDataContext } from './TableDataContext';
|
||||
import { useGridDragging } from './useGridDragging';
|
||||
import { useGridSelectedCellsCopy } from './useGridSelectedCellsCopy';
|
||||
import { useTableData } from './useTableData';
|
||||
@@ -56,6 +57,7 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
|
||||
const gridSortingContext = useGridSortingContext(model);
|
||||
const gridSelectionContext = useGridSelectionContext(tableData);
|
||||
const editingContext = useEditing({
|
||||
readonly: model.isReadonly(),
|
||||
onEdit: (position, key) => {
|
||||
const editor = model.source.getEditor(resultIndex);
|
||||
|
||||
@@ -149,33 +151,35 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
|
||||
<DataGridSortingContext.Provider value={gridSortingContext}>
|
||||
<DataGridSelectionContext.Provider value={gridSelectionContext}>
|
||||
<EditingContext.Provider value={editingContext}>
|
||||
<grid-container
|
||||
as='div'
|
||||
className="cb-react-grid-container"
|
||||
tabIndex={-1}
|
||||
onKeyDown={onKeydownHandler}
|
||||
onMouseDown={onMouseDownHandler}
|
||||
onMouseMove={onMouseMoveHandler}
|
||||
>
|
||||
<DataGrid
|
||||
ref={dataGridRef}
|
||||
className={`cb-react-grid-theme ${className}`}
|
||||
columns={tableData.columns}
|
||||
defaultColumnOptions={{
|
||||
minWidth: 40,
|
||||
resizable: true,
|
||||
formatter: CellFormatter,
|
||||
}}
|
||||
rows={tableData.rows}
|
||||
headerRowHeight={28}
|
||||
rowHeight={24}
|
||||
rowRenderer={RowRenderer}
|
||||
onSelectedCellChange={handleFocusChange}
|
||||
onColumnResize={(idx, width) => columnResize.execute({ column: idx, width })}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
<div ref={editorRef} />
|
||||
</grid-container>
|
||||
<TableDataContext.Provider value={tableData}>
|
||||
<grid-container
|
||||
as='div'
|
||||
className="cb-react-grid-container"
|
||||
tabIndex={-1}
|
||||
onKeyDown={onKeydownHandler}
|
||||
onMouseDown={onMouseDownHandler}
|
||||
onMouseMove={onMouseMoveHandler}
|
||||
>
|
||||
<DataGrid
|
||||
ref={dataGridRef}
|
||||
className={`cb-react-grid-theme ${className}`}
|
||||
columns={tableData.columns}
|
||||
defaultColumnOptions={{
|
||||
minWidth: 40,
|
||||
resizable: true,
|
||||
formatter: CellFormatter,
|
||||
}}
|
||||
rows={tableData.rows}
|
||||
headerRowHeight={28}
|
||||
rowHeight={24}
|
||||
rowRenderer={RowRenderer}
|
||||
onSelectedCellChange={handleFocusChange}
|
||||
onColumnResize={(idx, width) => columnResize.execute({ column: idx, width })}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
<div ref={editorRef} />
|
||||
</grid-container>
|
||||
</TableDataContext.Provider>
|
||||
</EditingContext.Provider>
|
||||
</DataGridSelectionContext.Provider>
|
||||
</DataGridSortingContext.Provider>
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ export const TableColumnHeader: React.FC<HeaderRendererProps<any>> = observer(fu
|
||||
const loading = model.isLoading();
|
||||
|
||||
// TODO we want to get "sortable" property from SqlResultColumn data
|
||||
const sortable = true;
|
||||
const sortable = model.source.results.length === 1;
|
||||
const currentSortMode = gridSortingContext.getSortMode(columnName);
|
||||
|
||||
const handleSort = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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 { createContext } from 'react';
|
||||
import type { Column } from 'react-data-grid';
|
||||
|
||||
import type { SqlResultColumn } from '@cloudbeaver/core-sdk';
|
||||
|
||||
export interface ITableData {
|
||||
columns: Array<Column<any[], any>>;
|
||||
rows: any[][];
|
||||
getColumnInfo: (key: string | number) => SqlResultColumn | undefined;
|
||||
getColumnsInRange: (startIndex: number, endIndex: number) => Array<Column<any[], any>>;
|
||||
getColumnIndexFromKey: (columnKey: string | number) => number;
|
||||
isIndexColumn: (columnKey: string) => boolean;
|
||||
isIndexColumnInRange: (columnsRange: Array<Column<any[], any>>) => boolean;
|
||||
getColumnKeyFromColumnIndex: (columnIndex: number) => string;
|
||||
}
|
||||
|
||||
export const TableDataContext = createContext<ITableData | null>(null);
|
||||
@@ -11,6 +11,7 @@ import { useState } from 'react';
|
||||
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';
|
||||
|
||||
@@ -18,6 +19,7 @@ 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: Number.MAX_SAFE_INTEGER + '',
|
||||
@@ -30,16 +32,6 @@ export const indexColumn: Column<any[], any> = {
|
||||
formatter: IndexFormatter,
|
||||
};
|
||||
|
||||
export interface ITableData {
|
||||
columns: Array<Column<any[], any>>;
|
||||
rows: any[][];
|
||||
getColumnsInRange: (startIndex: number, endIndex: number) => Array<Column<any[], any>>;
|
||||
getColumnIndexFromKey: (columnKey: string | number) => number;
|
||||
isIndexColumn: (columnKey: string) => boolean;
|
||||
isIndexColumnInRange: (columnsRange: Array<Column<any[], any>>) => boolean;
|
||||
getColumnKeyFromColumnIndex: (columnIndex: number) => string;
|
||||
}
|
||||
|
||||
export function useTableData(modelResultData: IDatabaseResultSet | null): ITableData {
|
||||
const props = useObjectRef({ modelResultData }, undefined, true);
|
||||
const [state] = useState(() => computed(() => {
|
||||
@@ -68,6 +60,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,
|
||||
width: Math.min(300, measuredCells[columnIndex]),
|
||||
headerRenderer: TableColumnHeader,
|
||||
})) || [];
|
||||
@@ -81,17 +74,20 @@ export function useTableData(modelResultData: IDatabaseResultSet | null): ITable
|
||||
return useObjectRef({
|
||||
columns,
|
||||
rows,
|
||||
getColumnInfo(key: string | number): SqlResultColumn | undefined {
|
||||
return props.modelResultData?.data?.columns?.[Number(key)];
|
||||
},
|
||||
getColumnIndexFromKey(key: string | number) {
|
||||
return columns.findIndex((column: any) => column.key === String(key));
|
||||
return this.columns.findIndex((column: any) => column.key === String(key));
|
||||
},
|
||||
getColumnsInRange(startIndex: number, endIndex: number) {
|
||||
if (startIndex === endIndex) {
|
||||
return [columns[startIndex]];
|
||||
return [this.columns[startIndex]];
|
||||
}
|
||||
|
||||
const firstIndex = Math.min(startIndex, endIndex);
|
||||
const lastIndex = Math.max(startIndex, endIndex);
|
||||
return columns.slice(firstIndex, lastIndex + 1);
|
||||
return this.columns.slice(firstIndex, lastIndex + 1);
|
||||
},
|
||||
isIndexColumn(columnKey: string) {
|
||||
return columnKey === indexColumn.key;
|
||||
@@ -100,7 +96,10 @@ export function useTableData(modelResultData: IDatabaseResultSet | null): ITable
|
||||
return columnsRange.some(column => this.isIndexColumn(column.key));
|
||||
},
|
||||
getColumnKeyFromColumnIndex(columnIndex: number) {
|
||||
return columns[columnIndex].key;
|
||||
return this.columns[columnIndex].key;
|
||||
},
|
||||
});
|
||||
}, {
|
||||
columns,
|
||||
rows,
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ function findPosition(position: Position): (position: Position) => boolean {
|
||||
}
|
||||
|
||||
interface IEditingOptions {
|
||||
readonly?: boolean;
|
||||
onEdit: (position: Position, key?: string) => boolean;
|
||||
}
|
||||
|
||||
@@ -28,6 +29,9 @@ export function useEditing(options: IEditingOptions): IEditingContext {
|
||||
|
||||
const [context] = useState<IEditingContext>({
|
||||
edit(position: Position, key?: string) {
|
||||
if (optionsRef.readonly) {
|
||||
return;
|
||||
}
|
||||
// TODO: not works yet
|
||||
switch (key) {
|
||||
case 'Escape':
|
||||
|
||||
@@ -43,6 +43,10 @@ implements IDatabaseDataModel<TOptions, TResult> {
|
||||
return this.source.isLoading();
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return this.source.isReadonly();
|
||||
}
|
||||
|
||||
isDataAvailable(offset: number, count: number): boolean {
|
||||
return this.source.offset <= offset && this.source.count >= count;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ implements IDatabaseDataSource<TOptions, TResult> {
|
||||
return this;
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return this.access === DatabaseDataAccessMode.Readonly || this.results.length > 1;
|
||||
}
|
||||
|
||||
isLoading(): boolean {
|
||||
return !!this.activeRequest || !!this.activeSave;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface IDatabaseDataModel<TOptions, TResult extends IDatabaseDataResul
|
||||
readonly supportedDataFormats: ResultDataFormat[];
|
||||
readonly countGain: number;
|
||||
|
||||
isReadonly: () => boolean;
|
||||
isLoading: () => boolean;
|
||||
isDataAvailable: (offset: number, count: number) => boolean;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface IDatabaseDataSource<TOptions, TResult extends IDatabaseDataResu
|
||||
readonly executionContext: IExecutionContext | null;
|
||||
readonly canCancel: boolean;
|
||||
|
||||
isReadonly: () => boolean;
|
||||
isLoading: () => boolean;
|
||||
|
||||
hasResult: (resultIndex: number) => boolean;
|
||||
|
||||
Reference in New Issue
Block a user