fix: collection undefined (#1169)

Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#1169
This commit is contained in:
sealday 2024-06-13 02:00:46 +08:00
parent 0cf4a450de
commit 6fc13dd14f
11 changed files with 6 additions and 440 deletions

View File

@ -1,8 +1,8 @@
import React, { FC, ReactNode, createContext, useContext, useMemo } from 'react'; import React, { createContext, FC, ReactNode, useContext, useMemo } from 'react';
import { useCollectionManager } from './CollectionManagerProvider';
import { CollectionDeletedPlaceholder } from '../components/CollectionDeletedPlaceholder'; import { CollectionDeletedPlaceholder } from '../components/CollectionDeletedPlaceholder';
import type { CollectionOptions, Collection, GetCollectionFieldPredicate } from './Collection'; import type { Collection, CollectionOptions, GetCollectionFieldPredicate } from './Collection';
import { useCollectionManager } from './CollectionManagerProvider';
export const CollectionContext = createContext<Collection>(null); export const CollectionContext = createContext<Collection>(null);
CollectionContext.displayName = 'CollectionContext'; CollectionContext.displayName = 'CollectionContext';

View File

@ -6,7 +6,6 @@ import { useFilterActionProps } from './filter/useFilterActionProps';
import { formV1Settings } from './form'; import { formV1Settings } from './form';
import { filterFormItemSettings, formItemSettings } from './form-item'; import { filterFormItemSettings, formItemSettings } from './form-item';
import { formDetailsSettings, formSettings, readPrettyFormSettings } from './form-v2'; import { formDetailsSettings, formSettings, readPrettyFormSettings } from './form-v2';
import { requestChartData } from './g2plot/requestChartData';
import { pageSettings, pageTabSettings } from './page'; import { pageSettings, pageTabSettings } from './page';
export class AntdSchemaComponentPlugin extends Plugin { export class AntdSchemaComponentPlugin extends Plugin {
@ -25,7 +24,6 @@ export class AntdSchemaComponentPlugin extends Plugin {
addScopes() { addScopes() {
this.app.addScopes({ this.app.addScopes({
requestChartData,
useFilterActionProps, useFilterActionProps,
}); });
} }

View File

@ -22,16 +22,16 @@ export const useGetAriaLabelOfBlockItem = (defaultName?: string) => {
const collection = useCollection(); const collection = useCollection();
const name = defaultName || blockName; const name = defaultName || blockName;
const title = compile(fieldSchema['title']) || compile(collection.getField(fieldSchema.name)?.uiSchema?.title); const title = compile(fieldSchema['title']) || compile(collection?.getField(fieldSchema.name)?.uiSchema?.title);
const getAriaLabel = useCallback( const getAriaLabel = useCallback(
(postfix?: string) => { (postfix?: string) => {
postfix = postfix ? `-${postfix}` : ''; postfix = postfix ? `-${postfix}` : '';
return ['block-item', component, collection.name, name, collectionField, title, postfix] return ['block-item', component, collection?.name, name, collectionField, title, postfix]
.filter(Boolean) .filter(Boolean)
.join('-'); .join('-');
}, },
[component, collection.name, name, collectionField, title], [component, collection?.name, name, collectionField, title],
); );
return { return {

View File

@ -1,183 +0,0 @@
import {
Area,
Bar,
BidirectionalBar,
Box,
Bullet,
Chord,
CirclePacking,
Column,
DualAxes,
Facet,
Funnel,
Gauge,
Heatmap,
Histogram,
Line,
Liquid,
Mix,
Pie,
Progress,
Radar,
RadialBar,
RingProgress,
Rose,
Sankey,
Scatter,
Stock,
Sunburst,
TinyArea,
TinyColumn,
TinyLine,
Treemap,
Venn,
Violin,
Waterfall,
WordCloud,
} from '@antv/g2plot';
import { Field } from '@tachybase/schema';
import { observer, useField } from '@tachybase/schema';
import { Spin } from 'antd';
import cls from 'classnames';
import React, { forwardRef, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../api-client';
import { G2PlotDesigner } from './G2PlotDesigner';
export type ReactG2PlotProps<O> = {
readonly className?: string;
readonly plot: any;
readonly config: O;
};
const plots = {
Line,
Area,
Column,
Bar,
Pie,
Rose,
WordCloud,
Scatter,
Radar,
DualAxes,
TinyLine,
TinyColumn,
TinyArea,
Histogram,
Progress,
RingProgress,
Heatmap,
Box,
Violin,
Venn,
Stock,
Funnel,
Liquid,
Bullet,
Sunburst,
Gauge,
Waterfall,
RadialBar,
BidirectionalBar,
Treemap,
Sankey,
Chord,
CirclePacking,
Mix,
Facet,
};
export const G2PlotRenderer = forwardRef(function <O = any>(props: ReactG2PlotProps<O>, ref: any) {
const { className, plot, config } = props;
const containerRef = useRef(undefined);
const plotRef = useRef(undefined);
function syncRef(source, target) {
if (typeof target === 'function') {
target(source.current);
} else if (target) {
target.current = source.current;
}
}
function renderPlot() {
if (plotRef.current) {
plotRef.current.update(config);
} else {
plotRef.current = new plot(containerRef.current, config);
plotRef?.current?.render();
}
syncRef(plotRef, ref);
}
function destoryPlot() {
if (plotRef.current) {
plotRef.current.destroy();
plotRef.current = undefined;
}
}
useEffect(() => {
renderPlot();
return () => destoryPlot();
}, [config, plot]);
return <div className={cls(['g2plot', className])} ref={containerRef} />;
});
G2PlotRenderer.displayName = 'G2PlotRenderer';
export const G2Plot: any = observer(
(props: any) => {
const { plot, config } = props;
const field = useField<Field>();
const { t } = useTranslation();
const api = useAPIClient();
useEffect(() => {
field.data = field.data || {};
field.data.loading = true;
const fn = config?.data;
if (typeof fn === 'function') {
const result = fn.bind({ api })();
if (result?.then) {
result
.then((data) => {
if (Array.isArray(data)) {
field.componentProps.config.data = data;
}
field.data.loading = false;
})
.catch(console.error);
} else {
field.data.loading = false;
}
} else {
field.data.loading = false;
}
}, []);
if (!plot || !config) {
return <div style={{ opacity: 0.3 }}>{t('In configuration')}...</div>;
}
if (field?.data?.loading !== false) {
return <Spin />;
}
return (
<div>
{field.title && <h2>{field.title}</h2>}
<G2PlotRenderer
plot={plots[plot]}
config={{
...config,
data: Array.isArray(config?.data) ? config.data : [],
}}
/>
</div>
);
},
{ displayName: 'G2Plot' },
);
G2Plot.Designer = G2PlotDesigner;
G2Plot.plots = plots;

View File

@ -1,120 +0,0 @@
import { ISchema, useField, useFieldSchema } from '@tachybase/schema';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../api-client';
import {
GeneralSchemaDesigner,
SchemaSettingsDivider,
SchemaSettingsModalItem,
SchemaSettingsRemove,
} from '../../../schema-settings';
import { useCompile, useDesignable } from '../../hooks';
import _ from 'lodash';
const validateJSON = {
validator: `{{(value, rule)=> {
if (!value) {
return '';
}
try {
const val = JSON.parse(value);
if(!isNaN(val)) {
return false;
}
return true;
} catch(error) {
console.error(error);
return false;
}
}}}`,
message: '{{t("Invalid JSON format")}}',
};
export const G2PlotDesigner = () => {
const { t } = useTranslation();
const { dn } = useDesignable();
const fieldSchema = useFieldSchema();
const field = useField();
const compile = useCompile();
const api = useAPIClient();
return (
<GeneralSchemaDesigner>
<SchemaSettingsModalItem
title={t('Edit chart')}
schema={
{
type: 'object',
title: t('Edit chart'),
properties: {
title: {
title: t('Chart title'),
type: 'string',
default: fieldSchema.title,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
plot: {
title: t('Chart type'),
type: 'string',
default: fieldSchema?.['x-component-props']?.plot,
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-disabled': !!fieldSchema?.['x-component-props']?.plot,
},
config: {
title: t('Chart config'),
type: 'string',
default: JSON.stringify(fieldSchema?.['x-component-props']?.config, null, 2),
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-component-props': {
autoSize: { minRows: 8, maxRows: 16 },
},
'x-validator': validateJSON,
},
},
} as ISchema
}
// {{ fetchData(api, { url: 'chartData:get' }) }}
onSubmit={async ({ plot, title, config }) => {
field.title = compile(title);
field.componentProps.plot = plot;
const conf = compile(JSON.parse(config));
const fn = conf?.data;
if (typeof fn === 'function') {
const result = fn.bind({ api })();
if (result?.then) {
result
.then((data) => {
if (Array.isArray(data)) {
field.componentProps.config.data = data;
}
})
.catch(console.error);
}
} else {
field.componentProps.config = conf;
}
_.set(fieldSchema, 'title', title);
_.set(fieldSchema, 'x-component-props.plot', plot);
_.set(fieldSchema, 'x-component-props.config', JSON.parse(config));
dn.emit('patch', {
schema: {
title,
'x-uid': fieldSchema['x-uid'],
'x-component-props': fieldSchema['x-component-props'],
},
});
dn.refresh();
}}
/>
<SchemaSettingsDivider />
<SchemaSettingsRemove
removeParentsIfNoChildren
breakRemoveOn={{
'x-component': 'Grid',
}}
/>
</GeneralSchemaDesigner>
);
};

View File

@ -1,15 +0,0 @@
import { render, waitFor } from '@tachybase/test/client';
import React from 'react';
import App1 from '../demos/demo1';
// jsdom does not support canvas, so we need to skip this test
describe.skip('G2Plot', () => {
it('basic', async () => {
render(<App1 />);
await waitFor(() => {
const g2plot = document.querySelector('.g2plot') as HTMLDivElement;
expect(g2plot).toBeInTheDocument();
});
});
});

View File

@ -1,91 +0,0 @@
import {
APIClient,
APIClientProvider,
CardItem,
G2Plot,
SchemaComponent,
SchemaComponentProvider,
} from '@tachybase/client';
import React from 'react';
import { mockAPIClient } from '../../../../testUtils';
const { apiClient, mockRequest } = mockAPIClient();
mockRequest.onGet('/test').reply(200, {
data: [
{
Date: '2010-01',
scales: 1998,
},
{
Date: '2010-02',
scales: 1850,
},
{
Date: '2010-03',
scales: 1720,
},
{
Date: '2010-04',
scales: 1818,
},
{
Date: '2010-05',
scales: 1920,
},
{
Date: '2010-06',
scales: 1802,
},
{
Date: '2010-07',
scales: 1945,
},
{
Date: '2010-08',
scales: 1856,
},
{
Date: '2010-09',
scales: 2107,
},
],
});
const requestChartData = (options) => {
return async function (this: { api: APIClient }) {
const response = await this.api.request(options);
return response?.data?.data;
};
};
const schema = {
type: 'void',
name: 'line',
'x-designer': 'G2Plot.Designer',
'x-decorator': 'CardItem',
'x-component': 'G2Plot',
'x-component-props': {
plot: 'Line',
config: {
data: '{{ requestChartData({ url: "/test" }) }}',
padding: 'auto',
xField: 'Date',
yField: 'scales',
xAxis: {
// type: 'timeCat',
tickCount: 5,
},
},
},
};
export default () => {
return (
<APIClientProvider apiClient={apiClient}>
<SchemaComponentProvider components={{ G2Plot, CardItem }} scope={{ requestChartData }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
</APIClientProvider>
);
};

View File

@ -1,9 +0,0 @@
---
group:
title: Schema Components
order: 3
---
# G2Plot
<code src="./demos/demo1.tsx"></code>

View File

@ -1 +0,0 @@
export * from './G2Plot';

View File

@ -1,12 +0,0 @@
import { APIClient } from '../../../api-client';
export const requestChartData = (options) => {
return async function (this: { api: APIClient }) {
try {
const response = await this.api.request(options);
return response?.data?.data;
} catch (error) {
return [];
}
};
};

View File

@ -23,7 +23,6 @@ export * from './form';
export * from './form-dialog'; export * from './form-dialog';
export * from './form-item'; export * from './form-item';
export * from './form-v2'; export * from './form-v2';
export * from './g2plot';
export * from './grid'; export * from './grid';
export * from './grid-card'; export * from './grid-card';
export * from './icon-picker'; export * from './icon-picker';