diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlock.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlock.tsx index 95a7b09a2a..9f7004af38 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlock.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlock.tsx @@ -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 }} >
{ }, }, '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', diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlockProvider.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlockProvider.tsx new file mode 100644 index 0000000000..9e035d7a0d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartBlockProvider.tsx @@ -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 ( + + {props.children} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartDataProvider.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartDataProvider.tsx index 7360e6f819..5fdf63afe2 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartDataProvider.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/ChartDataProvider.tsx @@ -34,6 +34,7 @@ export const ChartDataProvider: React.FC = (props) => { const removeChart = useMemoizedFn((uid: string) => { setCharts((charts) => ({ ...charts, [uid]: undefined })); }); + return ( {props.children} ); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/GlobalAutoRefreshProvider.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/GlobalAutoRefreshProvider.tsx new file mode 100644 index 0000000000..08d9a5b832 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/GlobalAutoRefreshProvider.tsx @@ -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(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 ( + + {props.children} + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/index.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/index.ts index e9048ca4af..1a00061fe7 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/index.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/block/index.ts @@ -10,3 +10,5 @@ export * from './ChartBlock'; export * from './ChartBlockDesigner'; export * from './ChartBlockInitializer'; +export * from './ChartBlockProvider'; +export * from './GlobalAutoRefreshProvider'; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigProvider.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigProvider.tsx index 75fe81736d..48b8abac78 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigProvider.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigProvider.tsx @@ -42,7 +42,7 @@ export const ChartConfigProvider: React.FC = (props) => { return ( {props.children} - + insertAdjacent('beforeEnd', schema, options)} /> diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx index 027a0cd2e3..70c945c3f5 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx @@ -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', { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/BlockRefreshAction.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/BlockRefreshAction.tsx new file mode 100644 index 0000000000..fee51839e9 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/BlockRefreshAction.tsx @@ -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((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 ( + setAutoRefresh(false), + }, + ...items, + ], + }} + icon={} + buttonsRender={([_, rightButton]) => [ + _, + React.cloneElement( + rightButton as React.ReactElement, + { iconPosition: 'end' }, + autoRefresh ? interval[autoRefresh as number] : null, + ), + ]} + > + + {t('Refresh')} + {props.children} + + ); +}); + +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 ; +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/RefreshAction.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/RefreshAction.tsx new file mode 100644 index 0000000000..608da85060 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/RefreshAction.tsx @@ -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((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 ? ( + setAutoRefresh(false), + }, + ...items, + ], + }} + icon={} + buttonsRender={([_, rightButton]) => [ + _, + React.cloneElement( + rightButton as React.ReactElement, + { iconPosition: 'end' }, + autoRefresh ? interval[autoRefresh as number] : null, + ), + ]} + > + + {props.children} + + ) : 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 ; +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/chartActions.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/chartActions.tsx new file mode 100644 index 0000000000..6268c04d56 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/chartActions.tsx @@ -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, + }, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/chartBlockActions.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/chartBlockActions.tsx new file mode 100644 index 0000000000..83b0acc089 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/initializers/chartBlockActions.tsx @@ -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, + }, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx index 913d96f5d2..adfa3def36 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRenderer.tsx @@ -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 ( +
+ + + + {error.message} + + + +
+ ); +}; + 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 ( - - { - setCurrent({ schema, field, dataSource: dataSource.key, collection: name, service, data: service.data }); - setVisible(true); - }} - > - {t('Configure')} - - insertAdjacent('afterEnd', gridRowColWrap(createRendererSchema(schema?.['x-decorator-props'])))} - > - {t('Duplicate')} - - {/* */} - - { - removeChart(schema['x-uid']); - }, - }} - /> - - ); -}; - -const ErrorFallback = ({ error }) => { - const { t } = useChartsTranslation(); - - return ( -
- - - - {error.message} - - - -
- ); -}; +ChartRenderer.Designer = ChartRendererDesigner; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererDesigner.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererDesigner.tsx new file mode 100644 index 0000000000..0d344c20be --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererDesigner.tsx @@ -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 ( + + { + setCurrent({ schema, field, dataSource: dataSource.key, collection: name, service, data: service.data }); + setVisible(true); + }} + > + {t('Configure')} + + insertAdjacent('afterEnd', gridRowColWrap(createRendererSchema(schema?.['x-decorator-props'])))} + > + {t('Duplicate')} + + {/* */} + + { + removeChart(schema['x-uid']); + }, + }} + /> + + ); +} diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererProvider.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererProvider.tsx index 1aa8bf9963..e2f22ec5d9 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererProvider.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/renderer/ChartRendererProvider.tsx @@ -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 = (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(false); + const [showActionBar, setShowActionBar] = React.useState(false); const service = useRequest( async (dataSource, collection, query, manual) => { if (!(collection && query?.measures?.length)) return; @@ -133,6 +141,9 @@ export const ChartRendererProvider: React.FC = (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 = (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 ( - - {props.children} + +
setShowActionBar(true)} onMouseOut={() => setShowActionBar(false)}> + {props.children} +
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/AutoRefreshItem.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/AutoRefreshItem.tsx new file mode 100644 index 0000000000..c1dfcce65c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/AutoRefreshItem.tsx @@ -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 ( + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/chartActionRefresh.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/chartActionRefresh.tsx new file mode 100644 index 0000000000..f911e08427 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/chartActionRefresh.tsx @@ -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 ( + { + 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'), + }, + }; + }, + }, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/chartBlockActionRefresh.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/chartBlockActionRefresh.tsx new file mode 100644 index 0000000000..57f641efd1 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/settings/chartBlockActionRefresh.tsx @@ -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 ( + { + 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'), + }, + }; + }, + }, + ], +}); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts index a823640fa5..43701e5a42 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/utils.ts @@ -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', diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-data-visualization/src/locale/en-US.json index 7a6b127771..2abce3cad8 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/locale/en-US.json @@ -95,5 +95,6 @@ "Fixed height": "Fixed height", "Show background": "Show background", "Show padding": "Show padding", - "Distinct": "Distinct" + "Distinct": "Distinct", + "Auto refresh": "Auto refresh" } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-data-visualization/src/locale/zh-CN.json index 563357e91f..811d8a6ca7 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/locale/zh-CN.json @@ -96,5 +96,6 @@ "Fixed height": "固定高度", "Show background": "显示背景", "Show padding": "显示内边距", - "Distinct": "去重" + "Distinct": "去重", + "Auto refresh": "自动刷新" }