feat: CB-675 table states

This commit is contained in:
Wroud
2021-02-19 19:54:43 +03:00
parent 23d9a0a6d5
commit 4176535998
11 changed files with 90 additions and 44 deletions
@@ -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]);
@@ -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>
@@ -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;