tachybase_todo/packages/plugins/@nocobase/plugin-data-visualization/src/client/chart/chart.ts
jack zhang 705b7449f0
feat: new plugin manager, supports adding plugins through UI (#2430)
* refactor: plugin manager page

* fix: bug

* feat: addByNpm api

* fix: improve the addByNpm

* feat: improve applicationPlugins:list api

* fix: re-download npm package when restart app

* fix: plugin delete api

* feat: plugin detail api

* feat: zipUrl add api

* fix: upload api bug

* fix: plugin detail info

* feat: upgrade api

* fix: upload api

* feat: handle plugin load error

* feat: support authToken

* feat: muti lang

* fix: build error

* fix: self review

* Update plugin-manager.ts

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: bugs

* fix: detail click and remove isOfficial

* fix: upgrade no refresh

* fix: file size and type check

* fix: bug

* fix: upgrade error

* fix: bug

* fix: bug

* fix: plugin card layout

* fix: handling exceptional cases

* fix: tgz file support

* fix: macos compress file

* fix: bug

* fix: bug

* fix: bug

* fix: bug

* fix: add upgrade npm type

* fix: bugs

* fix: bug

* fix: change plugins static expose url

* fix: api prefix

* fix: bug

* fix: add nginx `/static/plugin/` path

* fix: bugs and pr docker build no dts

* fix: bug

* fix: build tools bug

* fix: improve code

* fix: build bug

* feat: improve plugin info

* fix: ui bug

* fix: plugin document bug

* feat: improve code

* feat: improve code

* feat: process dev deps check

* feat: improve code

* feat: process.env.IS_DEV_CMD

* fix: do not delete the plugin package

* feat: plugin symlink

* fix: tsx watch --ignore=./storage/plugins/**

* fix: test error

* fix: improve code

* fix: improve code

* fix: emitStartedEvent

* fix: improve code

* fix: type error

* fix: test error

* test: console.log

* fix: createStoragePluginSymLink

* fix: clientStaticMiddleware rename to clientStaticUtils

* feat: build tools support plugins folder

* fix: 350px

* fix: error

* feat: client dev support plugin folder

* fix: clear cli options

* fix: typeError: Converting circular structure to JSON

* fix: plugin name

* chore: restart application after command

* feat: upgrade error & docs

* Update v14-changelog.md

* Update v14-changelog.md

* Update v14-changelog.md

* fix: gateway test

* refactor(plugin-workflow): add ready state for gracefully tearing down

* Revert "chore: restart application after command"

This reverts commit 5015274f8e4e06e506e15754b672330330e8c7f8.

* chore: stop application whe restart

* T 1218 change plugin folder (#2629)

* feat: change folder name

* feat: change `pm create` command

* feat:  revert plugin name change

* fix: delete samples

* feat: change plugins folder

* fix: pm create

* feat: update docs

* fix: link package error

* fix: docs

* fix: create command

* fix: pm add error

* fix: create  add build

* fix: pm creatre + add

* feat: add tar command

* fix: docs

* fix: bug

* fix: docs

---------

Co-authored-by: chenos <chenlinxh@gmail.com>

* feat: docs

* Update your-fisrt-plugin.md

* Update your-fisrt-plugin.md

* chore: application reload

* chore: test

* fix: pm add error

* chore: preset install skip exists plugin

* fix: createIfNotExists

---------

Co-authored-by: chenos <chenlinxh@gmail.com>
Co-authored-by: chareice <chareice@live.com>
Co-authored-by: Zhou <zhou.working@gmail.com>
Co-authored-by: mytharcher <mytharcher@gmail.com>
2023-09-12 22:39:23 +08:00

198 lines
5.1 KiB
TypeScript

import React from 'react';
import { FieldOption } from '../hooks';
import { QueryProps } from '../renderer';
import { parseField } from '../utils';
import { ISchema } from '@formily/react';
import configs, { AnySchemaProperties, ConfigProps } from './configs';
export type RenderProps = {
data: any[];
general: any;
advanced: any;
fieldProps: {
[field: string]: FieldOption & {
transformer: (val: any) => string;
};
};
};
export interface ChartType {
name: string;
title: string;
component: React.FC<any>;
schema: ISchema;
infer: (
fields: FieldOption[],
{
measures,
dimensions,
}: {
measures?: QueryProps['measures'];
dimensions?: QueryProps['dimensions'];
},
) => {
xField: FieldOption;
yField: FieldOption;
seriesField: FieldOption;
yFields: FieldOption[];
};
init?: (
fields: FieldOption[],
query: {
measures?: QueryProps['measures'];
dimensions?: QueryProps['dimensions'];
},
) => {
general?: any;
advanced?: any;
};
/**
* getProps
* Accept the information that the chart component needs to render,
* process it and return the props of the chart component.
*/
getProps: (props: RenderProps) => any;
getReference?: () => {
title: string;
link: string;
};
render: (props: RenderProps) => React.FC<any>;
}
type Config = (
| (ConfigProps & {
property?: string;
})
| string
)[];
export type ChartProps = {
name: string;
title: string;
component: React.FC<any>;
config?: Config;
};
export class Chart implements ChartType {
name: string;
title: string;
component: React.FC<any>;
config: Config;
configs = new Map<string, Function>();
constructor({ name, title, component, config }: ChartProps) {
this.name = name;
this.title = title;
this.component = component;
this.config = config;
this.addConfigs(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
* 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
*/
get schema() {
if (!this.config) {
return {};
}
const properties = this.config.reduce((properties, conf) => {
let schema: AnySchemaProperties = {};
if (typeof conf === 'string') {
const func = this.configs.get(conf);
schema = func?.() || {};
} else if (typeof conf === 'function') {
schema = conf();
} else {
if (conf.property) {
const func = this.configs.get(conf.property);
schema = func?.(conf) || {};
} else {
schema = conf as AnySchemaProperties;
}
}
return {
...properties,
...schema,
};
}, {} as AnySchemaProperties);
return {
type: 'object',
properties,
};
}
addConfigs(configs: { [key: string]: Function }) {
Object.entries(configs).forEach(([key, func]) => {
this.configs.set(key, func);
});
}
infer(
fields: FieldOption[],
{
measures,
dimensions,
}: {
measures?: QueryProps['measures'];
dimensions?: QueryProps['dimensions'];
},
) {
let xField: FieldOption;
let yField: FieldOption;
let seriesField: FieldOption;
let yFields: FieldOption[];
const getField = (fields: FieldOption[], selected: { field: string | string[]; alias?: string }) => {
if (selected.alias) {
return fields.find((f) => f.value === selected.alias);
}
const { alias } = parseField(selected.field);
return fields.find((f) => f.value === alias);
};
if (measures?.length) {
yField = getField(fields, measures[0]);
yFields = measures.map((m) => getField(fields, m));
}
if (dimensions) {
if (dimensions.length === 1) {
xField = getField(fields, dimensions[0]);
} else if (dimensions.length > 1) {
// If there is a time field, it is used as the x-axis field by default.
let xIndex: number;
dimensions.forEach((d, i) => {
const field = getField(fields, d);
if (['date', 'time', 'datetime'].includes(field?.type)) {
xField = field;
xIndex = i;
}
});
if (xIndex) {
// If there is a time field, the other field is used as the series field by default.
const index = xIndex === 0 ? 1 : 0;
seriesField = getField(fields, dimensions[index]);
} else {
xField = getField(fields, dimensions[0]);
seriesField = getField(fields, dimensions[1]);
}
}
}
return { xField, yField, seriesField, yFields };
}
getProps(props: RenderProps) {
return props;
}
render({ data, general, advanced, fieldProps }: RenderProps) {
return () =>
React.createElement(this.component, {
...this.getProps({ data, general, advanced, fieldProps }),
});
}
}