mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat(plugin-data-spreadsheet-new): CB-695 add cell formatter and custom styles
This commit is contained in:
+6
-10
@@ -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>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -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>
|
||||
);
|
||||
};
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user