mirror of
https://github.com/nocobase/nocobase
synced 2024-11-14 16:23:30 +00:00
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
This commit is contained in:
parent
bcd154453f
commit
ce74a77e96
@ -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]);
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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<any>;
|
||||
schema: ISchema;
|
||||
init?: (
|
||||
@ -53,6 +54,7 @@ export interface ChartType {
|
||||
export type ChartProps = {
|
||||
name: string;
|
||||
title: string;
|
||||
enableAdvancedConfig?: boolean;
|
||||
Component: React.FC<any>;
|
||||
config?: Config[];
|
||||
};
|
||||
@ -60,24 +62,26 @@ export type ChartProps = {
|
||||
export class Chart implements ChartType {
|
||||
name: string;
|
||||
title: string;
|
||||
enableAdvancedConfig = false;
|
||||
Component: React.FC<any>;
|
||||
config: Config[];
|
||||
configs = new Map<string, Function>();
|
||||
configTypes = new Map<string, ConfigType>();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -15,48 +15,241 @@ export type FieldConfigProps = Partial<{
|
||||
title: string;
|
||||
required: boolean;
|
||||
defaultValue: any;
|
||||
description: string;
|
||||
options: { label: string; value: any }[];
|
||||
componentProps: Record<string, any>;
|
||||
}>;
|
||||
|
||||
export type AnySchemaProperties = SchemaProperties<any, any, any, any, any, any, any, any>;
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
@ -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<any>) => (props: any) => {
|
||||
@ -18,31 +18,7 @@ export const getAntChart = (Component: React.FC<any>) => (props: any) => {
|
||||
if (!fixedHeight && size.type === 'fixed') {
|
||||
fixedHeight = size.fixed;
|
||||
}
|
||||
const { service } = useContext(ChartRendererContext);
|
||||
const chartRef = useRef(null);
|
||||
const [height, setHeight] = React.useState<number>(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 (
|
||||
<div ref={chartRef} style={chartHeight ? { height: `${chartHeight}px` } : {}}>
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
@ -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',
|
||||
{
|
||||
|
@ -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 }) => {
|
||||
|
@ -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 }),
|
||||
];
|
||||
|
@ -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,
|
||||
|
@ -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<string, ChartType[]> = new Map();
|
||||
charts: Map<string, Group> = 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;
|
||||
});
|
||||
|
@ -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(() => (
|
||||
<Button
|
||||
type="link"
|
||||
loading={service?.loading}
|
||||
@ -165,7 +166,7 @@ export const ChartConfigure: React.FC<{
|
||||
>
|
||||
{t('Run query')}
|
||||
</Button>
|
||||
);
|
||||
));
|
||||
|
||||
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 (
|
||||
<SchemaComponent
|
||||
schema={getConfigSchema(schema)}
|
||||
schema={getConfigSchema(schema, enableAdvancedConfig)}
|
||||
scope={{ t, chartTypes, useChartFields: getChartFields, getReference, formCollapse }}
|
||||
components={{ FormItem, ArrayItems, Space, AutoComplete, FormCollapse }}
|
||||
/>
|
||||
|
@ -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]) }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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<number>(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 };
|
||||
};
|
@ -11,3 +11,4 @@ export * from './query';
|
||||
export * from './transformer';
|
||||
export * from './useVariableOptions';
|
||||
export * from './filter';
|
||||
export * from './chart';
|
||||
|
@ -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';
|
||||
|
@ -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 <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />;
|
||||
}
|
||||
|
||||
if (!(data && data.length) && !service.loading) {
|
||||
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('No data')} />;
|
||||
}
|
||||
|
||||
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 <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('Please configure chart')} />;
|
||||
}
|
||||
if (!(data && data.length) && !service.loading) {
|
||||
return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('No data')} />;
|
||||
}
|
||||
const C = chart.Component;
|
||||
|
||||
return (
|
||||
<Spin spinning={service.loading}>
|
||||
<ErrorBoundary
|
||||
key={errorKey}
|
||||
onError={(error) => {
|
||||
console.error(error);
|
||||
}}
|
||||
FallbackComponent={ErrorFallback}
|
||||
>
|
||||
<C {...compiledProps} />
|
||||
{!service.loading && <C {...compiledProps} />}
|
||||
</ErrorBoundary>
|
||||
</Spin>
|
||||
);
|
||||
|
@ -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",
|
||||
|
@ -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": "百分比",
|
||||
|
@ -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<Repository>('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 });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
59
yarn.lock
59
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"
|
||||
|
Loading…
Reference in New Issue
Block a user