feat(plugin-data-viewer): CB-751 DataValuePanelService

This commit is contained in:
Wroud
2021-03-17 13:21:53 +03:00
parent a2daf7533c
commit abaa00caec
15 changed files with 235 additions and 64 deletions
@@ -46,9 +46,12 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
!selectedId
&& currentTabId === undefined
&& container
&& container.tabInfoList.length > 0
) {
selectedId = container.tabInfoList[0].key;
const displayed = container.getDisplayed(rest as T);
if (displayed.length > 0) {
selectedId = displayed[0].key;
}
}
const [localTabsState] = useState(() => new MetadataMap<string, any>());
@@ -79,7 +82,6 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
if (currentTabId !== undefined) {
state.selectedId = currentTabId;
state.setSelectedId(currentTabId);
dynamic.selectedId = currentTabId;
}
@@ -17,6 +17,7 @@ export interface IDataPresentationProps<
TOptions = any,
TResult extends IDatabaseDataResult = IDatabaseDataResult
> {
dataFormat: ResultDataFormat;
model: IDatabaseDataModel<TOptions, TResult>;
resultIndex: number;
className?: string;
@@ -24,7 +25,7 @@ export interface IDataPresentationProps<
export enum DataPresentationType {
main,
value
toolsPanel
}
export type DataPresentationComponent<
@@ -41,11 +42,15 @@ export type PresentationTabComponent = React.FunctionComponent<PresentationTabPr
export interface IDataPresentationOptions {
id: string;
dataFormat: ResultDataFormat;
dataFormat?: ResultDataFormat;
type?: DataPresentationType;
title?: string;
icon?: string;
hidden?: () => boolean;
hidden?: (
dataFormat: ResultDataFormat | null,
model: IDatabaseDataModel<any>,
resultIndex: number
) => boolean;
getPresentationComponent: () => DataPresentationComponent;
getTabComponent?: () => PresentationTabComponent;
onActivate?: () => void;
@@ -69,21 +74,26 @@ export class DataPresentationService {
getSupportedList(
type: DataPresentationType,
dataFormat: ResultDataFormat | ResultDataFormat[]
dataFormat: ResultDataFormat[],
model: IDatabaseDataModel<any>,
resultIndex: number,
): IDataPresentation[] {
return Array.from(this.dataPresentations.values()).filter(presentation => {
if (presentation.type !== type || presentation.hidden?.()) {
if (presentation.type !== type || presentation.hidden?.(null, model, resultIndex)) {
return false;
}
return presentation.dataFormat === dataFormat || dataFormat.includes(presentation.dataFormat);
return presentation.dataFormat === undefined
|| dataFormat.includes(presentation.dataFormat);
});
}
getSupported(
type: DataPresentationType,
dataFormat: ResultDataFormat,
presentationId: string | undefined
presentationId: string | undefined,
model: IDatabaseDataModel<any>,
resultIndex: number,
): IDataPresentation | null {
if (presentationId) {
const presentation = this.dataPresentations.get(presentationId);
@@ -95,9 +105,9 @@ export class DataPresentationService {
for (const presentation of this.dataPresentations.values()) {
if (
presentation.dataFormat === dataFormat
(presentation.dataFormat === undefined || presentation.dataFormat === dataFormat)
&& presentation.type === type
&& !presentation.hidden?.()
&& !presentation.hidden?.(dataFormat, model, resultIndex)
) {
return presentation;
}
@@ -111,7 +111,7 @@ export class DataViewerTabService {
const pageState = this.page.getState(tab);
if (pageState) {
const presentation = this.dataPresentationService.get(pageState?.presentationId);
if (presentation) {
if (presentation?.dataFormat !== undefined) {
model.setDataFormat(presentation.dataFormat);
}
}
@@ -40,7 +40,7 @@ export const TableGrid = observer(function TableGrid({
const translate = useTranslate();
const result = model.getResult(resultIndex);
if (dataFormat !== presentation.dataFormat) {
if (presentation.dataFormat !== undefined && dataFormat !== presentation.dataFormat) {
if (model.isLoading()) {
return null;
}
@@ -56,6 +56,6 @@ export const TableGrid = observer(function TableGrid({
}
return styled(styles)(
<Presentation model={model} resultIndex={resultIndex} />
<Presentation dataFormat={dataFormat} model={model} resultIndex={resultIndex} />
);
});
@@ -52,6 +52,7 @@ interface Props {
presentationId: string | null | undefined;
supportedDataFormat: ResultDataFormat[];
model: IDatabaseDataModel<any>;
resultIndex: number;
className?: string;
onPresentationChange: (id: string) => void;
}
@@ -61,11 +62,12 @@ export const TablePresentationBar = observer(function TablePresentationBar({
presentationId,
supportedDataFormat,
model,
resultIndex,
className,
onPresentationChange,
}: Props) {
const dataPresentationService = useService(DataPresentationService);
const presentations = dataPresentationService.getSupportedList(type, supportedDataFormat);
const presentations = dataPresentationService.getSupportedList(type, supportedDataFormat, model, resultIndex);
const Tab = PresentationTab; // alias for styles matching
const changePresentation = ({ tabId }: ITabData<any>) => onPresentationChange(tabId);
@@ -11,17 +11,16 @@ import type { PropsWithChildren } from 'react';
import styled, { css } from 'reshadow';
import { TextPlaceholder } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DataPresentationService, DataPresentationType } from '../DataPresentationService';
import type { IDataPresentationOptions } from '../DataPresentationService';
import type { DataModelWrapper } from './DataModelWrapper';
type TableGridProps = PropsWithChildren<{
model: DataModelWrapper; // TODO: change to IDatabaseDataModel<any>
dataFormat: ResultDataFormat;
presentationId: string | null;
presentation: IDataPresentationOptions | null;
resultIndex: number;
}>;
@@ -32,27 +31,17 @@ const styles = css`
}
`;
export const ValuePanel = observer(function ValuePanel({
export const TableToolsPanel = observer(function TableToolsPanel({
model,
dataFormat,
presentationId,
presentation,
resultIndex,
}: TableGridProps) {
const translate = useTranslate();
const dataPresentationService = useService(DataPresentationService);
if (!presentationId) {
return null;
}
const presentation = dataPresentationService.getSupported(
DataPresentationType.value,
dataFormat,
presentationId
);
const result = model.getResult(resultIndex);
if (!presentation || dataFormat !== presentation.dataFormat) {
if (!presentation || (presentation.dataFormat !== undefined && dataFormat !== presentation.dataFormat)) {
if (model.isLoading()) {
return null;
}
@@ -68,6 +57,6 @@ export const ValuePanel = observer(function ValuePanel({
}
return styled(styles)(
<Presentation model={model} resultIndex={resultIndex} />
<Presentation dataFormat={dataFormat} model={model} resultIndex={resultIndex} />
);
});
@@ -21,8 +21,8 @@ import { TableFooter } from './TableFooter/TableFooter';
import { TableGrid } from './TableGrid';
import { TableHeader } from './TableHeader/TableHeader';
import { TablePresentationBar } from './TablePresentationBar/TablePresentationBar';
import { TableToolsPanel } from './TableToolsPanel';
import { TableViewerStorageService } from './TableViewerStorageService';
import { ValuePanel } from './ValuePanel';
const viewerStyles = composes(
css`
@@ -113,7 +113,10 @@ export const TableViewer = observer(function TableViewer({
const handlePresentationChange = useCallback((id: string) => {
const presentation = dataPresentationService.get(id);
if (presentation) {
if (presentation.dataFormat !== dataModel?.source.dataFormat) {
if (
presentation.dataFormat !== undefined
&& presentation.dataFormat !== dataModel?.source.dataFormat
) {
dataModel?.setDataFormat(presentation.dataFormat)
.reload();
}
@@ -141,21 +144,28 @@ export const TableViewer = observer(function TableViewer({
const presentation = dataPresentationService.getSupported(
DataPresentationType.main,
dataFormat,
presentationId
presentationId,
dataModel,
resultIndex
);
const valuePresentation = valuePresentationId
? dataPresentationService.getSupported(
DataPresentationType.value,
dataFormat,
valuePresentationId
)
: null;
if (!presentation) {
return <TextPlaceholder>There are no available presentation for data format: {dataFormat}</TextPlaceholder>;
}
const valuePresentation = valuePresentationId
? dataPresentationService.getSupported(
DataPresentationType.toolsPanel,
dataFormat,
valuePresentationId,
dataModel,
resultIndex
)
: null;
const valuePanelDisplayed = valuePresentation
&& (valuePresentation.dataFormat === undefined
|| valuePresentation?.dataFormat === dataFormat);
const overlay = dataModel.source.results.length > 0 && presentation.dataFormat === dataFormat;
const loading = dataModel.isLoading();
@@ -168,12 +178,13 @@ export const TableViewer = observer(function TableViewer({
presentationId={presentation.id}
supportedDataFormat={dataModel.supportedDataFormats}
model={dataModel}
resultIndex={resultIndex}
onPresentationChange={handlePresentationChange}
/>
<table-data as='div'>
<table-box as='div'>
{(overlay || !loading) && (
<Split sticky={30} mode={valuePresentation?.dataFormat === dataFormat ? undefined : 'minimize'} keepRatio>
<Split sticky={30} mode={valuePanelDisplayed ? undefined : 'minimize'} keepRatio>
<Pane>
{dataModel.source.hasResult(resultIndex) && (
<TableGrid
@@ -196,12 +207,12 @@ export const TableViewer = observer(function TableViewer({
)}
</error>
</Pane>
{valuePresentation?.dataFormat === dataFormat && <ResizerControls />}
{valuePanelDisplayed && <ResizerControls />}
<Pane main>
<ValuePanel
<TableToolsPanel
model={dataModel}
dataFormat={dataFormat}
presentationId={valuePresentationId ?? null}
presentation={valuePresentation}
resultIndex={resultIndex}
/>
</Pane>
@@ -216,10 +227,11 @@ export const TableViewer = observer(function TableViewer({
</table-box>
</table-data>
<TablePresentationBar
type={DataPresentationType.value}
type={DataPresentationType.toolsPanel}
presentationId={valuePresentationId ?? null}
supportedDataFormat={[dataFormat]}
model={dataModel}
resultIndex={resultIndex}
onPresentationChange={handleValuePresentationChange}
/>
</table-content>
@@ -0,0 +1,40 @@
/*
* 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 { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { DataPresentationService, DataPresentationType } from '../../DataPresentationService';
import { DataValuePanelService } from './DataValuePanelService';
import { ValuePanel } from './ValuePanel';
@injectable()
export class DataValuePanelBootstrap extends Bootstrap {
constructor(
private readonly dataPresentationService: DataPresentationService,
private readonly dataValuePanelService: DataValuePanelService
) {
super();
}
register(): void | Promise<void> {
this.dataPresentationService.add({
id: 'value-text-presentation',
type: DataPresentationType.toolsPanel,
title: 'data_viewer_presentation_value_title',
icon: '/icons/text_value_presentation.png',
hidden: (
dataFormat,
model,
resultIndex
) => this.dataValuePanelService.getDisplayed({ model, resultIndex, dataFormat }).length === 0,
getPresentationComponent: () => ValuePanel,
});
}
load(): void {}
}
@@ -0,0 +1,45 @@
/*
* 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 { ITabInfo, ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
import type { IDatabaseDataResult } from '../../DatabaseDataModel/IDatabaseDataResult';
export interface IDataValuePanelOptions {
dataFormat: ResultDataFormat[];
}
export interface IDataValuePanelProps<TOptions, TResult extends IDatabaseDataResult = IDatabaseDataResult> {
dataFormat: ResultDataFormat | null;
model: IDatabaseDataModel<TOptions, TResult>;
resultIndex: number;
}
@injectable()
export class DataValuePanelService {
readonly tabs: TabsContainer<IDataValuePanelProps<any>, IDataValuePanelOptions>;
constructor() {
this.tabs = new TabsContainer();
}
get(tabId: string): ITabInfo<IDataValuePanelProps<any>, IDataValuePanelOptions> | undefined {
return this.tabs.getTabInfo(tabId);
}
getDisplayed(props?: IDataValuePanelProps<any>): Array<ITabInfo<IDataValuePanelProps<any>, IDataValuePanelOptions>> {
return this.tabs.getDisplayed(props);
}
add(tabInfo: ITabInfoOptions<IDataValuePanelProps<any>, IDataValuePanelOptions>): void {
this.tabs.add(tabInfo);
}
}
@@ -0,0 +1,61 @@
/*
* 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 { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { TabList, TabPanelList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
import type { DataPresentationComponent } from '../../DataPresentationService';
import { DataValuePanelService } from './DataValuePanelService';
const styles = composes(
css`
Tab {
composes: theme-ripple from global;
}
TabList {
composes: theme-background-surface theme-text-on-surface from global;
}
`,
css`
table-left-bar {
display: flex;
}
Tab {
composes: theme-typography--body2 from global;
text-transform: uppercase;
font-weight: normal;
&:global([aria-selected=true]) {
font-weight: normal !important;
}
}
TabList tab-outer:only-child {
display: none;
}
`
);
export const ValuePanel: DataPresentationComponent<any, IDatabaseResultSet> = observer(function ValuePanel({
dataFormat,
model,
resultIndex,
}) {
const service = useService(DataValuePanelService);
return styled(useStyles(styles, UNDERLINE_TAB_STYLES))(
<TabsState container={service.tabs} dataFormat={dataFormat} model={model} resultIndex={resultIndex} lazy>
<TabList style={[styles, UNDERLINE_TAB_STYLES]} />
<TabPanelList style={[styles, UNDERLINE_TAB_STYLES]} />
</TabsState>
);
});
@@ -9,19 +9,25 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { BASE_CONTAINERS_STYLES, TextareaNew } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES, TabContainerPanelComponent, TextareaNew } from '@cloudbeaver/core-blocks';
import { useStyles } from '@cloudbeaver/core-theming';
import { css } from '@reshadow/react';
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSetFormatAction';
import { IResultSetSelectKey, ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSetSelectAction';
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
import type { DataPresentationComponent } from '../../DataPresentationService';
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
export const TextValuePresentation: DataPresentationComponent<any, IDatabaseResultSet> = observer(function TextValuePresentation({
const styles = css`
TextareaNew {
flex: 1;
}
`;
export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelProps<any, IDatabaseResultSet>> = observer(function TextValuePresentation({
model,
resultIndex,
}) {
const styles = useStyles(BASE_CONTAINERS_STYLES);
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
const result = model.getResult(resultIndex);
@@ -50,7 +56,7 @@ export const TextValuePresentation: DataPresentationComponent<any, IDatabaseResu
}
};
return styled(styles)(
return styled(useStyles(styles, BASE_CONTAINERS_STYLES))(
<TextareaNew
name="value"
rows={3}
@@ -9,23 +9,21 @@
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DataPresentationService, DataPresentationType } from '../../DataPresentationService';
import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService';
import { TextValuePresentation } from './TextValuePresentation';
@injectable()
export class TextValuePresentationBootstrap extends Bootstrap {
constructor(private readonly dataPresentationService: DataPresentationService) {
constructor(private readonly dataValuePanelService: DataValuePanelService) {
super();
}
register(): void | Promise<void> {
this.dataPresentationService.add({
id: 'value-text-presentation',
dataFormat: ResultDataFormat.Resultset,
type: DataPresentationType.value,
title: 'data_viewer_presentation_value_title',
icon: '/icons/text_value_presentation.png',
getPresentationComponent: () => TextValuePresentation,
this.dataValuePanelService.add({
key: 'text-presentation',
options: { dataFormat: [ResultDataFormat.Resultset] },
name: 'data_viewer_presentation_value_text_title',
panel: () => TextValuePresentation,
});
}
@@ -2,4 +2,5 @@ export default [
['table_header_sql_expression', 'Enter a SQL expression to filter results'],
['data_viewer_nodata_message', 'No data to show'],
['data_viewer_presentation_value_title', 'Value'],
['data_viewer_presentation_value_text_title', 'Text'],
];
@@ -2,4 +2,5 @@ export default [
['table_header_sql_expression', 'Введите SQL выражение чтобы отфильтровать результаты'],
['data_viewer_nodata_message', 'Нет данных для отображения'],
['data_viewer_presentation_value_title', 'Значение'],
['data_viewer_presentation_value_text_title', 'Текст'],
];
@@ -8,7 +8,6 @@
import type { PluginManifest } from '@cloudbeaver/core-di';
import { TextValuePresentationBootstrap } from './DataPresentation/TextValuePresentation/TextValuePresentationBootstrap';
import { DataPresentationService } from './DataPresentationService';
import { DataViewerBootstrap } from './DataViewerBootstrap';
import { DataViewerTableService } from './DataViewerTableService';
@@ -17,6 +16,9 @@ import { LocaleService } from './LocaleService';
import { TableFooterMenuService } from './TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService';
import { TableHeaderService } from './TableViewer/TableHeader/TableHeaderService';
import { TableViewerStorageService } from './TableViewer/TableViewerStorageService';
import { DataValuePanelBootstrap } from './TableViewer/ValuePanel/DataValuePanelBootstrap';
import { DataValuePanelService } from './TableViewer/ValuePanel/DataValuePanelService';
import { TextValuePresentationBootstrap } from './ValuePanelPresentation/TextValue/TextValuePresentationBootstrap';
export const manifest: PluginManifest = {
info: {
@@ -32,6 +34,8 @@ export const manifest: PluginManifest = {
TableFooterMenuService,
TableHeaderService,
LocaleService,
DataValuePanelService,
TextValuePresentationBootstrap,
DataValuePanelBootstrap,
],
};