refactor(DataBlock): grid card block (#3781)

* refactor: migrate createGridCardBlockSchema to modules

* refactor: add grid-card block hooks and decorator props

* refactor: should not get collection on getting association in UISchema

* refactor: use x-use-component-props instead of useProps

* chore: fix build
This commit is contained in:
Zeke Zhang 2024-03-27 20:56:30 +08:00 committed by GitHub
parent d6f38495a7
commit d932546194
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 317 additions and 119 deletions

View File

@ -26,6 +26,7 @@ import { useTableSelectorDecoratorProps } from '../modules/blocks/data-blocks/ta
import { useCollapseBlockDecoratorProps } from '../modules/blocks/filter-blocks/collapse/hooks/useCollapseBlockDecoratorProps';
import { useFilterFormBlockProps } from '../modules/blocks/filter-blocks/form/hooks/useFilterFormBlockProps';
import { useFilterFormBlockDecoratorProps } from '../modules/blocks/filter-blocks/form/hooks/useFilterFormBlockDecoratorProps';
import { useGridCardBlockDecoratorProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps';
// TODO: delete this, replaced by `BlockSchemaComponentPlugin`
export const BlockSchemaComponentProvider: React.FC = (props) => {
@ -62,6 +63,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
useCollapseBlockDecoratorProps,
useFilterFormBlockProps,
useFilterFormBlockDecoratorProps,
useGridCardBlockDecoratorProps,
}}
>
{props.children}
@ -115,6 +117,7 @@ export class BlockSchemaComponentPlugin extends Plugin {
useCollapseBlockDecoratorProps,
useFilterFormBlockProps,
useFilterFormBlockDecoratorProps,
useGridCardBlockDecoratorProps,
});
}
}

View File

@ -2,7 +2,7 @@ import { OrderedListOutlined } from '@ant-design/icons';
import React from 'react';
import { useSchemaInitializer, useSchemaInitializerItem } from '../../../../application';
import { useCollectionManager_deprecated } from '../../../../collection-manager';
import { createGridCardBlockSchema } from '../../../../schema-initializer/utils';
import { createGridCardBlockSchema } from './createGridCardBlockSchema';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
@ -48,10 +48,9 @@ export const GridCardBlockInitializer = ({
const collection = getCollection(item.name, item.dataSource);
const schema = createGridCardBlockSchema({
collection: item.name,
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
settings: 'blockSettings:gridCard',
});
insert(schema);
}}

View File

@ -0,0 +1,158 @@
import { createGridCardBlockSchema } from '../createGridCardBlockSchema';
describe('createGridCardBlockSchema', () => {
test('should return the correct schema', () => {
const options = {
collectionName: 'testCollection',
dataSource: 'testDataSource',
association: 'testAssociation',
templateSchema: { type: 'string' },
rowKey: 'testRowKey',
};
const schema = createGridCardBlockSchema(options);
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"actionBar": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)",
},
},
"x-initializer": "gridCard:configureActions",
},
"list": {
"properties": {
"item": {
"properties": {
"actionBar": {
"type": "void",
"x-align": "left",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
},
"x-initializer": "gridCard:configureItemActions",
"x-use-component-props": "useGridCardActionBarProps",
},
"grid": {
"type": "string",
},
},
"type": "object",
"x-component": "GridCard.Item",
"x-read-pretty": true,
"x-use-component-props": "useGridCardItemProps",
},
},
"type": "array",
"x-component": "GridCard",
"x-use-component-props": "useGridCardBlockProps",
},
},
"type": "void",
"x-acl-action": "testAssociation:view",
"x-component": "BlockItem",
"x-decorator": "GridCard.Decorator",
"x-decorator-props": {
"action": "list",
"association": "testAssociation",
"collection": "testCollection",
"dataSource": "testDataSource",
"params": {
"pageSize": 12,
},
"readPretty": true,
"rowKey": "testRowKey",
"runWhenParamsChanged": true,
},
"x-settings": "blockSettings:gridCard",
"x-toolbar": "BlockSchemaToolbar",
"x-use-component-props": "useGridCardBlockItemProps",
"x-use-decorator-props": "useGridCardBlockDecoratorProps",
}
`);
});
test('should return the correct schema when templateSchema is empty', () => {
const options = {
collectionName: 'testCollection',
dataSource: 'testDataSource',
association: 'testAssociation',
rowKey: 'testRowKey',
};
const schema = createGridCardBlockSchema(options);
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"actionBar": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)",
},
},
"x-initializer": "gridCard:configureActions",
},
"list": {
"properties": {
"item": {
"properties": {
"actionBar": {
"type": "void",
"x-align": "left",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
},
"x-initializer": "gridCard:configureItemActions",
"x-use-component-props": "useGridCardActionBarProps",
},
"grid": {
"type": "void",
"x-component": "Grid",
"x-initializer": "details:configureFields",
},
},
"type": "object",
"x-component": "GridCard.Item",
"x-read-pretty": true,
"x-use-component-props": "useGridCardItemProps",
},
},
"type": "array",
"x-component": "GridCard",
"x-use-component-props": "useGridCardBlockProps",
},
},
"type": "void",
"x-acl-action": "testAssociation:view",
"x-component": "BlockItem",
"x-decorator": "GridCard.Decorator",
"x-decorator-props": {
"action": "list",
"association": "testAssociation",
"collection": "testCollection",
"dataSource": "testDataSource",
"params": {
"pageSize": 12,
},
"readPretty": true,
"rowKey": "testRowKey",
"runWhenParamsChanged": true,
},
"x-settings": "blockSettings:gridCard",
"x-toolbar": "BlockSchemaToolbar",
"x-use-component-props": "useGridCardBlockItemProps",
"x-use-decorator-props": "useGridCardBlockDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,81 @@
import { ISchema } from '@formily/react';
export const createGridCardBlockSchema = (options: {
dataSource: string;
collectionName?: string;
association?: string;
templateSchema?: ISchema;
rowKey?: string;
}): ISchema => {
const { collectionName, association, templateSchema, dataSource, rowKey } = options;
const resourceName = association || collectionName;
if (!dataSource) {
throw new Error('dataSource are required');
}
return {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'GridCard.Decorator',
'x-use-decorator-props': 'useGridCardBlockDecoratorProps',
'x-decorator-props': {
collection: collectionName,
association,
dataSource,
readPretty: true,
action: 'list',
params: {
pageSize: 12,
},
runWhenParamsChanged: true,
rowKey,
},
'x-component': 'BlockItem',
'x-use-component-props': 'useGridCardBlockItemProps',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:gridCard',
properties: {
actionBar: {
type: 'void',
'x-initializer': 'gridCard:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
},
list: {
type: 'array',
'x-component': 'GridCard',
'x-use-component-props': 'useGridCardBlockProps',
properties: {
item: {
type: 'object',
'x-component': 'GridCard.Item',
'x-read-pretty': true,
'x-use-component-props': 'useGridCardItemProps',
properties: {
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'details:configureFields',
},
actionBar: {
type: 'void',
'x-align': 'left',
'x-initializer': 'gridCard:configureItemActions',
'x-component': 'ActionBar',
'x-use-component-props': 'useGridCardActionBarProps',
'x-component-props': {
layout: 'one-column',
},
},
},
},
},
},
},
};
};

View File

@ -0,0 +1,18 @@
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
import { useGridCardBlockParams } from './useGridCardBlockParams';
export function useGridCardBlockDecoratorProps(props) {
const params = useGridCardBlockParams(props);
let sourceId;
// 因为 association 是固定的,所以可以在条件中使用 hooks
if (props.association) {
// eslint-disable-next-line react-hooks/rules-of-hooks
sourceId = useDataBlockSourceId({ association: props.association });
}
return {
params,
sourceId,
};
}

View File

@ -0,0 +1,17 @@
import { useParsedFilter } from '../../../../../block-provider/hooks/useParsedFilter';
import { useMemo } from 'react';
export function useGridCardBlockParams(props) {
const { params } = props;
const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter,
});
const paramsWithFilter = useMemo(() => {
return {
...params,
filter: parsedFilter,
};
}, [parsedFilter, params]);
return paramsWithFilter;
}

View File

@ -5,9 +5,12 @@ import { SortableItem } from '../../common';
import { useDesigner, useProps } from '../../hooks';
import { useGetAriaLabelOfBlockItem } from './hooks/useGetAriaLabelOfBlockItem';
import { useFieldSchema } from '@formily/react';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const BlockItem: React.FC<any> = (props) => {
export const BlockItem: React.FC<any> = withDynamicSchemaProps((props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { className, children } = useProps(props);
const Designer = useDesigner();
const fieldSchema = useFieldSchema();
const { getAriaLabel } = useGetAriaLabelOfBlockItem(props.name);
@ -69,4 +72,4 @@ export const BlockItem: React.FC<any> = (props) => {
{children}
</SortableItem>
);
};
});

View File

@ -1,9 +1,12 @@
import { FormLayout } from '@formily/antd-v5';
import { createForm } from '@formily/core';
import { FormContext, useField } from '@formily/react';
import { FormContext, useField, useFieldSchema } from '@formily/react';
import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
import useStyles from './GridCard.Decorator.style';
import { useGridCardBlockParams } from '../../../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockParams';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const GridCardBlockContext = createContext<any>({});
GridCardBlockContext.displayName = 'GridCardBlockContext';
@ -41,24 +44,31 @@ const InternalGridCardBlockProvider = (props) => {
);
};
export const GridCardBlockProvider = (props) => {
const { params } = props;
const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter,
});
const paramsWithFilter = useMemo(() => {
return {
...params,
filter: parsedFilter,
};
}, [parsedFilter, params]);
/**
* UISchema x-use-decorator-props
* @param props
*/
const useCompatGridCardBlockParams = (props) => {
const schema = useFieldSchema();
// 因为 x-use-decorator-props 的值是固定的,所以可以在条件中使用 hooks
if (schema['x-use-decorator-props']) {
return props.params;
} else {
// eslint-disable-next-line react-hooks/rules-of-hooks
return useGridCardBlockParams(props);
}
};
export const GridCardBlockProvider = withDynamicSchemaProps((props) => {
const params = useCompatGridCardBlockParams(props);
return (
<BlockProvider name="grid-card" {...props} params={paramsWithFilter}>
<BlockProvider name="grid-card" {...props} params={params}>
<InternalGridCardBlockProvider {...props} />
</BlockProvider>
);
};
});
export const useGridCardBlockContext = () => {
return useContext(GridCardBlockContext);

View File

@ -5,6 +5,7 @@ import { Card } from 'antd';
import React from 'react';
import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider';
import { RecordProvider } from '../../../record-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const itemCss = css`
display: flex;
@ -15,7 +16,7 @@ const itemCss = css`
gap: 8px;
`;
export const GridCardItem = (props) => {
export const GridCardItem = withDynamicSchemaProps((props) => {
const field = useField<ObjectField>();
const parentRecordData = useCollectionParentRecordData();
return (
@ -40,4 +41,4 @@ export const GridCardItem = (props) => {
</div>
</Card>
);
};
});

View File

@ -11,6 +11,7 @@ import { GridCardDesigner } from './GridCard.Designer';
import { GridCardItem } from './GridCard.Item';
import { useGridCardActionBarProps } from './hooks';
import { defaultColumnCount, pageSizeOptions } from './options';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const rowGutter = {
md: 12,
@ -56,7 +57,9 @@ const designerCss = css`
`;
const InternalGridCard = (props) => {
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { columnCount: columnCountProp, pagination } = useProps(props);
const { service, columnCount: _columnCount = defaultColumnCount } = useGridCardBlockContext();
const columnCount = columnCountProp || _columnCount;
const { run, params } = service;
@ -145,7 +148,7 @@ const InternalGridCard = (props) => {
);
};
export const GridCard = InternalGridCard as typeof InternalGridCard & {
export const GridCard = withDynamicSchemaProps(InternalGridCard) as typeof InternalGridCard & {
Item: typeof GridCardItem;
Designer: typeof GridCardDesigner;
Decorator: typeof GridCardBlockProvider;

View File

@ -4,7 +4,8 @@ import React, { useCallback } from 'react';
import { useCollectionManager_deprecated } from '../../collection-manager';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useSchemaTemplateManager } from '../../schema-templates';
import { createGridCardBlockSchema, useRecordCollectionDataSourceItems } from '../utils';
import { useRecordCollectionDataSourceItems } from '../utils';
import { createGridCardBlockSchema } from '../../modules/blocks/data-blocks/grid-card/createGridCardBlockSchema';
/**
* @deprecated
@ -31,11 +32,8 @@ export const RecordAssociationGridCardBlockInitializer = () => {
insert(
createGridCardBlockSchema({
rowKey: collection.filterTargetKey,
collection: field.target,
resource,
dataSource: collection.dataSource,
association: resource,
settings: 'blockSettings:gridCard',
}),
);
}
@ -57,10 +55,8 @@ export function useCreateAssociationGridCardBlock() {
insert(
createGridCardBlockSchema({
rowKey: collection.filterTargetKey,
collection: field.target,
dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`,
settings: 'blockSettings:gridCard',
}),
);
},

View File

@ -972,97 +972,6 @@ export const createDetailsBlockSchema = (options) => {
return schema;
};
export const createGridCardBlockSchema = (options) => {
const {
formItemInitializers = 'details:configureFields',
actionInitializers = 'gridCard:configureActions',
itemActionInitializers = 'gridCard:configureItemActions',
collection,
association,
template,
dataSource,
settings,
...others
} = options;
const resourceName = association || collection;
const schema: ISchema = {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'GridCard.Decorator',
'x-decorator-props': {
collection,
association,
dataSource,
readPretty: true,
action: 'list',
params: {
pageSize: 12,
},
runWhenParamsChanged: true,
...others,
},
'x-component': 'BlockItem',
'x-component-props': {
useProps: '{{ useGridCardBlockItemProps }}',
},
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': settings,
properties: {
actionBar: {
type: 'void',
'x-initializer': actionInitializers,
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
properties: {},
},
list: {
type: 'array',
'x-component': 'GridCard',
'x-component-props': {
useProps: '{{ useGridCardBlockProps }}',
},
properties: {
item: {
type: 'object',
'x-component': 'GridCard.Item',
'x-read-pretty': true,
'x-component-props': {
useProps: '{{ useGridCardItemProps }}',
},
properties: {
grid: template || {
type: 'void',
'x-component': 'Grid',
'x-initializer': formItemInitializers,
'x-initializer-props': {
useProps: '{{ useGridCardItemInitializerProps }}',
},
properties: {},
},
actionBar: {
type: 'void',
'x-align': 'left',
'x-initializer': itemActionInitializers,
'x-component': 'ActionBar',
'x-component-props': {
useProps: '{{ useGridCardActionBarProps }}',
layout: 'one-column',
},
properties: {},
},
},
},
},
},
},
};
return schema;
};
/**
* @deprecated
* 使 createCreateFormBlockUISchema createEditFormBlockUISchema