mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:45:18 +00:00
feat(data-vi): allow to add refresh button and set auto refresh interval (#5112)
* feat(data-vi): allow to set auto refresh interval for charts * feat(data-vi): allow to add refresh button and set auto refresh interval * fix: build * fix: bug * chore: optimize global auto refresh * chore: remove console.log * fix: remove console log
This commit is contained in:
parent
dfe517e737
commit
61f95f78c2
@ -14,6 +14,7 @@ import { ChartDataProvider } from './ChartDataProvider';
|
||||
import { ChartRenderer, ChartRendererProvider } from '../renderer';
|
||||
import { ChartFilterBlockProvider, ChartFilterBlockDesigner } from '../filter';
|
||||
import { ChartFilterProvider } from '../filter/FilterProvider';
|
||||
import { RefreshButton } from '../initializers/RefreshAction';
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
export const ChartV2Block: React.FC = (props) => {
|
||||
@ -24,7 +25,13 @@ export const ChartV2Block: React.FC = (props) => {
|
||||
value={{ ...schemaInitializerContextData, visible: initialVisible, setVisible: setInitialVisible }}
|
||||
>
|
||||
<SchemaComponentOptions
|
||||
components={{ ChartRenderer, ChartRendererProvider, ChartFilterBlockProvider, ChartFilterBlockDesigner }}
|
||||
components={{
|
||||
ChartRenderer,
|
||||
ChartRendererProvider,
|
||||
ChartFilterBlockProvider,
|
||||
ChartFilterBlockDesigner,
|
||||
RefreshButton,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={css`
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
SchemaSettingsBlockTitleItem,
|
||||
SchemaSettingsDivider,
|
||||
SchemaSettingsRemove,
|
||||
SchemaSettingsSelectItem,
|
||||
SchemaSettingsSwitchItem,
|
||||
useDesignable,
|
||||
useToken,
|
||||
|
@ -120,7 +120,18 @@ export const ChartV2BlockInitializer: React.FC = () => {
|
||||
},
|
||||
},
|
||||
'x-designer': 'ChartV2BlockDesigner',
|
||||
'x-decorator': 'ChartBlockProvider',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginBottom: 'var(--nb-designer-offset)',
|
||||
},
|
||||
},
|
||||
'x-initializer': 'chartBlock:configureActions',
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { BlockRefreshButton } from '../initializers/BlockRefreshAction';
|
||||
import { SchemaComponentOptions } from '@nocobase/client';
|
||||
import { GlobalAutoRefreshProvider } from './GlobalAutoRefreshProvider';
|
||||
|
||||
export const ChartBlockProvider: React.FC = (props) => {
|
||||
return (
|
||||
<SchemaComponentOptions
|
||||
components={{
|
||||
BlockRefreshButton,
|
||||
}}
|
||||
>
|
||||
<GlobalAutoRefreshProvider> {props.children}</GlobalAutoRefreshProvider>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
};
|
@ -34,6 +34,7 @@ export const ChartDataProvider: React.FC = (props) => {
|
||||
const removeChart = useMemoizedFn((uid: string) => {
|
||||
setCharts((charts) => ({ ...charts, [uid]: undefined }));
|
||||
});
|
||||
|
||||
return (
|
||||
<ChartDataContext.Provider value={{ charts, addChart, removeChart }}>{props.children}</ChartDataContext.Provider>
|
||||
);
|
||||
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
|
||||
export const GlobalAutoRefreshContext = React.createContext<{
|
||||
addChart: (uid: string, chart: { service: any }) => void;
|
||||
removeChart: (uid: string) => void;
|
||||
autoRefresh: number | boolean;
|
||||
setAutoRefresh: (autoRefresh: number | boolean) => void;
|
||||
refreshCharts: () => void;
|
||||
}>({} as any);
|
||||
|
||||
export const GlobalAutoRefreshProvider: React.FC = (props) => {
|
||||
const [autoRefresh, setAutoRefresh] = useState<number | boolean>(false);
|
||||
const charts = useRef<{ [uid: string]: { service: any; selfAutoRefresh?: boolean } }>({});
|
||||
const addChart = useMemoizedFn((uid: string, { service }) => {
|
||||
charts.current[uid] = { service };
|
||||
});
|
||||
const removeChart = useMemoizedFn((uid: string) => {
|
||||
const chart = charts.current[uid];
|
||||
if (!chart) {
|
||||
return;
|
||||
}
|
||||
charts.current[uid] = { service: chart.service, selfAutoRefresh: true };
|
||||
});
|
||||
const refreshCharts = useMemoizedFn(() => {
|
||||
for (const chart of Object.values(charts.current)) {
|
||||
chart?.service.refresh();
|
||||
}
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!autoRefresh) {
|
||||
return;
|
||||
}
|
||||
const timer = setInterval(
|
||||
() => {
|
||||
const refreshCharts = Object.values(charts.current).filter((chart) => !chart.selfAutoRefresh);
|
||||
for (const chart of refreshCharts) {
|
||||
chart?.service.refresh();
|
||||
}
|
||||
},
|
||||
(autoRefresh as number) * 1000,
|
||||
);
|
||||
return () => clearInterval(timer);
|
||||
}, [autoRefresh]);
|
||||
return (
|
||||
<GlobalAutoRefreshContext.Provider value={{ addChart, removeChart, autoRefresh, setAutoRefresh, refreshCharts }}>
|
||||
{props.children}
|
||||
</GlobalAutoRefreshContext.Provider>
|
||||
);
|
||||
};
|
@ -10,3 +10,5 @@
|
||||
export * from './ChartBlock';
|
||||
export * from './ChartBlockDesigner';
|
||||
export * from './ChartBlockInitializer';
|
||||
export * from './ChartBlockProvider';
|
||||
export * from './GlobalAutoRefreshProvider';
|
||||
|
@ -42,7 +42,7 @@ export const ChartConfigProvider: React.FC = (props) => {
|
||||
return (
|
||||
<ChartConfigContext.Provider value={{ visible, setVisible, current, setCurrent }}>
|
||||
{props.children}
|
||||
<ChartRendererProvider {...current.field?.decoratorProps}>
|
||||
<ChartRendererProvider {...current.field?.decoratorProps} disableAutoRefresh={true}>
|
||||
<ChartConfigure insert={(schema, options) => insertAdjacent('beforeEnd', schema, options)} />
|
||||
</ChartRendererProvider>
|
||||
</ChartConfigContext.Provider>
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
ChartV2Block,
|
||||
ChartV2BlockDesigner,
|
||||
ChartV2BlockInitializer,
|
||||
ChartBlockProvider,
|
||||
chartInitializers,
|
||||
chartInitializers_deprecated,
|
||||
} from './block';
|
||||
@ -25,6 +26,12 @@ import {
|
||||
chartFilterItemInitializers_deprecated,
|
||||
} from './filter';
|
||||
import { lang } from './locale';
|
||||
import { chartActionsInitializer } from './initializers/chartActions';
|
||||
import { chartActionRefreshSettings } from './settings/chartActionRefresh';
|
||||
import { useChartRefreshActionProps } from './initializers/RefreshAction';
|
||||
import { chartBlockActionsInitializer } from './initializers/chartBlockActions';
|
||||
import { useChartBlockRefreshActionProps } from './initializers/BlockRefreshAction';
|
||||
import { chartBlockActionRefreshSettings } from './settings/chartBlockActionRefresh';
|
||||
|
||||
class PluginDataVisualiztionClient extends Plugin {
|
||||
public charts: ChartGroup = new ChartGroup();
|
||||
@ -36,14 +43,22 @@ class PluginDataVisualiztionClient extends Plugin {
|
||||
ChartV2BlockInitializer,
|
||||
ChartV2BlockDesigner,
|
||||
ChartV2Block,
|
||||
ChartBlockProvider,
|
||||
});
|
||||
this.app.addScopes({
|
||||
useChartRefreshActionProps,
|
||||
useChartBlockRefreshActionProps,
|
||||
});
|
||||
|
||||
this.app.schemaInitializerManager.add(chartInitializers_deprecated);
|
||||
this.app.schemaInitializerManager.add(chartInitializers);
|
||||
this.app.schemaInitializerManager.add(chartFilterItemInitializers_deprecated);
|
||||
this.app.schemaInitializerManager.add(chartFilterItemInitializers);
|
||||
this.app.schemaInitializerManager.add(chartFilterActionInitializers_deprecated);
|
||||
this.app.schemaInitializerManager.add(chartFilterActionInitializers);
|
||||
this.app.schemaInitializerManager.add(chartActionsInitializer);
|
||||
this.app.schemaInitializerManager.add(chartBlockActionsInitializer);
|
||||
this.app.schemaSettingsManager.add(chartActionRefreshSettings);
|
||||
this.app.schemaSettingsManager.add(chartBlockActionRefreshSettings);
|
||||
|
||||
const blockInitializers = this.app.schemaInitializerManager.get('page:addBlock');
|
||||
blockInitializers?.add('dataBlocks.chartV2', {
|
||||
|
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { ActionInitializer } from '@nocobase/client';
|
||||
import React, { forwardRef, useContext, useEffect } from 'react';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { Dropdown, MenuProps } from 'antd';
|
||||
import { DownOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { GlobalAutoRefreshContext } from '../block/GlobalAutoRefreshProvider';
|
||||
|
||||
export const BlockRefreshButton: React.FC = forwardRef<HTMLButtonElement | HTMLAnchorElement, any>((props, ref) => {
|
||||
const { t } = useChartsTranslation();
|
||||
const { autoRefresh, setAutoRefresh } = useContext(GlobalAutoRefreshContext);
|
||||
const interval = {
|
||||
5: '5s',
|
||||
10: '10s',
|
||||
30: '30s',
|
||||
60: '1m',
|
||||
300: '5m',
|
||||
900: '15m',
|
||||
1800: '30m',
|
||||
3600: '1h',
|
||||
7200: '2h',
|
||||
86400: '1d',
|
||||
};
|
||||
const items: MenuProps['items'] = Object.entries(interval).map(([key, label]) => ({
|
||||
key,
|
||||
label,
|
||||
onClick: () => setAutoRefresh(+key),
|
||||
}));
|
||||
return (
|
||||
<Dropdown.Button
|
||||
{...props}
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'off',
|
||||
label: t('Off'),
|
||||
onClick: () => setAutoRefresh(false),
|
||||
},
|
||||
...items,
|
||||
],
|
||||
}}
|
||||
icon={<DownOutlined />}
|
||||
buttonsRender={([_, rightButton]) => [
|
||||
_,
|
||||
React.cloneElement(
|
||||
rightButton as React.ReactElement<any, string>,
|
||||
{ iconPosition: 'end' },
|
||||
autoRefresh ? interval[autoRefresh as number] : null,
|
||||
),
|
||||
]}
|
||||
>
|
||||
<ReloadOutlined />
|
||||
{t('Refresh')}
|
||||
{props.children}
|
||||
</Dropdown.Button>
|
||||
);
|
||||
});
|
||||
|
||||
export const useChartBlockRefreshActionProps = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { setAutoRefresh, refreshCharts } = useContext(GlobalAutoRefreshContext);
|
||||
useEffect(() => {
|
||||
setAutoRefresh(fieldSchema['x-decorator-props']?.autoRefresh);
|
||||
return () => {
|
||||
setAutoRefresh(false);
|
||||
};
|
||||
}, [fieldSchema, setAutoRefresh]);
|
||||
return {
|
||||
onClick: () => {
|
||||
refreshCharts?.();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const BlockRefreshActionInitializer = (props) => {
|
||||
const schema = {
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useChartBlockRefreshActionProps',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'chartBlockActionSettings:refresh',
|
||||
'x-component-props': {
|
||||
component: 'BlockRefreshButton',
|
||||
},
|
||||
};
|
||||
return <ActionInitializer {...props} schema={schema} />;
|
||||
};
|
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useContext, useEffect } from 'react';
|
||||
import { ActionInitializer, useDesignable } from '@nocobase/client';
|
||||
import { ChartRendererContext } from '../renderer';
|
||||
import { Dropdown, MenuProps } from 'antd';
|
||||
import { DownOutlined, ReloadOutlined } from '@ant-design/icons';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
|
||||
export const RefreshButton: React.FC = forwardRef<HTMLButtonElement | HTMLAnchorElement, any>((props, ref) => {
|
||||
const { t } = useChartsTranslation();
|
||||
const { autoRefresh, setAutoRefresh, showActionBar } = useContext(ChartRendererContext);
|
||||
const { designable } = useDesignable();
|
||||
const interval = {
|
||||
5: '5s',
|
||||
10: '10s',
|
||||
30: '30s',
|
||||
60: '1m',
|
||||
300: '5m',
|
||||
900: '15m',
|
||||
1800: '30m',
|
||||
3600: '1h',
|
||||
7200: '2h',
|
||||
86400: '1d',
|
||||
};
|
||||
const items: MenuProps['items'] = Object.entries(interval).map(([key, label]) => ({
|
||||
key,
|
||||
label,
|
||||
onClick: () => setAutoRefresh(+key),
|
||||
}));
|
||||
return showActionBar || designable ? (
|
||||
<Dropdown.Button
|
||||
{...props}
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
key: 'off',
|
||||
label: t('Off'),
|
||||
onClick: () => setAutoRefresh(false),
|
||||
},
|
||||
...items,
|
||||
],
|
||||
}}
|
||||
icon={<DownOutlined />}
|
||||
buttonsRender={([_, rightButton]) => [
|
||||
_,
|
||||
React.cloneElement(
|
||||
rightButton as React.ReactElement<any, string>,
|
||||
{ iconPosition: 'end' },
|
||||
autoRefresh ? interval[autoRefresh as number] : null,
|
||||
),
|
||||
]}
|
||||
>
|
||||
<ReloadOutlined />
|
||||
{props.children}
|
||||
</Dropdown.Button>
|
||||
) : null;
|
||||
});
|
||||
|
||||
export const useChartRefreshActionProps = () => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { service, setAutoRefresh } = useContext(ChartRendererContext);
|
||||
useEffect(() => {
|
||||
setAutoRefresh(fieldSchema['x-component-props']?.autoRefresh);
|
||||
return () => {
|
||||
setAutoRefresh(false);
|
||||
};
|
||||
}, [fieldSchema, setAutoRefresh]);
|
||||
return {
|
||||
onClick: service.refresh,
|
||||
};
|
||||
};
|
||||
|
||||
export const RefreshActionInitializer = (props) => {
|
||||
const schema = {
|
||||
'x-component': 'Action',
|
||||
'x-use-component-props': 'useChartRefreshActionProps',
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'chartActionSettings:refresh',
|
||||
'x-component-props': {
|
||||
size: 'small',
|
||||
component: 'RefreshButton',
|
||||
},
|
||||
};
|
||||
return <ActionInitializer {...props} schema={schema} />;
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { SchemaInitializer } from '@nocobase/client';
|
||||
import { lang } from '../locale';
|
||||
import { RefreshActionInitializer } from './RefreshAction';
|
||||
|
||||
export const chartActionsInitializer = new SchemaInitializer({
|
||||
name: 'chart:configureActions',
|
||||
title: lang('Configure actions'),
|
||||
icon: 'SettingOutlined',
|
||||
items: [
|
||||
{
|
||||
name: 'refresh',
|
||||
title: lang('Refresh'),
|
||||
Component: RefreshActionInitializer,
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { SchemaInitializer } from '@nocobase/client';
|
||||
import { lang } from '../locale';
|
||||
import { BlockRefreshActionInitializer } from './BlockRefreshAction';
|
||||
|
||||
export const chartBlockActionsInitializer = new SchemaInitializer({
|
||||
name: 'chartBlock:configureActions',
|
||||
title: lang('Configure actions'),
|
||||
icon: 'SettingOutlined',
|
||||
items: [
|
||||
{
|
||||
name: 'refresh',
|
||||
title: lang('Refresh'),
|
||||
Component: BlockRefreshActionInitializer,
|
||||
},
|
||||
],
|
||||
});
|
@ -7,32 +7,35 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
GeneralSchemaDesigner,
|
||||
SchemaSettingsBlockTitleItem,
|
||||
SchemaSettingsDivider,
|
||||
SchemaSettingsItem,
|
||||
SchemaSettingsRemove,
|
||||
gridRowColWrap,
|
||||
useAPIClient,
|
||||
useCollection_deprecated,
|
||||
useDataSource,
|
||||
useDesignable,
|
||||
} from '@nocobase/client';
|
||||
import { useAPIClient } from '@nocobase/client';
|
||||
import { Empty, Result, Spin, Typography } from 'antd';
|
||||
import React, { useContext, useEffect, useRef } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { ChartConfigContext } from '../configure';
|
||||
import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { createRendererSchema, getField } from '../utils';
|
||||
import { getField } from '../utils';
|
||||
import { ChartRendererContext } from './ChartRendererProvider';
|
||||
import { useChart } from '../chart/group';
|
||||
import { ChartDataContext } from '../block/ChartDataProvider';
|
||||
import { Schema } from '@formily/react';
|
||||
import { ChartRendererDesigner } from './ChartRendererDesigner';
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
const ErrorFallback = ({ error }) => {
|
||||
const { t } = useChartsTranslation();
|
||||
|
||||
return (
|
||||
<div style={{ backgroundColor: 'white' }}>
|
||||
<Result status="error" title={t('Render Failed')} subTitle={t('Please check the configuration.')}>
|
||||
<Paragraph copyable>
|
||||
<Text type="danger" style={{ whiteSpace: 'pre-line', textAlign: 'center' }}>
|
||||
{error.message}
|
||||
</Text>
|
||||
</Paragraph>
|
||||
</Result>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChartRenderer: React.FC & {
|
||||
Designer: React.FC;
|
||||
} = (props) => {
|
||||
@ -84,64 +87,4 @@ export const ChartRenderer: React.FC & {
|
||||
);
|
||||
};
|
||||
|
||||
ChartRenderer.Designer = function Designer() {
|
||||
const { t } = useChartsTranslation();
|
||||
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
||||
const { removeChart } = useContext(ChartDataContext);
|
||||
const { service } = useContext(ChartRendererContext);
|
||||
const field = useField();
|
||||
const schema = useFieldSchema();
|
||||
const { insertAdjacent } = useDesignable();
|
||||
const dataSource = useDataSource();
|
||||
const { name, title } = useCollection_deprecated();
|
||||
return (
|
||||
<GeneralSchemaDesigner disableInitializer title={title || name}>
|
||||
<SchemaSettingsItem
|
||||
title="Configure"
|
||||
key="configure"
|
||||
onClick={async () => {
|
||||
setCurrent({ schema, field, dataSource: dataSource.key, collection: name, service, data: service.data });
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{t('Configure')}
|
||||
</SchemaSettingsItem>
|
||||
<SchemaSettingsItem
|
||||
title="Duplicate"
|
||||
key="duplicate"
|
||||
onClick={() => insertAdjacent('afterEnd', gridRowColWrap(createRendererSchema(schema?.['x-decorator-props'])))}
|
||||
>
|
||||
{t('Duplicate')}
|
||||
</SchemaSettingsItem>
|
||||
{/* <SchemaSettingsBlockTitleItem /> */}
|
||||
<SchemaSettingsDivider />
|
||||
<SchemaSettingsRemove
|
||||
// removeParentsIfNoChildren
|
||||
breakRemoveOn={{
|
||||
'x-component': 'ChartV2Block',
|
||||
}}
|
||||
confirm={{
|
||||
onOk: () => {
|
||||
removeChart(schema['x-uid']);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
};
|
||||
|
||||
const ErrorFallback = ({ error }) => {
|
||||
const { t } = useChartsTranslation();
|
||||
|
||||
return (
|
||||
<div style={{ backgroundColor: 'white' }}>
|
||||
<Result status="error" title={t('Render Failed')} subTitle={t('Please check the configuration.')}>
|
||||
<Paragraph copyable>
|
||||
<Text type="danger" style={{ whiteSpace: 'pre-line', textAlign: 'center' }}>
|
||||
{error.message}
|
||||
</Text>
|
||||
</Paragraph>
|
||||
</Result>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
ChartRenderer.Designer = ChartRendererDesigner;
|
||||
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import {
|
||||
GeneralSchemaDesigner,
|
||||
SchemaSettingsDivider,
|
||||
SchemaSettingsItem,
|
||||
SchemaSettingsRemove,
|
||||
SchemaSettingsSelectItem,
|
||||
gridRowColWrap,
|
||||
useCollection_deprecated,
|
||||
useDataSource,
|
||||
useDesignable,
|
||||
} from '@nocobase/client';
|
||||
import React, { useContext } from 'react';
|
||||
import { ChartConfigContext } from '../configure';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { createRendererSchema } from '../utils';
|
||||
import { ChartRendererContext } from './ChartRendererProvider';
|
||||
import { ChartDataContext } from '../block/ChartDataProvider';
|
||||
|
||||
export function ChartRendererDesigner() {
|
||||
const { t } = useChartsTranslation();
|
||||
const { setVisible, setCurrent } = useContext(ChartConfigContext);
|
||||
const { removeChart } = useContext(ChartDataContext);
|
||||
const { service } = useContext(ChartRendererContext);
|
||||
const field = useField();
|
||||
const schema = useFieldSchema();
|
||||
const { insertAdjacent } = useDesignable();
|
||||
const dataSource = useDataSource();
|
||||
const { name, title } = useCollection_deprecated();
|
||||
return (
|
||||
<GeneralSchemaDesigner disableInitializer title={title || name}>
|
||||
<SchemaSettingsItem
|
||||
title="Configure"
|
||||
key="configure"
|
||||
onClick={async () => {
|
||||
setCurrent({ schema, field, dataSource: dataSource.key, collection: name, service, data: service.data });
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{t('Configure')}
|
||||
</SchemaSettingsItem>
|
||||
<SchemaSettingsItem
|
||||
title="Duplicate"
|
||||
key="duplicate"
|
||||
onClick={() => insertAdjacent('afterEnd', gridRowColWrap(createRendererSchema(schema?.['x-decorator-props'])))}
|
||||
>
|
||||
{t('Duplicate')}
|
||||
</SchemaSettingsItem>
|
||||
{/* <SchemaSettingsBlockTitleItem /> */}
|
||||
<SchemaSettingsDivider />
|
||||
<SchemaSettingsRemove
|
||||
// removeParentsIfNoChildren
|
||||
breakRemoveOn={{
|
||||
'x-component': 'ChartV2Block',
|
||||
}}
|
||||
confirm={{
|
||||
onOk: () => {
|
||||
removeChart(schema['x-uid']);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</GeneralSchemaDesigner>
|
||||
);
|
||||
}
|
@ -13,16 +13,14 @@ import {
|
||||
DEFAULT_DATA_SOURCE_KEY,
|
||||
MaybeCollectionProvider,
|
||||
useAPIClient,
|
||||
useDataSourceManager,
|
||||
useParsedFilter,
|
||||
useRequest,
|
||||
} from '@nocobase/client';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import { parseField, removeUnparsableFilter } from '../utils';
|
||||
import { ChartDataContext } from '../block/ChartDataProvider';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { useChartFilter } from '../hooks';
|
||||
import { ChartFilterContext } from '../filter/FilterProvider';
|
||||
import { GlobalAutoRefreshContext } from '../block/GlobalAutoRefreshProvider';
|
||||
|
||||
export type MeasureProps = {
|
||||
field: string | string[];
|
||||
@ -66,26 +64,36 @@ export type ChartRendererProps = {
|
||||
chartType: string;
|
||||
general: any;
|
||||
advanced: any;
|
||||
title?: string;
|
||||
bordered?: boolean;
|
||||
};
|
||||
transform?: TransformProps[];
|
||||
mode?: 'builder' | 'sql';
|
||||
disableAutoRefresh?: boolean;
|
||||
};
|
||||
|
||||
export const ChartRendererContext = createContext<
|
||||
{
|
||||
service: any;
|
||||
data?: any[];
|
||||
autoRefresh?: number | boolean;
|
||||
setAutoRefresh?: (autoRefresh: number | boolean) => void;
|
||||
showActionBar?: boolean;
|
||||
} & ChartRendererProps
|
||||
>({} as any);
|
||||
ChartRendererContext.displayName = 'ChartRendererContext';
|
||||
|
||||
export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
||||
const { query, config, collection, transform, dataSource = DEFAULT_DATA_SOURCE_KEY } = props;
|
||||
const { query, config, collection, transform, dataSource = DEFAULT_DATA_SOURCE_KEY, disableAutoRefresh } = props;
|
||||
const { addChart } = useContext(ChartDataContext);
|
||||
const { addChart: addGlobalAutoRefreshChart, removeChart: removeGlobalAutoRefreshChart } =
|
||||
useContext(GlobalAutoRefreshContext);
|
||||
const { ready, form, enabled } = useContext(ChartFilterContext);
|
||||
const { getFilter, hasFilter, appendFilter, parseFilter } = useChartFilter();
|
||||
const schema = useFieldSchema();
|
||||
const api = useAPIClient();
|
||||
const [autoRefresh, setAutoRefresh] = React.useState<number | boolean>(false);
|
||||
const [showActionBar, setShowActionBar] = React.useState<boolean>(false);
|
||||
const service = useRequest(
|
||||
async (dataSource, collection, query, manual) => {
|
||||
if (!(collection && query?.measures?.length)) return;
|
||||
@ -133,6 +141,9 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
||||
} finally {
|
||||
if (!manual && schema?.['x-uid']) {
|
||||
addChart(schema?.['x-uid'], { dataSource, collection, service, query });
|
||||
if (!autoRefresh) {
|
||||
addGlobalAutoRefreshChart?.(schema?.['x-uid'], { service });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -144,11 +155,41 @@ export const ChartRendererProvider: React.FC<ChartRendererProps> = (props) => {
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (disableAutoRefresh) {
|
||||
return;
|
||||
}
|
||||
if (!autoRefresh) {
|
||||
addGlobalAutoRefreshChart?.(schema?.['x-uid'], { service });
|
||||
return;
|
||||
}
|
||||
removeGlobalAutoRefreshChart?.(schema?.['x-uid']);
|
||||
const refresh = autoRefresh as number;
|
||||
const timer = setInterval(service.refresh, refresh * 1000);
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [autoRefresh, disableAutoRefresh]);
|
||||
|
||||
return (
|
||||
<CollectionManagerProvider dataSource={dataSource}>
|
||||
<MaybeCollectionProvider collection={collection}>
|
||||
<ChartRendererContext.Provider value={{ dataSource, collection, config, transform, service, query }}>
|
||||
{props.children}
|
||||
<ChartRendererContext.Provider
|
||||
value={{
|
||||
dataSource,
|
||||
collection,
|
||||
config,
|
||||
transform,
|
||||
service,
|
||||
query,
|
||||
autoRefresh,
|
||||
setAutoRefresh,
|
||||
showActionBar,
|
||||
}}
|
||||
>
|
||||
<div onMouseOver={() => setShowActionBar(true)} onMouseOut={() => setShowActionBar(false)}>
|
||||
{props.children}
|
||||
</div>
|
||||
</ChartRendererContext.Provider>
|
||||
</MaybeCollectionProvider>
|
||||
</CollectionManagerProvider>
|
||||
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { useField } from '@formily/react';
|
||||
import { SchemaSettingsSelectItem } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
|
||||
export const AutoRefreshItem: React.FC<{
|
||||
value: number | boolean;
|
||||
onChange?: (value: any) => void;
|
||||
}> = (props) => {
|
||||
const { t } = useChartsTranslation();
|
||||
return (
|
||||
<SchemaSettingsSelectItem
|
||||
title={t('Auto refresh')}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
options={[
|
||||
{
|
||||
label: t('Off'),
|
||||
value: false,
|
||||
},
|
||||
{
|
||||
label: '5s',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
label: '10s',
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
label: '30s',
|
||||
value: 30,
|
||||
},
|
||||
{
|
||||
label: '1m',
|
||||
value: 60,
|
||||
},
|
||||
{
|
||||
label: '5m',
|
||||
value: 300,
|
||||
},
|
||||
{
|
||||
label: '15m',
|
||||
value: 900,
|
||||
},
|
||||
{
|
||||
label: '30m',
|
||||
value: 1800,
|
||||
},
|
||||
{
|
||||
label: '1h',
|
||||
value: 3600,
|
||||
},
|
||||
{
|
||||
label: '2h',
|
||||
value: 7200,
|
||||
},
|
||||
{
|
||||
label: '1d',
|
||||
value: 86400,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { SchemaSettings, useDesignable } from '@nocobase/client';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { AutoRefreshItem } from './AutoRefreshItem';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import { ChartRendererContext } from '../renderer';
|
||||
|
||||
export const chartActionRefreshSettings = new SchemaSettings({
|
||||
name: 'chartActionSettings:refresh',
|
||||
items: [
|
||||
{
|
||||
name: 'refresh',
|
||||
Component: () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
const { setAutoRefresh } = useContext(ChartRendererContext);
|
||||
return (
|
||||
<AutoRefreshItem
|
||||
value={field.componentProps?.autoRefresh || false}
|
||||
onChange={(v) => {
|
||||
setAutoRefresh(v);
|
||||
field.componentProps = {
|
||||
...field.componentProps,
|
||||
autoRefresh: v,
|
||||
};
|
||||
fieldSchema['x-component-props'] = field.componentProps;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-component-props': field.componentProps,
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'remove',
|
||||
useComponentProps() {
|
||||
const { t } = useChartsTranslation();
|
||||
return {
|
||||
removeParentsIfNoChildren: true,
|
||||
breakRemoveOn: (s) => {
|
||||
return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar');
|
||||
},
|
||||
confirm: {
|
||||
title: t('Delete action'),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import { SchemaSettings, useDesignable } from '@nocobase/client';
|
||||
import { useChartsTranslation } from '../locale';
|
||||
import { AutoRefreshItem } from './AutoRefreshItem';
|
||||
import { useField, useFieldSchema } from '@formily/react';
|
||||
import { GlobalAutoRefreshContext } from '../block';
|
||||
|
||||
export const chartBlockActionRefreshSettings = new SchemaSettings({
|
||||
name: 'chartBlockActionSettings:refresh',
|
||||
items: [
|
||||
{
|
||||
name: 'refresh',
|
||||
Component: () => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { dn } = useDesignable();
|
||||
const { setAutoRefresh } = useContext(GlobalAutoRefreshContext);
|
||||
return (
|
||||
<AutoRefreshItem
|
||||
value={field.decoratorProps?.autoRefresh || false}
|
||||
onChange={(v) => {
|
||||
setAutoRefresh(v);
|
||||
field.decoratorProps = {
|
||||
...field.decoratorProps,
|
||||
autoRefresh: v,
|
||||
};
|
||||
fieldSchema['x-decorator-props'] = field.decoratorProps;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
'x-decorator-props': field.decoratorProps,
|
||||
},
|
||||
});
|
||||
dn.refresh();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
type: 'remove',
|
||||
useComponentProps() {
|
||||
const { t } = useChartsTranslation();
|
||||
return {
|
||||
removeParentsIfNoChildren: true,
|
||||
breakRemoveOn: (s) => {
|
||||
return s['x-component'] === 'Space' || s['x-component'].endsWith('ActionBar');
|
||||
},
|
||||
confirm: {
|
||||
title: t('Delete action'),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
@ -12,7 +12,8 @@ import { uid } from '@formily/shared';
|
||||
import lodash from 'lodash';
|
||||
import { SelectedField } from './configure';
|
||||
import { FieldOption } from './hooks';
|
||||
import { QueryProps } from './renderer';
|
||||
import { ChartRendererContext, QueryProps } from './renderer';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const createRendererSchema = (decoratorProps: any, componentProps = {}) => {
|
||||
const { collection, config } = decoratorProps;
|
||||
@ -31,6 +32,26 @@ export const createRendererSchema = (decoratorProps: any, componentProps = {}) =
|
||||
},
|
||||
'x-initializer': 'charts:addBlock',
|
||||
properties: {
|
||||
actions: {
|
||||
type: 'void',
|
||||
'x-decorator': 'div',
|
||||
'x-decorator-props': {
|
||||
style: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
zIndex: 10,
|
||||
},
|
||||
},
|
||||
'x-component': 'ActionBar',
|
||||
'x-component-props': {
|
||||
style: {
|
||||
marginRight: 'var(--nb-designer-offset)',
|
||||
marginTop: 'var(--nb-designer-offset)',
|
||||
},
|
||||
},
|
||||
'x-initializer': 'chart:configureActions',
|
||||
},
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-component': 'ChartRenderer',
|
||||
|
@ -95,5 +95,6 @@
|
||||
"Fixed height": "Fixed height",
|
||||
"Show background": "Show background",
|
||||
"Show padding": "Show padding",
|
||||
"Distinct": "Distinct"
|
||||
"Distinct": "Distinct",
|
||||
"Auto refresh": "Auto refresh"
|
||||
}
|
||||
|
@ -96,5 +96,6 @@
|
||||
"Fixed height": "固定高度",
|
||||
"Show background": "显示背景",
|
||||
"Show padding": "显示内边距",
|
||||
"Distinct": "去重"
|
||||
"Distinct": "去重",
|
||||
"Auto refresh": "自动刷新"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user