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:
YANG QIA 2024-09-03 14:46:43 +08:00 committed by GitHub
parent dfe517e737
commit 61f95f78c2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 743 additions and 92 deletions

View File

@ -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`

View File

@ -12,7 +12,6 @@ import {
SchemaSettingsBlockTitleItem,
SchemaSettingsDivider,
SchemaSettingsRemove,
SchemaSettingsSelectItem,
SchemaSettingsSwitchItem,
useDesignable,
useToken,

View File

@ -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',

View File

@ -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>
);
};

View File

@ -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>
);

View File

@ -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>
);
};

View File

@ -10,3 +10,5 @@
export * from './ChartBlock';
export * from './ChartBlockDesigner';
export * from './ChartBlockInitializer';
export * from './ChartBlockProvider';
export * from './GlobalAutoRefreshProvider';

View File

@ -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>

View File

@ -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', {

View File

@ -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} />;
};

View File

@ -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} />;
};

View File

@ -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,
},
],
});

View File

@ -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,
},
],
});

View File

@ -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;

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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,
},
]}
/>
);
};

View File

@ -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'),
},
};
},
},
],
});

View File

@ -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'),
},
};
},
},
],
});

View File

@ -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',

View File

@ -95,5 +95,6 @@
"Fixed height": "Fixed height",
"Show background": "Show background",
"Show padding": "Show padding",
"Distinct": "Distinct"
"Distinct": "Distinct",
"Auto refresh": "Auto refresh"
}

View File

@ -96,5 +96,6 @@
"Fixed height": "固定高度",
"Show background": "显示背景",
"Show padding": "显示内边距",
"Distinct": "去重"
"Distinct": "去重",
"Auto refresh": "自动刷新"
}