From ce74a77e9687853f69170737dcf90210d1b68af2 Mon Sep 17 00:00:00 2001 From: YANG QIA <2013xile@gmail.com> Date: Tue, 8 Oct 2024 20:15:00 +0800 Subject: [PATCH] feat(data-vi): optimize (#5299) * refactor(data-vi): add echarts * fix: echart * feat: echarts * chore: add migration * feat: update * feat: dark theme * feat: add configuration * chore: update * chore: update * fix: bug * chore: update * chore: update * fix: test * fix: build * chore: update * chore: locale --- .../src/client/__tests__/chart-api.test.ts | 45 ++-- .../src/client/chart/antd/statistic.ts | 3 +- .../src/client/chart/antd/table.ts | 2 +- .../src/client/chart/chart.ts | 40 +-- .../src/client/chart/configs.ts | 229 ++++++++++++++++-- .../src/client/chart/g2plot/AntChart.tsx | 30 +-- .../src/client/chart/g2plot/configs.ts | 103 -------- .../src/client/chart/g2plot/dualAxes.ts | 3 +- .../src/client/chart/g2plot/g2plot.ts | 3 +- .../src/client/chart/g2plot/index.ts | 14 +- .../src/client/chart/g2plot/pie.ts | 6 +- .../src/client/chart/group.ts | 61 ++--- .../src/client/configure/ChartConfigure.tsx | 10 +- .../src/client/configure/schemas/configure.ts | 64 ++--- .../src/client/hooks/chart.ts | 51 ++++ .../src/client/hooks/index.ts | 1 + .../src/client/index.tsx | 4 +- .../src/client/renderer/ChartRenderer.tsx | 31 ++- .../src/locale/en-US.json | 26 +- .../src/locale/zh-CN.json | 16 +- .../20240921214400-rename-charttype.ts | 43 ++++ yarn.lock | 59 ++--- 22 files changed, 515 insertions(+), 329 deletions(-) delete mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts create mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/chart.ts create mode 100644 packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20240921214400-rename-charttype.ts diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts index e8be89497e..b28f0ec663 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/__tests__/chart-api.test.ts @@ -19,43 +19,36 @@ describe('api', () => { plugin.charts = new ChartGroup(); }); - test('setGroup', () => { - const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; - plugin.charts.setGroup('group', charts1); - expect(plugin.charts.charts.get('group')).toEqual(charts1); - - const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; - plugin.charts.setGroup('group', charts2); - expect(plugin.charts.charts.get('group')).toEqual(charts2); - }); - test('addGroup', () => { const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; - plugin.charts.setGroup('group1', charts1); + plugin.charts.addGroup('group', { title: 'Group', charts: charts1 }); + expect(plugin.charts.charts.get('group')).toEqual({ title: 'Group', charts: charts1 }); + const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; - plugin.charts.addGroup('group2', charts2); - expect(plugin.charts.charts.get('group1')).toEqual(charts1); - expect(plugin.charts.charts.get('group2')).toEqual(charts2); + try { + plugin.charts.addGroup('group', { title: 'Group2', charts: charts2 }); + } catch (error) { + expect(error.message).toEqual('[data-visualization] Chart group "group" already exists'); + } }); test('add', () => { const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; - plugin.charts.setGroup('group', charts1); - + plugin.charts.addGroup('group', { title: 'Group', charts: charts1 }); const chart = new Chart({ name: 'test2', title: 'Test2', Component: null }); plugin.charts.add('group', chart); - expect(plugin.charts.charts.get('group').length).toEqual(2); - expect(plugin.charts.charts.get('group')[1].name).toEqual('test2'); + expect(plugin.charts.charts.get('group').charts.length).toEqual(2); + expect(plugin.charts.charts.get('group').charts[1].name).toEqual('test2'); }); test('getChartTypes', () => { const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; - plugin.charts.setGroup('group1', charts1); + plugin.charts.addGroup('group1', { title: 'Group1', charts: charts1 }); const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; - plugin.charts.setGroup('group2', charts2); + plugin.charts.addGroup('group2', { title: 'Group2', charts: charts2 }); expect(plugin.charts.getChartTypes()).toEqual([ { - label: 'group1', + label: 'Group1', children: [ { key: 'group1.test1', @@ -65,7 +58,7 @@ describe('api', () => { ], }, { - label: 'group2', + label: 'Group2', children: [ { key: 'group2.test2', @@ -79,9 +72,9 @@ describe('api', () => { test('getCharts', () => { const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; - plugin.charts.setGroup('group1', charts1); + plugin.charts.addGroup('group1', { title: 'Group1', charts: charts1 }); const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; - plugin.charts.setGroup('group2', charts2); + plugin.charts.addGroup('group2', { title: 'Group2', charts: charts2 }); expect(plugin.charts.getCharts()).toEqual({ 'group1.test1': charts1[0], 'group2.test2': charts2[0], @@ -90,9 +83,9 @@ describe('api', () => { test('getChart', () => { const charts1 = [new Chart({ name: 'test1', title: 'Test1', Component: null })]; - plugin.charts.setGroup('group1', charts1); + plugin.charts.addGroup('group1', { title: 'Group1', charts: charts1 }); const charts2 = [new Chart({ name: 'test2', title: 'Test2', Component: null })]; - plugin.charts.setGroup('group2', charts2); + plugin.charts.addGroup('group2', { title: 'Group2', charts: charts2 }); expect(plugin.charts.getChart('group1.test1')).toEqual(charts1[0]); expect(plugin.charts.getChart('group2.test2')).toEqual(charts2[0]); }); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts index 12eb95e32c..b2a7d513da 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/statistic.ts @@ -17,10 +17,11 @@ export class Statistic extends AntdChart { super({ name: 'statistic', title: 'Statistic', + enableAdvancedConfig: true, Component: C, config: [ { - property: 'field', + configType: 'field', name: 'field', title: 'Field', required: true, diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts index d9a074b8d6..7d5dd87246 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/antd/table.ts @@ -13,7 +13,7 @@ import { Table as AntdTable } from 'antd'; export class Table extends AntdChart { constructor() { - super({ name: 'table', title: 'Table', Component: AntdTable }); + super({ name: 'table', title: 'Table', enableAdvancedConfig: true, Component: AntdTable }); } getProps({ data, fieldProps, general, advanced }: RenderProps) { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts index 3f21499551..6a099e06ed 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts @@ -7,12 +7,12 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React from 'react'; +import React, { memo } from 'react'; import { FieldOption } from '../hooks'; import { DimensionProps, MeasureProps, QueryProps } from '../renderer'; import { parseField } from '../utils'; import { ISchema } from '@formily/react'; -import configs, { AnySchemaProperties, Config, FieldConfigProps } from './configs'; +import configs, { AnySchemaProperties, Config, ConfigType } from './configs'; import { Transformer } from '../transformers'; export type RenderProps = { @@ -31,6 +31,7 @@ export type RenderProps = { export interface ChartType { name: string; title: string; + enableAdvancedConfig?: boolean; Component: React.FC; schema: ISchema; init?: ( @@ -53,6 +54,7 @@ export interface ChartType { export type ChartProps = { name: string; title: string; + enableAdvancedConfig?: boolean; Component: React.FC; config?: Config[]; }; @@ -60,24 +62,26 @@ export type ChartProps = { export class Chart implements ChartType { name: string; title: string; + enableAdvancedConfig = false; Component: React.FC; config: Config[]; - configs = new Map(); + configTypes = new Map(); - constructor({ name, title, Component, config }: ChartProps) { + constructor({ name, title, enableAdvancedConfig, Component, config }: ChartProps) { this.name = name; this.title = title; - this.Component = Component; + this.Component = memo(Component, (prev, next) => JSON.stringify(prev) === JSON.stringify(next)); this.config = config; - this.addConfigs(configs); + this.enableAdvancedConfig = enableAdvancedConfig || false; + this.addConfigTypes(configs); } /* * Generate config schema according to this.config * How to set up this.config: * 1. string - the config function name in config.ts - * 2. object - { property: string, ...props } - * - property is the config function name in config.ts, and the other props are the arguments of the function + * 2. object - { configType: string, ...props } + * - sttingType is the config function name in config.ts, and the other props are the arguments of the function * 3. object - use the object directly as the properties of the schema * 4. function - use the custom function to return the properties of the schema */ @@ -85,35 +89,35 @@ export class Chart implements ChartType { if (!this.config) { return {}; } - const properties = this.config.reduce((properties, conf) => { + const properties = this.config.reduce((props, conf) => { let schema: AnySchemaProperties = {}; if (typeof conf === 'string') { - const func = this.configs.get(conf); - schema = func?.() || {}; - } else if (typeof conf === 'function') { + conf = this.configTypes.get(conf); + } + if (typeof conf === 'function') { schema = conf(); } else { - if (conf.property) { - const func = this.configs.get(conf.property); + if (conf.configType) { + const func = this.configTypes.get(conf.configType as string) as Function; schema = func?.(conf) || {}; } else { schema = conf as AnySchemaProperties; } } return { - ...properties, + ...props, ...schema, }; - }, {} as AnySchemaProperties); + }, {} as any); return { type: 'object', properties, }; } - addConfigs(configs: { [key: string]: (props: FieldConfigProps) => AnySchemaProperties }) { + addConfigTypes(configs: { [key: string]: ConfigType }) { Object.entries(configs).forEach(([key, func]) => { - this.configs.set(key, func); + this.configTypes.set(key, func); }); } diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts index 8725598d47..ecee939995 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/configs.ts @@ -15,48 +15,241 @@ export type FieldConfigProps = Partial<{ title: string; required: boolean; defaultValue: any; + description: string; + options: { label: string; value: any }[]; + componentProps: Record; }>; export type AnySchemaProperties = SchemaProperties; -export type ConfigProps = FieldConfigProps | AnySchemaProperties | (() => AnySchemaProperties); +export type ConfigType = + | (FieldConfigProps & { configType?: string }) + | ((props?: FieldConfigProps) => AnySchemaProperties) + | AnySchemaProperties; -export type Config = - | (ConfigProps & { - property?: string; - }) - | string; +export type Config = string | ConfigType; -const selectField = ({ name, title, required, defaultValue }: FieldConfigProps) => { +const field = ({ name, title, required, defaultValue, description }: FieldConfigProps) => { return { - [name || 'field']: { - title: lang(title || 'Field'), + [name]: { + title: lang(title), type: 'string', 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-reactions': '{{ useChartFields }}', required, + description, default: defaultValue, }, }; }; -const booleanField = ({ name, title, defaultValue = false }: FieldConfigProps) => { +const select = ({ name, title, required, defaultValue, options, description }: FieldConfigProps) => { return { - [name || 'field']: { - 'x-content': lang(title || 'Field'), + [name]: { + title: lang(title), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Select', + required, + default: defaultValue, + description, + enum: options, + }, + }; +}; + +const boolean = ({ name, title, defaultValue = false, description }: FieldConfigProps) => { + return { + [name]: { + 'x-content': lang(title), type: 'boolean', 'x-decorator': 'FormItem', 'x-component': 'Checkbox', default: defaultValue, + description, + }, + }; +}; + +const radio = ({ name, title, defaultValue, options, description, componentProps }: FieldConfigProps) => { + return { + [name]: { + title: lang(title), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Radio.Group', + 'x-component-props': { + ...componentProps, + }, + default: defaultValue, + description, + enum: options, + }, + }; +}; + +const percent = ({ name, title, defaultValue, description }: FieldConfigProps) => { + return { + [name]: { + title, + type: 'number', + 'x-decorator': 'FormItem', + 'x-component': 'InputNumber', + default: defaultValue, + description, + 'x-component-props': { + suffix: '%', + }, + }, + }; +}; + +const input = ({ name, title, required, defaultValue, description }: FieldConfigProps) => { + return { + [name]: { + title: lang(title), + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + required, + default: defaultValue, + description, }, }; }; export default { - field: selectField, - booleanField, - xField: (props: FieldConfigProps) => selectField({ name: 'xField', title: 'xField', required: true, ...props }), - yField: (props: FieldConfigProps) => selectField({ name: 'yField', title: 'yField', required: true, ...props }), - seriesField: (props: FieldConfigProps) => selectField({ name: 'seriesField', title: 'seriesField', ...props }), - colorField: (props: FieldConfigProps) => selectField({ name: 'colorField', title: 'colorField', ...props }), + field, + input, + boolean, + select, + radio, + percent, + xField: { + configType: 'field', + name: 'xField', + title: 'xField', + required: true, + }, + yField: { + configType: 'field', + name: 'yField', + title: 'yField', + required: true, + }, + seriesField: { + configType: 'field', + name: 'seriesField', + title: 'seriesField', + }, + colorField: { + configType: 'field', + name: 'colorField', + title: 'colorField', + required: true, + }, + isStack: { + configType: 'boolean', + name: 'isStack', + title: 'isStack', + }, + smooth: { + configType: 'boolean', + name: 'smooth', + title: 'smooth', + }, + isPercent: { + configType: 'boolean', + name: 'isPercent', + title: 'isPercent', + }, + isGroup: { + configType: 'boolean', + name: 'isGroup', + title: 'isGroup', + }, + size: () => ({ + size: { + title: lang('Size'), + type: 'object', + 'x-decorator': 'FormItem', + 'x-component': 'Space', + properties: { + type: { + 'x-component': 'Select', + 'x-component-props': { + allowClear: false, + }, + default: 'ratio', + enum: [ + { + label: lang('Aspect ratio'), + value: 'ratio', + }, + { + label: lang('Fixed height'), + value: 'fixed', + }, + ], + }, + fixed: { + type: 'number', + 'x-component': 'InputNumber', + 'x-component-props': { + min: 0, + addonAfter: 'px', + }, + 'x-reactions': [ + { + dependencies: ['.type'], + fulfill: { + state: { + visible: "{{$deps[0] === 'fixed'}}", + }, + }, + }, + ], + }, + ratio: { + type: 'object', + 'x-component': 'Space', + 'x-reactions': [ + { + dependencies: ['.type'], + fulfill: { + state: { + visible: "{{$deps[0] === 'ratio'}}", + }, + }, + }, + ], + properties: { + width: { + type: 'number', + 'x-component': 'InputNumber', + 'x-component-props': { + placeholder: lang('Width'), + min: 1, + }, + }, + colon: { + type: 'void', + 'x-component': 'Text', + 'x-component-props': { + children: ':', + }, + }, + height: { + type: 'number', + 'x-component': 'InputNumber', + 'x-component-props': { + placeholder: lang('Height'), + min: 1, + }, + }, + }, + }, + }, + }, + }), }; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/AntChart.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/AntChart.tsx index 8e50a22931..e5c10fdf67 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/AntChart.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/AntChart.tsx @@ -7,8 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React, { useContext, useEffect, useRef } from 'react'; -import { ChartRendererContext } from '../../renderer'; +import React from 'react'; +import { useSetChartSize } from '../../hooks/chart'; import { useGlobalTheme } from '@nocobase/client'; export const getAntChart = (Component: React.FC) => (props: any) => { @@ -18,31 +18,7 @@ export const getAntChart = (Component: React.FC) => (props: any) => { if (!fixedHeight && size.type === 'fixed') { fixedHeight = size.fixed; } - const { service } = useContext(ChartRendererContext); - const chartRef = useRef(null); - const [height, setHeight] = React.useState(0); - useEffect(() => { - const el = chartRef.current; - if (!el || service.loading === true || fixedHeight) { - return; - } - let ratio = 0; - if (size.type === 'ratio' && size.ratio?.width && size.ratio?.height) { - ratio = size.ratio.height / size.ratio.width; - } - const observer = new ResizeObserver((entries) => { - entries.forEach((entry) => { - if (ratio) { - setHeight(entry.contentRect.width * ratio); - return; - } - setHeight(entry.contentRect.height); - }); - }); - observer.observe(el); - return () => observer.disconnect(); - }, [service.loading, fixedHeight, size.type, size.ratio?.width, size.ratio?.height]); - const chartHeight = fixedHeight || height; + const { chartRef, chartHeight } = useSetChartSize(size, fixedHeight); return (
diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts deleted file mode 100644 index edbbce55e7..0000000000 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/configs.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * 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 config, { FieldConfigProps } from '../configs'; -const { booleanField } = config; -import { lang } from '../../locale'; - -export default { - isStack: (props: FieldConfigProps) => booleanField({ name: 'isStack', title: 'isStack', ...props }), - smooth: (props: FieldConfigProps) => booleanField({ name: 'smooth', title: 'smooth', ...props }), - isPercent: (props: FieldConfigProps) => booleanField({ name: 'isPercent', title: 'isPercent', ...props }), - isGroup: (props: FieldConfigProps) => booleanField({ name: 'isGroup', title: 'isGroup', ...props }), - size: () => ({ - size: { - title: lang('Size'), - type: 'object', - 'x-decorator': 'FormItem', - 'x-component': 'Space', - properties: { - type: { - 'x-component': 'Select', - 'x-component-props': { - allowClear: false, - }, - default: 'ratio', - enum: [ - { - label: lang('Aspect ratio'), - value: 'ratio', - }, - { - label: lang('Fixed height'), - value: 'fixed', - }, - ], - }, - fixed: { - type: 'number', - 'x-component': 'InputNumber', - 'x-component-props': { - min: 0, - addonAfter: 'px', - }, - 'x-reactions': [ - { - dependencies: ['.type'], - fulfill: { - state: { - visible: "{{$deps[0] === 'fixed'}}", - }, - }, - }, - ], - }, - ratio: { - type: 'object', - 'x-component': 'Space', - 'x-reactions': [ - { - dependencies: ['.type'], - fulfill: { - state: { - visible: "{{$deps[0] === 'ratio'}}", - }, - }, - }, - ], - properties: { - width: { - type: 'number', - 'x-component': 'InputNumber', - 'x-component-props': { - placeholder: lang('Width'), - min: 1, - }, - }, - colon: { - type: 'void', - 'x-component': 'Text', - 'x-component-props': { - children: ':', - }, - }, - height: { - type: 'number', - 'x-component': 'InputNumber', - 'x-component-props': { - placeholder: lang('Height'), - min: 1, - }, - }, - }, - }, - }, - }, - }), -}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts index a538440bfc..407180bd39 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/dualAxes.ts @@ -9,13 +9,12 @@ import { G2PlotChart } from './g2plot'; import { ChartType, RenderProps } from '../chart'; -import React from 'react'; import { DualAxes as G2DualAxes } from '@ant-design/plots'; import lodash from 'lodash'; export class DualAxes extends G2PlotChart { constructor() { - super({ name: 'dualAxes', title: 'Dual Axes Chart', Component: G2DualAxes }); + super({ name: 'dualAxes', title: 'Dual axes', Component: G2DualAxes }); this.config = [ 'xField', { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts index f35d744bd1..82d8c1490a 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/g2plot.ts @@ -8,7 +8,6 @@ */ import { Chart, ChartProps, ChartType, RenderProps } from '../chart'; -import configs from './configs'; import { getAntChart } from './AntChart'; export class G2PlotChart extends Chart { @@ -16,10 +15,10 @@ export class G2PlotChart extends Chart { super({ name, title, + enableAdvancedConfig: true, Component: getAntChart(Component), config: ['xField', 'yField', 'seriesField', 'size', ...(config || [])], }); - this.addConfigs(configs); } init: ChartType['init'] = (fields, { measures, dimensions }) => { diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts index af2a552cb5..8595615bf2 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/index.ts @@ -15,18 +15,20 @@ import { G2PlotChart } from './g2plot'; export default [ new G2PlotChart({ name: 'line', - title: 'Line Chart', + title: 'Line', Component: Line, config: ['smooth', 'isStack'], }), new G2PlotChart({ name: 'area', - title: 'Area Chart', + title: 'Area', Component: Area, config: [ 'smooth', { - property: 'isStack', + configType: 'boolean', + name: 'isStack', + title: 'isStack', defaultValue: true, }, 'isPercent', @@ -34,17 +36,17 @@ export default [ }), new G2PlotChart({ name: 'column', - title: 'Column Chart', + title: 'Column', Component: Column, config: ['isGroup', 'isStack', 'isPercent'], }), new G2PlotChart({ name: 'bar', - title: 'Bar Chart', + title: 'Bar', Component: Bar, config: ['isGroup', 'isStack', 'isPercent'], }), new Pie(), new DualAxes(), - new G2PlotChart({ name: 'scatter', title: 'Scatter Chart', Component: Scatter }), + new G2PlotChart({ name: 'scatter', title: 'Scatter', Component: Scatter }), ]; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts index 109940890e..f9c98eea60 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/g2plot/pie.ts @@ -13,16 +13,16 @@ import { ChartType, RenderProps } from '../chart'; export class Pie extends G2PlotChart { constructor() { - super({ name: 'pie', title: 'Pie Chart', Component: G2Pie }); + super({ name: 'pie', title: 'Pie', Component: G2Pie }); this.config = [ { - property: 'field', + configType: 'field', name: 'angleField', title: 'angleField', required: true, }, { - property: 'field', + configType: 'field', name: 'colorField', title: 'colorField', required: true, diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts index 7070d2a2eb..1c76e4e002 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/group.ts @@ -12,28 +12,37 @@ import { ChartType } from './chart'; import DataVisualizationPlugin from '..'; import { lang } from '../locale'; +interface Group { + title: string; + charts: ChartType[]; + sort?: number; +} + export class ChartGroup { /** * @internal */ - charts: Map = new Map(); + charts: Map = new Map(); - setGroup(name: string, charts: ChartType[]) { - this.charts.set(name, charts); - } - - addGroup(name: string, charts: ChartType[]) { + addGroup(name: string, group: Group) { if (this.charts.has(name)) { throw new Error(`[data-visualization] Chart group "${name}" already exists`); } - this.setGroup(name, charts); + this.charts.set(name, group); } - add(group: string, chart: ChartType) { - if (!this.charts.has(group)) { - this.setGroup(group, []); + add(name: string, charts: ChartType | ChartType[]) { + if (!this.charts.has(name)) { + return; } - this.charts.get(group)?.push(chart); + if (!Array.isArray(charts)) { + charts = [charts]; + } + const group = this.charts.get(name); + this.charts.set(name, { + ...group, + charts: [...group.charts, ...charts], + }); } /** @@ -48,23 +57,19 @@ export class ChartGroup { }[]; }[] { const result = []; - this.charts.forEach((charts, group) => { - const children = charts.map((chart) => ({ - key: `${group}.${chart.name}`, - label: lang(chart.title), - value: `${group}.${chart.name}`, - })); - result.push({ - label: lang(group), - children, + Array.from(this.charts.entries()) + .sort(([, a], [, b]) => a.sort || 0 - b.sort || 0) + .forEach(([group, { title, charts }]) => { + const children = charts.map((chart) => ({ + key: `${group}.${chart.name}`, + label: lang(chart.title), + value: `${group}.${chart.name}`, + })); + result.push({ + label: lang(title), + children, + }); }); - }); - // Put group named "Built-in" at the first - const index = result.findIndex((item) => item.label === lang('Built-in')); - if (index > -1) { - const [item] = result.splice(index, 1); - result.unshift(item); - } return result; } @@ -75,7 +80,7 @@ export class ChartGroup { [key: string]: ChartType; } { const result = {}; - this.charts.forEach((charts, group) => { + this.charts.forEach(({ charts }, group) => { charts.forEach((chart) => { result[`${group}.${chart.name}`] = chart; }); diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx index 11bfa13bba..8b9c7a2b5b 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/ChartConfigure.tsx @@ -22,7 +22,7 @@ import { } from '@nocobase/client'; import { Alert, App, Button, Card, Col, Modal, Row, Space, Table, Tabs, Typography, theme } from 'antd'; import { cloneDeep, isEqual } from 'lodash'; -import React, { useContext, useEffect, useMemo, useRef } from 'react'; +import React, { memo, useContext, useEffect, useMemo, useRef } from 'react'; import { useChartFields, useCollectionOptions, @@ -101,6 +101,7 @@ export const ChartConfigure: React.FC<{ const selectedFields = getSelectedFields(fields, query); const { general, advanced } = chart.init(selectedFields, query); if (general || overwrite) { + form.setInitialValuesIn('config.general', {}); form.values.config.general = general; } if (advanced || overwrite) { @@ -142,7 +143,7 @@ export const ChartConfigure: React.FC<{ [field, visible, dataSource, collection], ); - const RunButton: React.FC = () => ( + const RunButton: React.FC = memo(() => ( - ); + )); const queryRef = useRef(null); const configRef = useRef(null); @@ -434,10 +435,11 @@ ChartConfigure.Config = function Config() { {(form) => { const chartType = form.values.config?.chartType; const chart = charts[chartType]; + const enableAdvancedConfig = chart?.enableAdvancedConfig; const schema = chart?.schema || {}; return ( diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/schemas/configure.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/schemas/configure.ts index 71343fc02d..a2ffd2d6e6 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/schemas/configure.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/configure/schemas/configure.ts @@ -47,7 +47,7 @@ const getArraySchema = (fields = {}, extra = {}) => ({ }, }); -export const getConfigSchema = (general: any): ISchema => ({ +export const getConfigSchema = (general: any, enableAdvancedConfig?: boolean): ISchema => ({ type: 'void', properties: { config: { @@ -115,37 +115,43 @@ export const getConfigSchema = (general: any): ISchema => ({ general, }, }, - [uid()]: { - type: 'void', - properties: { - advanced: { - type: 'json', - title: '{{t("JSON config")}}', - 'x-decorator': 'FormItem', - 'x-decorator-props': { - extra: lang('Same properties set in the form above will be overwritten by this JSON config.'), - }, - 'x-component': 'Input.JSON', - 'x-component-props': { - autoSize: { - minRows: 3, + ...(enableAdvancedConfig + ? { + [uid()]: { + type: 'void', + properties: { + advanced: { + type: 'json', + title: '{{t("JSON config")}}', + 'x-decorator': 'FormItem', + 'x-decorator-props': { + extra: lang( + 'Same properties set in the form above will be overwritten by this JSON config.', + ), + }, + 'x-component': 'Input.JSON', + 'x-component-props': { + autoSize: { + minRows: 3, + }, + json5: true, + }, + }, }, - json5: true, }, - }, - }, - }, - reference: { - type: 'string', - 'x-reactions': { - dependencies: ['.chartType'], - fulfill: { - schema: { - 'x-content': '{{ getReference($deps[0]) }}', + reference: { + type: 'string', + 'x-reactions': { + dependencies: ['.chartType'], + fulfill: { + schema: { + 'x-content': '{{ getReference($deps[0]) }}', + }, + }, + }, }, - }, - }, - }, + } + : {}), }, }, }, diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/chart.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/chart.ts new file mode 100644 index 0000000000..fbdf1a6e22 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/chart.ts @@ -0,0 +1,51 @@ +/** + * 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 { useContext, useEffect, useRef, useState } from 'react'; +import { ChartRendererContext } from '../renderer'; + +export const useSetChartSize = ( + size: { + type: 'fixed' | 'ratio'; + ratio?: { + width: number; + height: number; + }; + fixed?: number; + }, + fixedHeight?: number, +) => { + const [height, setHeight] = useState(0); + const chartRef = useRef(null); + const { service } = useContext(ChartRendererContext); + useEffect(() => { + const el = chartRef.current; + if (!el || service.loading === true || fixedHeight) { + return; + } + let ratio = 0; + if (size.type === 'ratio' && size.ratio?.width && size.ratio?.height) { + ratio = size.ratio.height / size.ratio.width; + } + const observer = new ResizeObserver((entries) => { + entries.forEach((entry) => { + if (ratio) { + setHeight(entry.contentRect.width * ratio); + return; + } + setHeight(entry.contentRect.height); + }); + }); + observer.observe(el); + return () => observer.disconnect(); + }, [service.loading, fixedHeight, size.type, size.ratio?.width, size.ratio?.height]); + const chartHeight = fixedHeight || height; + + return { chartRef, chartHeight }; +}; diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/index.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/index.ts index cce623cf6e..6940ea93fa 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/index.ts +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/hooks/index.ts @@ -11,3 +11,4 @@ export * from './query'; export * from './transformer'; export * from './useVariableOptions'; export * from './filter'; +export * from './chart'; 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 5b180b4dfb..3398be1e6d 100644 --- a/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/client/index.tsx @@ -39,7 +39,8 @@ class PluginDataVisualiztionClient extends Plugin { public charts: ChartGroup = new ChartGroup(); async load() { - this.charts.setGroup('Built-in', [...g2plot, ...antd]); + this.charts.addGroup('antd', { title: 'Ant Design', charts: antd }); + this.charts.addGroup('ant-design-charts', { title: 'Ant Design Charts', charts: g2plot }); this.app.addComponents({ ChartV2BlockInitializer, @@ -85,5 +86,6 @@ export default PluginDataVisualiztionClient; export { Chart } from './chart/chart'; export type { ChartProps, ChartType, RenderProps } from './chart/chart'; export { ChartConfigContext } from './configure'; +export { useSetChartSize } from './hooks'; export type { FieldOption } from './hooks'; export type { QueryProps } from './renderer'; 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 adfa3def36..95b5744c88 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 @@ -9,7 +9,7 @@ import { useAPIClient } from '@nocobase/client'; import { Empty, Result, Spin, Typography } from 'antd'; -import React, { useContext } from 'react'; +import React, { useContext, useEffect } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { useData, useFieldTransformer, useFieldsWithAssociation } from '../hooks'; import { useChartsTranslation } from '../locale'; @@ -18,6 +18,7 @@ import { ChartRendererContext } from './ChartRendererProvider'; import { useChart } from '../chart/group'; import { Schema } from '@formily/react'; import { ChartRendererDesigner } from './ChartRendererDesigner'; +import { uid } from '@formily/shared'; const { Paragraph, Text } = Typography; const ErrorFallback = ({ error }) => { @@ -50,7 +51,21 @@ export const ChartRenderer: React.FC & { const chart = useChart(config?.chartType); const locale = api.auth.getLocale(); const transformers = useFieldTransformer(transform, locale); - const chartProps = chart?.getProps({ + // error key is used for resetting error boundary when config changes + const [errorKey, setErrorKey] = React.useState(uid()); + useEffect(() => { + setErrorKey(uid()); + }, [config]); + + if (!chart) { + return ; + } + + if (!(data && data.length) && !service.loading) { + return ; + } + + const chartProps = chart.getProps({ data, general, advanced, @@ -64,24 +79,18 @@ export const ChartRenderer: React.FC & { }, {}), }); const compiledProps = Schema.compile(chartProps); - const C = chart?.Component; - - if (!chart) { - return ; - } - if (!(data && data.length) && !service.loading) { - return ; - } + const C = chart.Component; return ( { console.error(error); }} FallbackComponent={ErrorFallback} > - + {!service.loading && } ); 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 2abce3cad8..d5fbefc7e7 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 @@ -34,19 +34,19 @@ "Type": "Type", "Add field": "Add field", "Add chart": "Add chart", - "xField": "xField", - "yField": "yField", - "seriesField": "seriesField", - "angleField": "angleField", - "colorField": "colorField", - "Line Chart": "Line Chart", - "Area Chart": "Area Chart", - "Column Chart": "Column Chart", - "Bar Chart": "Bar Chart", - "Pie Chart": "Pie Chart", - "Dual Axes Chart": "Dual Axes Chart", - "Scatter Chart": "Scatter Chart", - "Gauge Chart": "Gauge Chart", + "xField": "X field", + "yField": "Y field", + "seriesField": "Series field", + "angleField": "Angle field", + "colorField": "Color field", + "Line": "Line", + "Area": "Area", + "Column": "Column", + "Bar": "Bar", + "Pie": "Pie", + "Dual axes": "Dual axes", + "Scatter": "Scatter", + "Gauge": "Gauge", "Statistic": "Statistic", "Currency": "Currency", "Percent": "Percent", 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 811d8a6ca7..813d9a15dd 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 @@ -39,14 +39,14 @@ "seriesField": "分类字段", "angleField": "角度字段", "colorField": "颜色字段", - "Line Chart": "折线图", - "Area Chart": "面积图", - "Column Chart": "柱状图", - "Bar Chart": "条形图", - "Pie Chart": "饼图", - "Dual Axes Chart": "双轴图", - "Scatter Chart": "散点图", - "Gauge Chart": "仪表盘", + "Line": "折线图", + "Area": "面积图", + "Column": "柱状图", + "Bar": "条形图", + "Pie": "饼图", + "Dual axes": "双轴图", + "Scatter": "散点图", + "Gauge": "仪表盘", "Statistic": "统计", "Currency": "货币", "Percent": "百分比", diff --git a/packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20240921214400-rename-charttype.ts b/packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20240921214400-rename-charttype.ts new file mode 100644 index 0000000000..79927be297 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-data-visualization/src/server/migrations/20240921214400-rename-charttype.ts @@ -0,0 +1,43 @@ +/** + * 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 { Repository } from '@nocobase/database'; +import { Migration } from '@nocobase/server'; + +export default class extends Migration { + on = 'afterLoad'; // 'beforeLoad' or 'afterLoad' + appVersion = '<=1.5.0-beta'; + async up() { + const r = this.db.getRepository('uiSchemas'); + const items = await r.find({ + filter: { + 'schema.x-decorator': 'ChartRendererProvider', + }, + }); + + await this.db.sequelize.transaction(async (transaction) => { + for (const item of items) { + const schema = item.schema; + const chartType = schema['x-decorator-props']?.config?.chartType; + if (!chartType) { + continue; + } + if (chartType.startsWith('Built-in.')) { + if (chartType === 'Built-in.statistic' || chartType === 'Built-in.table') { + schema['x-decorator-props'].config.chartType = chartType.replace('Built-in', 'antd'); + } else { + schema['x-decorator-props'].config.chartType = chartType.replace('Built-in', 'ant-design-charts'); + } + item.set('schema', schema); + await item.save({ transaction }); + } + } + }); + } +} diff --git a/yarn.lock b/yarn.lock index 441b7bf670..c04dc14d0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12975,6 +12975,22 @@ ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer "^5.0.1" +echarts-for-react@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/echarts-for-react/-/echarts-for-react-3.0.2.tgz#ac5859157048a1066d4553e34b328abb24f2b7c1" + integrity sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA== + dependencies: + fast-deep-equal "^3.1.3" + size-sensor "^1.0.1" + +echarts@^5.5.0: + version "5.5.0" + resolved "https://registry.npmmirror.com/echarts/-/echarts-5.5.0.tgz#c13945a7f3acdd67c134d8a9ac67e917830113ac" + integrity sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw== + dependencies: + tslib "2.3.0" + zrender "5.5.0" + editions@^2.2.0: version "2.3.1" resolved "https://registry.npmmirror.com/editions/-/editions-2.3.1.tgz#3bc9962f1978e801312fbd0aebfed63b49bfe698" @@ -25213,7 +25229,7 @@ string-convert@^0.2.0: resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -25231,15 +25247,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" @@ -25354,7 +25361,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -25382,13 +25389,6 @@ strip-ansi@^5.1.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -26318,6 +26318,11 @@ tslib@1.9.3: resolved "https://registry.npmmirror.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== +tslib@2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" + integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== + tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -27783,7 +27788,7 @@ wordwrap@^1.0.0: resolved "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -27809,15 +27814,6 @@ wrap-ansi@^6.0.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -28170,6 +28166,13 @@ zip-stream@^4.1.0: compress-commons "^4.1.2" readable-stream "^3.6.0" +zrender@5.5.0: + version "5.5.0" + resolved "https://registry.npmmirror.com/zrender/-/zrender-5.5.0.tgz#54d0d6c4eda81a96d9f60a9cd74dc48ea026bc1e" + integrity sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w== + dependencies: + tslib "2.3.0" + zustand@^4.4.1: version "4.4.7" resolved "https://registry.npmmirror.com/zustand/-/zustand-4.4.7.tgz#355406be6b11ab335f59a66d2cf9815e8f24038c"