feat: CB-1041 CB-1013 CB-1050 show in value panel button

This commit is contained in:
Wroud
2021-06-23 14:15:12 +03:00
parent 8eab411a31
commit edefe428e0
19 changed files with 184 additions and 78 deletions
@@ -26,7 +26,6 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
const selectionContext = useContext(DataGridSelectionContext);
const editingContext = useContext(EditingContext);
const editor = dataGridContext?.model.source.getEditor(dataGridContext.resultIndex);
const dataGridApi = dataGridContext?.getDataGridApi();
const mouse = useMouse<HTMLDivElement>({});
const classes: string[] = [];
@@ -45,6 +44,8 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
}
const handleMouseDown = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const dataGridApi = dataGridContext?.getDataGridApi();
if (dataGridApi) {
dataGridApi.selectCell({ idx: column.idx, rowIdx });
}
@@ -58,7 +59,7 @@ export const CellRenderer: React.FC<CellRendererProps<any>> = observer(function
event.shiftKey,
true
);
}, [column, rowIdx, selectionContext]);
}, [column, rowIdx, dataGridContext, selectionContext]);
const handleMouseUp = useCallback((event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!dataGridContext?.isGridInFocus()) {
@@ -10,7 +10,7 @@ import { createContext } from 'react';
import type { DataGridHandle } from 'react-data-grid';
import type { IExecutor } from '@cloudbeaver/core-executor';
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
import type { IDatabaseDataModel, IDataTableActions } from '@cloudbeaver/plugin-data-viewer';
export interface IColumnResizeInfo {
column: number;
@@ -19,6 +19,7 @@ export interface IColumnResizeInfo {
export interface IDataGridContext {
model: IDatabaseDataModel<any>;
actions: IDataTableActions;
resultIndex: number;
columnResize: IExecutor<IColumnResizeInfo>;
isGridInFocus: () => boolean;
@@ -73,6 +73,7 @@ export class DataGridContextMenuFilterService {
context: IMenuContext<IDataGridCellMenuContext>,
value: any | (() => any),
icon: string,
isHidden?: (context: IMenuContext<IDataGridCellMenuContext>) => boolean,
): Array<IContextMenuItem<IDataGridCellMenuContext>> {
const { model, resultIndex, column } = context.data;
const data = model.source.getAction(resultIndex, ResultSetDataAction);
@@ -88,6 +89,9 @@ export class DataGridContextMenuFilterService {
isDisabled(context) {
return context.data.model.isLoading();
},
isHidden(context) {
return isHidden?.(context) ?? false;
},
titleGetter() {
const val = typeof value === 'function' ? value() : value;
const stringifyValue = format.toDisplayString(val);
@@ -115,7 +119,7 @@ export class DataGridContextMenuFilterService {
return context.data.model.isDisabled(context.data.resultIndex)
|| context.data.model.source.results.length > 1;
},
order: 1,
order: 2,
title: 'data_grid_table_filter',
icon: '/icons/filter.png',
isPanel: true,
@@ -132,7 +136,7 @@ export class DataGridContextMenuFilterService {
const constraints = context.data.model.source.getAction(context.data.resultIndex, ResultSetConstraintAction);
return constraints.orderConstraints.length === 0 && constraints.filterConstraints.length === 0;
},
order: 2,
order: 3,
title: 'data_grid_table_delete_filters_and_orders',
icon: '/icons/erase.png',
onClick: async context => {
@@ -171,10 +175,19 @@ export class DataGridContextMenuFilterService {
return [];
}
if (this.clipboardService.state === 'prompt') {
return [{
const valueGetter = () => this.clipboardService.clipboardValue || '';
const items = this.getGeneralizedMenuItems(
context,
valueGetter,
'/icons/filter_clipboard.png',
() => this.clipboardService.state === 'prompt'
);
return [
{
id: 'permission',
isPresent: () => true,
isHidden: () => this.clipboardService.state !== 'prompt',
isDisabled(context) {
return context.data.model.isLoading();
},
@@ -183,12 +196,9 @@ export class DataGridContextMenuFilterService {
onClick: async () => {
await this.clipboardService.read();
},
}];
}
const valueGetter = () => this.clipboardService.clipboardValue || '';
const items = this.getGeneralizedMenuItems(context, valueGetter, '/icons/filter_clipboard.png');
return items;
},
...items,
];
},
}),
}
@@ -45,7 +45,7 @@ export class DataGridContextMenuOrderService {
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
order: 0,
order: 1,
title: 'data_grid_table_order',
icon: 'order-arrow-unknown',
isPanel: true,
@@ -9,10 +9,11 @@
import { injectable } from '@cloudbeaver/core-di';
import { ContextMenuService, IContextMenuItem, IMenuPanel } from '@cloudbeaver/core-dialogs';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
import type { IDatabaseDataModel, IDataTableActions } from '@cloudbeaver/plugin-data-viewer';
export interface IDataGridCellMenuContext {
model: IDatabaseDataModel<any>;
actions: IDataTableActions;
resultIndex: number;
row: number;
column: number;
@@ -36,6 +37,7 @@ export class DataGridContextMenuService {
constructMenuWithContext(
model: IDatabaseDataModel<any>,
actions: IDataTableActions,
resultIndex: number,
row: number,
column: number
@@ -43,17 +45,18 @@ export class DataGridContextMenuService {
return this.contextMenuService.createContextMenu<IDataGridCellMenuContext>({
menuId: this.getMenuToken(),
contextType: DataGridContextMenuService.cellContext,
data: { model, resultIndex, row, column },
data: { model, actions, resultIndex, row, column },
}, this.getMenuToken());
}
openMenu(
model: IDatabaseDataModel<any>,
actions: IDataTableActions,
resultIndex: number,
row: number,
column: number
): void {
this.onRootMenuOpen.execute({ model, resultIndex, row, column });
this.onRootMenuOpen.execute({ model, actions, resultIndex, row, column });
}
add(panelId: string, menuItem: IContextMenuItem<IDataGridCellMenuContext>): void {
@@ -7,29 +7,21 @@
*/
import { ComplexLoader, createComplexLoader, Loader } from '@cloudbeaver/core-blocks';
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
interface Props {
tableModel: IDatabaseDataModel<any, any>;
resultIndex: number;
className?: string;
}
import type { IDataPresentationProps } from '@cloudbeaver/plugin-data-viewer';
const loader = createComplexLoader(async function loader() {
const { DataGridTable } = await import('./DataGridTable');
return { DataGridTable };
});
export const DataGridLoader: React.FC<Props> = function DataGridLoader({
tableModel, resultIndex, className,
}) {
export const DataGridLoader: React.FC<IDataPresentationProps> = function DataGridLoader(props) {
return (
<ComplexLoader
loader={loader}
placeholder={<Loader />}
>
{({ DataGridTable }) => (
<DataGridTable model={tableModel} resultIndex={resultIndex} className={className} />
<DataGridTable {...props} />
)}
</ComplexLoader>
);
@@ -14,7 +14,7 @@ import styled from 'reshadow';
import { Executor } from '@cloudbeaver/core-executor';
import { useStyles } from '@cloudbeaver/core-theming';
import { IDatabaseDataEditorActionsData, IDatabaseDataModel, IDatabaseResultSet, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
import { IDatabaseDataEditorActionsData, IDatabaseDataModel, IDatabaseResultSet, IDataPresentationProps, IDataTableActions, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer';
import { CellPosition, EditingContext } from '../Editing/EditingContext';
import { useEditing } from '../Editing/useEditing';
@@ -30,18 +30,12 @@ import { useGridDragging } from './useGridDragging';
import { useGridSelectedCellsCopy } from './useGridSelectedCellsCopy';
import { useTableData } from './useTableData';
interface Props {
model: IDatabaseDataModel<any, IDatabaseResultSet>;
resultIndex: number;
className?: string;
}
function isAtBottom(event: React.UIEvent<HTMLDivElement>): boolean {
const target = event.target as HTMLDivElement;
return target.clientHeight + target.scrollTop + 100 > target.scrollHeight;
}
export const DataGridTable: React.FC<Props> = observer(function DataGridTable({ model, resultIndex, className }) {
export const DataGridTable: React.FC<IDataPresentationProps<any, IDatabaseResultSet>> = observer(function DataGridTable({ model, actions, resultIndex, className }) {
const gridContainerRef = useRef<HTMLDivElement>(null);
const dataGridRef = useRef<DataGridHandle>(null);
const editorRef = useRef<HTMLDivElement>(null);
@@ -143,12 +137,13 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
const gridContext = useMemo<IDataGridContext>(() => ({
model,
actions,
columnResize,
resultIndex,
isGridInFocus: () => gridContainerRef.current === document.activeElement,
getEditorPortal: () => editorRef.current,
getDataGridApi: () => dataGridRef.current,
}), [model, resultIndex, editorRef, dataGridRef]);
}), [model, actions, resultIndex, editorRef, dataGridRef]);
return styled(styles)(
<DataGridContext.Provider value={gridContext}>
@@ -157,7 +152,6 @@ export const DataGridTable: React.FC<Props> = observer(function DataGridTable({
<TableDataContext.Provider value={tableData}>
<grid-container
ref={gridContainerRef}
as='div'
className="cb-react-grid-container"
tabIndex={-1}
onKeyDown={onKeydownHandler}
@@ -72,6 +72,7 @@ export const CellFormatter: React.FC<FormatterProps> = observer(function CellFor
{(isCellSelected || cellContext?.mouse.state.mouseEnter || menuVisible) && context && (
<CellMenu
model={context.model}
actions={context.actions}
resultIndex={context.resultIndex}
row={rowIdx}
column={Number(column.key)}
@@ -15,24 +15,28 @@ import { Icon } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { MenuTrigger } from '@cloudbeaver/core-dialogs';
import { useStyles } from '@cloudbeaver/core-theming';
import type { IDatabaseDataModel } from '@cloudbeaver/plugin-data-viewer';
import type { IDatabaseDataModel, IDataTableActions } from '@cloudbeaver/plugin-data-viewer';
import { DataGridContextMenuService } from '../../DataGridContextMenu/DataGridContextMenuService';
import { cellMenuStyles } from './cellMenuStyles';
interface Props {
model: IDatabaseDataModel<any>;
actions: IDataTableActions;
resultIndex: number;
row: number;
column: number;
onClick?: () => void;
onStateSwitch?: (state: boolean) => void;
}
export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
model,
actions,
resultIndex,
row,
column,
onClick,
onStateSwitch,
}) {
const dataGridContextMenuService = useService(DataGridContextMenuService);
@@ -40,7 +44,7 @@ export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
const { panel, hidden } = useMemo(
() => {
const panel = dataGridContextMenuService.constructMenuWithContext(model, resultIndex, row, column);
const panel = dataGridContextMenuService.constructMenuWithContext(model, actions, resultIndex, row, column);
const hidden = computed(() => !panel.menuItems.length
|| panel.menuItems.every(item => item.isHidden));
@@ -49,6 +53,11 @@ export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
[column, row]
);
function handleClick() {
dataGridContextMenuService.openMenu(model, actions, resultIndex, row, column);
onClick?.();
}
if (hidden.get()) {
return null;
}
@@ -59,7 +68,7 @@ export const CellMenu: React.FC<Props> = observer(function TreeNodeMenu({
panel={panel}
style={[cellMenuStyles]}
modal
onClick={() => dataGridContextMenuService.openMenu(model, resultIndex, row, column)}
onClick={handleClick}
onVisibleSwitch={onStateSwitch}
>
<Icon name="snack" viewBox="0 0 16 10" />
@@ -13,6 +13,7 @@ import { DataPresentationService } from '@cloudbeaver/plugin-data-viewer';
import { DataGridContextMenuFilterService } from './DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService';
import { DataGridContextMenuOrderService } from './DataGrid/DataGridContextMenu/DataGridContextMenuOrderService';
import { DataGridContextMenuService } from './DataGrid/DataGridContextMenu/DataGridContextMenuService';
import { DataGridSettingsService } from './DataGridSettingsService';
import { SpreadsheetGrid } from './SpreadsheetGrid';
@@ -23,6 +24,7 @@ export class SpreadsheetBootstrap extends Bootstrap {
private dataGridSettingsService: DataGridSettingsService,
private dataGridContextMenuSortingService: DataGridContextMenuOrderService,
private dataGridContextMenuFilterService: DataGridContextMenuFilterService,
private dataGridContextMenuService: DataGridContextMenuService,
exceptionsCatcherService: ExceptionsCatcherService
) {
super();
@@ -40,6 +42,25 @@ export class SpreadsheetBootstrap extends Bootstrap {
});
this.dataGridContextMenuSortingService.register();
this.dataGridContextMenuFilterService.register();
this.dataGridContextMenuService.add(
this.dataGridContextMenuService.getMenuToken(),
{
id: 'view_value_panel',
isPresent(context) {
return context.contextType === DataGridContextMenuService.cellContext;
},
isHidden(context) {
return typeof context.data.actions.valuePresentationId === 'string';
},
order: 0.5,
title: 'data_grid_table_open_value_panel',
icon: '/icons/text_value_presentation.png',
onClick(context) {
context.data.actions.setValuePresentation('');
},
}
);
}
load(): void | Promise<void> { }
@@ -10,10 +10,6 @@ import type { IDataPresentationProps } from '@cloudbeaver/plugin-data-viewer';
import { DataGridLoader } from './DataGrid/DataGridLoader';
export const SpreadsheetGrid: React.FC<IDataPresentationProps> = function SpreadsheetGrid({
model,
className,
resultIndex,
}) {
return <DataGridLoader tableModel={model} resultIndex={resultIndex} className={className} />;
export const SpreadsheetGrid: React.FC<IDataPresentationProps> = function SpreadsheetGrid(props) {
return <DataGridLoader {...props} />;
};
@@ -1,5 +1,6 @@
export default [
['data_grid_table_order', 'Sorting'],
['data_grid_table_open_value_panel', 'Show in value panel'],
['data_grid_table_filter', 'Filters'],
['data_grid_table_filter_cell_value', 'Cell value'],
['data_grid_table_filter_custom_value', 'Custom'],
@@ -1,5 +1,6 @@
export default [
['data_grid_table_order', 'Сортировка'],
['data_grid_table_open_value_panel', 'Показать в панели значений'],
['data_grid_table_filter', 'Фильтры'],
['data_grid_table_filter_cell_value', 'Значение ячейки'],
['data_grid_table_filter_custom_value', 'Произвольный'],
@@ -12,6 +12,7 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataModel } from './DatabaseDataModel/IDatabaseDataModel';
import type { IDatabaseDataResult } from './DatabaseDataModel/IDatabaseDataResult';
import type { IDataTableActions } from './TableViewer/IDataTableActions';
export interface IDataPresentationProps<
TOptions = any,
@@ -19,6 +20,7 @@ export interface IDataPresentationProps<
> {
dataFormat: ResultDataFormat;
model: IDatabaseDataModel<TOptions, TResult>;
actions: IDataTableActions;
resultIndex: number;
className?: string;
}
@@ -0,0 +1,27 @@
/*
* 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 { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
export interface IDataTableActions {
presentationId: string | undefined;
valuePresentationId: string | null | undefined;
dataModel: IDatabaseDataModel<any, any> | undefined;
setPresentation: (id: string) => void;
setValuePresentation: (id: string | null) => void;
}
export interface IDataTableActionsPrivate extends IDataTableActions {
resultIndex: number;
dataFormat: ResultDataFormat;
onPresentationChange: (id: string) => void;
onValuePresentationChange: (id: string | null) => void;
}
@@ -15,10 +15,12 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
import type { IDataPresentationOptions } from '../DataPresentationService';
import type { IDataTableActions } from './IDataTableActions';
import { TableStatistics } from './TableStatistics';
type TableGridProps = PropsWithChildren<{
model: IDatabaseDataModel<any>;
actions: IDataTableActions;
dataFormat: ResultDataFormat;
presentation: IDataPresentationOptions;
resultIndex: number;
@@ -33,6 +35,7 @@ const styles = css`
export const TableGrid = observer(function TableGrid({
model,
actions,
dataFormat,
presentation,
resultIndex,
@@ -58,6 +61,6 @@ export const TableGrid = observer(function TableGrid({
}
return styled(styles)(
<Presentation dataFormat={dataFormat} model={model} resultIndex={resultIndex} />
<Presentation dataFormat={dataFormat} model={model} actions={actions} resultIndex={resultIndex} />
);
});
@@ -16,9 +16,11 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
import type { IDataPresentationOptions } from '../DataPresentationService';
import type { IDataTableActions } from './IDataTableActions';
type TableGridProps = PropsWithChildren<{
model: IDatabaseDataModel<any, any>;
actions: IDataTableActions;
dataFormat: ResultDataFormat;
presentation: IDataPresentationOptions | null;
resultIndex: number;
@@ -33,6 +35,7 @@ const styles = css`
export const TableToolsPanel = observer(function TableToolsPanel({
model,
actions,
dataFormat,
presentation,
resultIndex,
@@ -57,6 +60,6 @@ export const TableToolsPanel = observer(function TableToolsPanel({
}
return styled(styles)(
<Presentation dataFormat={dataFormat} model={model} resultIndex={resultIndex} />
<Presentation dataFormat={dataFormat} model={model} actions={actions} resultIndex={resultIndex} />
);
});
@@ -6,16 +6,18 @@
* you may not use this file except in compliance with the License.
*/
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useCallback, useEffect } from 'react';
import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import { Loader, Pane, ResizerControls, Split, splitStyles, TextPlaceholder } from '@cloudbeaver/core-blocks';
import { Loader, Pane, ResizerControls, Split, splitStyles, TextPlaceholder, useObjectRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import { DataPresentationService, DataPresentationType } from '../DataPresentationService';
import type { IDataTableActionsPrivate } from './IDataTableActions';
import { TableError } from './TableError';
import { TableFooter } from './TableFooter/TableFooter';
import { TableGrid } from './TableGrid';
@@ -117,30 +119,66 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
const loading = dataModel?.isLoading() ?? true;
const dataFormat = result?.dataFormat || ResultDataFormat.Resultset;
const handlePresentationChange = useCallback((id: string) => {
const presentation = dataPresentationService.get(id);
if (presentation) {
if (
presentation.dataFormat !== undefined
&& presentation.dataFormat !== dataModel?.source.dataFormat
) {
dataModel?.setDataFormat(presentation.dataFormat)
.reload();
}
onPresentationChange(id);
}
}, [onPresentationChange, dataModel]);
const dataTableActions = useObjectRef<IDataTableActionsPrivate>({
presentationId,
valuePresentationId,
dataModel,
resultIndex,
dataFormat,
onPresentationChange,
onValuePresentationChange,
function handleValuePresentationChange(id: string) {
if (id === valuePresentationId) {
onValuePresentationChange(null);
return;
}
const presentation = dataPresentationService.get(id);
if (presentation) {
onValuePresentationChange(id);
}
}
setPresentation(id: string) {
const presentation = dataPresentationService.get(id);
if (presentation) {
if (
presentation.dataFormat !== undefined
&& presentation.dataFormat !== this.dataModel?.source.dataFormat
) {
this.dataModel?.setDataFormat(presentation.dataFormat).reload();
}
this.onPresentationChange(id);
}
},
setValuePresentation(id: string | null) {
if (id === this.valuePresentationId || id === null) {
this.onValuePresentationChange(null);
return;
}
let presentation = dataPresentationService.get(id);
if (!presentation && this.dataModel) {
presentation = dataPresentationService.getSupported(
DataPresentationType.toolsPanel,
this.dataFormat,
undefined,
this.dataModel,
this.resultIndex
) ?? undefined;
}
if (presentation) {
this.onValuePresentationChange(presentation.id);
}
},
}, {
presentationId,
valuePresentationId,
dataModel,
resultIndex,
dataFormat,
onPresentationChange,
onValuePresentationChange,
}, {
presentationId: observable,
valuePresentationId: observable,
dataFormat: observable,
resultIndex: observable,
dataModel: observable.ref,
}, ['setPresentation', 'setValuePresentation']);
useEffect(() => {
if (!presentationId || !dataModel) {
@@ -189,9 +227,9 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
&& resultExist;
return styled(styles)(
<table-viewer as="div" className={className}>
<table-viewer className={className}>
<TableHeader model={dataModel} resultIndex={resultIndex} />
<table-content as='div'>
<table-content>
<TablePresentationBar
type={DataPresentationType.main}
presentationId={presentation.id}
@@ -199,7 +237,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
supportedDataFormat={dataModel.supportedDataFormats}
model={dataModel}
resultIndex={resultIndex}
onPresentationChange={handlePresentationChange}
onPresentationChange={dataTableActions.setPresentation}
/>
<table-data>
<Split sticky={30} mode={valuePanelDisplayed ? undefined : 'minimize'} keepRatio>
@@ -207,6 +245,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
<pane-content>
<TableGrid
model={dataModel}
actions={dataTableActions}
dataFormat={dataFormat}
presentation={presentation}
resultIndex={resultIndex}
@@ -219,6 +258,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
{resultExist && (
<TableToolsPanel
model={dataModel}
actions={dataTableActions}
dataFormat={dataFormat}
presentation={valuePresentation}
resultIndex={resultIndex}
@@ -242,7 +282,7 @@ export const TableViewer: React.FC<Props> = observer(function TableViewer({
supportedDataFormat={[dataFormat]}
model={dataModel}
resultIndex={resultIndex}
onPresentationChange={handleValuePresentationChange}
onPresentationChange={dataTableActions.setValuePresentation}
/>
</table-content>
<TableFooter model={dataModel} resultIndex={resultIndex} />
@@ -37,6 +37,7 @@ export * from './DatabaseDataModel/Order';
export * from './TableViewer/TableViewerStorageService';
export * from './TableViewer/ValuePanel/DataValuePanelService';
export * from './TableViewer/IDataTableActions';
export * from './TableViewer/TableViewer';
export * from './TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService';
export * from './TableViewer/DataModelWrapper';