feat(plugin-data-spreadsheet-new): CB-695 add cell formatter and custom styles

This commit is contained in:
Naumov Alexey
2021-01-31 21:33:27 +03:00
parent 06f0594cc5
commit c8559a4784
11 changed files with 235 additions and 35 deletions
@@ -10,32 +10,28 @@ import { observer } from 'mobx-react-lite';
import { useCallback, useContext } from 'react';
import type { CellRendererProps } from 'react-data-grid';
import { Cell } from 'react-data-grid';
import styled, { css, use } from 'reshadow';
import { DataGridSelectionContext } from '../DataGridSelection/DataGridSelectionContext';
const styles = css`
Cell[|selected] {
background-color: rgba(0, 145, 234, 0.3);
}
`;
export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function CellRenderer(props) {
const gridSelectionContext = useContext(DataGridSelectionContext);
if (!gridSelectionContext) {
throw new Error('Grid selection context must be provided');
}
let classes = '';
const { rowIdx, column } = props;
const { isSelected, select } = gridSelectionContext;
const selected = isSelected(column.key, rowIdx);
if (isSelected(column.key, rowIdx)) {
classes += 'rdg-cell-custom-selected';
}
const onClickHandler = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
select(column.key, rowIdx, event.ctrlKey, event.shiftKey);
}, [column, rowIdx, select]);
return styled(styles)(
<Cell onClick={onClickHandler} {...use({ selected })} {...props} />
return (
<Cell className={classes} onClick={onClickHandler} {...props} />
);
});
@@ -6,15 +6,12 @@
* you may not use this file except in compliance with the License.
*/
import 'react-data-grid/dist/react-data-grid.css';
import { computed } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect, useMemo } from 'react';
import DataGrid from 'react-data-grid';
import type { Column } from 'react-data-grid';
import styled from 'reshadow';
import { css } from 'reshadow';
import type { SqlResultSet } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
@@ -22,22 +19,19 @@ import { TextTools } from '@cloudbeaver/core-utils';
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
import { ResultSetTools } from '../ResultSetTools';
import { CellFormatter } from './CellFormatter/CellFormatter';
import baseStyles from '../styles/base.scss';
import { reactGridStyles } from '../styles/styles';
import { DataGridContext } from './DataGridContext';
import { DataGridSelectionContext } from './DataGridSelection/DataGridSelectionContext';
import { useGridSelectionContext } from './DataGridSelection/useGridSelectionContext';
import { DataGridSortingContext } from './DataGridSorting/DataGridSortingContext';
import { useGridSortingContext } from './DataGridSorting/useGridSortingContext';
import { DataGridTableContainer } from './DataGridTableContainer';
import { CellFormatter } from './Formatters/CellFormatter';
import { IndexFormatter } from './Formatters/IndexFormatter';
import { RowRenderer } from './RowRenderer/RowRenderer';
import { TableColumnHeaderNew } from './TableColumnHeader/TableColumnHeader-new';
const baseStyles = css`
DataGrid {
flex: 1;
}
`;
interface Props {
model: IDatabaseDataModel<any>;
resultIndex: number;
@@ -51,15 +45,16 @@ function isAtBottom(event: React.UIEvent<HTMLDivElement>): boolean {
const indexColumn: Column<any[], any> = {
key: Number.MAX_SAFE_INTEGER + '',
name: '#',
width: 100,
name: <div className='cell-index-column-name'>#</div>,
minWidth: 60,
width: 60,
resizable: false,
frozen: true,
formatter: ({ rowIdx }: any) => <b>{rowIdx + 1}</b>,
formatter: IndexFormatter,
};
export const DataGridTable: React.FC<Props> = observer(function DataGridTable({ model, resultIndex }) {
const styles = useStyles(baseStyles);
export const DataGridTable: React.FC<Props> = observer(function DataGridTable({ model, resultIndex, className }) {
const styles = useStyles(reactGridStyles, baseStyles);
const modelResultData = model?.getResult(resultIndex);
@@ -108,7 +103,7 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
}
return rowStrings[i];
}),
}).map(v => v + 16 + 32 + 20 + 24);
}).map(v => v + 16 + 32 + 20);
// TODO: we need some result type specified formatter to common actions with data
const rows = (modelResultData.data as SqlResultSet).rows || [];
@@ -117,6 +112,7 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
key: i + '',
name: col.name!,
width: Math.min(300, measuredCells[i]),
minWidth: 40,
resizable: true,
headerRenderer: TableColumnHeaderNew,
formatter: CellFormatter,
@@ -130,10 +126,11 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
<DataGridContext.Provider value={{ model, resultIndex }}>
<DataGridSortingContext.Provider value={gridSortingContext}>
<DataGridSelectionContext.Provider value={gridSelectionContext}>
<DataGridTableContainer modelResultData={modelResultData}>
<DataGridTableContainer modelResultData={modelResultData} className={`cb-react-grid-theme ${className}`}>
<DataGrid
columns={columns}
rows={rows}
headerRowHeight={28}
rowHeight={24}
rowRenderer={RowRenderer}
onScroll={handleScroll}
@@ -26,6 +26,7 @@ const styles = css`
interface Props {
children: React.ReactNode;
modelResultData: IDatabaseDataResult | null;
className?: string;
}
const EVENT_KEY_CODE = {
@@ -70,7 +71,9 @@ function copyGridSelectedDataToClipboard(modelData: SqlResultSet, selectedCells:
// needed for event.code
type IKeyboardEvent = React.KeyboardEvent<HTMLDivElement> & KeyboardEvent;
export const DataGridTableContainer: React.FC<Props> = function DataGridTableContainer({ modelResultData, children }) {
export const DataGridTableContainer: React.FC<Props> = function DataGridTableContainer({
modelResultData, children, className,
}) {
const selectionContext = useContext(DataGridSelectionContext);
if (!selectionContext) {
@@ -88,7 +91,7 @@ export const DataGridTableContainer: React.FC<Props> = function DataGridTableCon
}, [modelResultData, selectionContext.selectedCells]);
return styled(styles)(
<grid-container as='div' tabIndex={-1} onKeyDown={onKeydownHandler}>
<grid-container as='div' tabIndex={-1} className={className} onKeyDown={onKeydownHandler}>
{children}
</grid-container>
);
@@ -0,0 +1,51 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 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 { FormatterProps } from 'react-data-grid';
function valueGetter(rawValue: any) {
if (rawValue !== null && typeof rawValue === 'object') {
return JSON.stringify(rawValue);
}
return rawValue;
}
function formatValue(rawValue: any) {
const value = valueGetter(rawValue);
if (typeof value === 'string' && value.length > 1000) {
return value.split('').map(v => (v.charCodeAt(0) < 32 ? ' ' : v)).join('');
}
if (value === null) {
return '[null]';
}
return String(value);
}
function getClasses(rawValue: any) {
const classes = [];
if (rawValue === null) {
classes.push('cell-null');
}
return classes.join(' ');
}
export const CellFormatter: React.FC<FormatterProps> = function CellFormatter({ row, column }) {
const rawValue = row[column.key];
const classes = getClasses(rawValue);
const value = formatValue(rawValue);
return (
<cell-formatter as='div' className={`cell-formatter ${classes}`}>
{value}
</cell-formatter>
);
};
@@ -8,10 +8,10 @@
import type { FormatterProps } from 'react-data-grid';
export const CellFormatter: React.FC<FormatterProps> = function CellFormatter({ row, column }) {
export const IndexFormatter: React.FC<FormatterProps> = function IndexFormatter({ rowIdx }) {
return (
<div>
{JSON.stringify(row[column.key])}
</div>
<index-formatter as='div'>
{rowIdx + 1}
</index-formatter>
);
};
@@ -33,10 +33,10 @@ const headerStyles = css`
overflow: hidden;
}
icon {
height: 16px;
display: flex;
}
StaticImage {
height: 100%;
height: 16px;
}
name {
margin-left: 8px;
@@ -0,0 +1,29 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 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 "react-data-grid/dist/react-data-grid.css";
.cb-react-grid-theme {
font-family: inherit;
outline: 0;
.rdg {
--font-size: 12px;
height: 100%;
background-color: inherit;
border: none;
}
.rdg-header-row,
.rdg-row {
background-color: transparent;
}
.rdg-cell {
border-color: transparent;
}
}
@@ -0,0 +1,24 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 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 { ThemeSelector } from '@cloudbeaver/core-theming';
export const reactGridStyles: ThemeSelector = async theme => {
let styles: any;
switch (theme) {
case 'dark':
styles = await import('./themes/dark.module.scss');
break;
default:
styles = await import('./themes/light.module.scss');
break;
}
return [styles.default];
};
@@ -0,0 +1,72 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 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 "@cloudbeaver/core-theming/src/styles/branding";
@mixin base-react-grid() {
:global {
.cb-react-grid-theme {
&:focus-within .rdg-cell-custom-selected {
background-color: rgba(0, 145, 234, 0.3);
}
.rdg {
@include mdc-theme-prop(color, on-surface, false);
}
.rdg-header-row {
@include mdc-theme-prop(background-color, surface, false);
}
.rdg-row {
@include mdc-theme-prop(border-color, background, false);
}
.rdg-row:hover,
.rdg-row:hover .rdg-cell-frozen {
@include stripes-background($mdc-theme-secondary);
border-bottom: 1px solid;
border-bottom-color: $color-positive;
}
.rdg-cell {
@include mdc-theme-prop(border-color, background, false);
&:focus {
outline: 0;
}
}
.rdg-cell-frozen {
@include mdc-theme-prop(background-color, surface, false);
}
.rdg-cell-selected {
box-shadow: inset 0 0 0 1px #0091ea;
}
.rdg-cell-custom-selected {
background-color: rgba(0, 145, 234, 0.2);
box-shadow: inset 0 0 0 1px #0091ea;
}
.cell-formatter {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cell-index-column-name {
margin-left: 8px;
}
.cell-null {
text-transform: uppercase;
opacity: 0.65;
}
}
}
}
@@ -0,0 +1,14 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 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 "@cloudbeaver/core-theming/src/styles/theme-dark";
@import "base-react-grid";
:global .#{$theme-class} {
@include base-react-grid;
}
@@ -0,0 +1,14 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 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 "@cloudbeaver/core-theming/src/styles/theme-light";
@import "base-react-grid";
:global .#{$theme-class} {
@include base-react-grid;
}