refactor(DataBlock): list block (#3779)

* refactor: refactor list block initializer and record association list block initializer

* refactor: add useListBlockDecoratorProps

* chore: fix import path to avoid build error

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

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

* chore: fix unit test
This commit is contained in:
Zeke Zhang 2024-03-27 17:41:56 +08:00 committed by GitHub
parent c6922b071d
commit 76cd3474c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 210 additions and 105 deletions

View File

@ -21,6 +21,7 @@ import { TableFieldProvider, useTableFieldProps } from './TableFieldProvider';
import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider'; import { TableSelectorProvider, useTableSelectorProps } from './TableSelectorProvider';
import * as bp from './hooks'; import * as bp from './hooks';
import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps'; import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
import { useListBlockDecoratorProps } from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps';
// TODO: delete this, replaced by `BlockSchemaComponentPlugin` // TODO: delete this, replaced by `BlockSchemaComponentPlugin`
export const BlockSchemaComponentProvider: React.FC = (props) => { export const BlockSchemaComponentProvider: React.FC = (props) => {
@ -52,6 +53,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
useTableBlockProps, useTableBlockProps,
useTableSelectorProps, useTableSelectorProps,
useTableBlockDecoratorProps, useTableBlockDecoratorProps,
useListBlockDecoratorProps,
}} }}
> >
{props.children} {props.children}
@ -100,6 +102,7 @@ export class BlockSchemaComponentPlugin extends Plugin {
useTableBlockProps, useTableBlockProps,
useTableSelectorProps, useTableSelectorProps,
useTableBlockDecoratorProps, useTableBlockDecoratorProps,
useListBlockDecoratorProps,
}); });
} }
} }

View File

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

View File

@ -0,0 +1,97 @@
import { createListBlockSchema } from '../createListBlockSchema';
describe('createListBlockSchema', () => {
test('should return the correct schema', () => {
const options = {
collectionName: 'users',
dataSource: 'users',
association: 'user',
templateSchema: {
type: 'object',
properties: {
name: {
type: 'string',
},
age: {
type: 'number',
},
},
},
rowKey: 'id',
};
const schema = createListBlockSchema(options);
expect(schema).toMatchInlineSnapshot(`
{
"properties": {
"actionBar": {
"type": "void",
"x-component": "ActionBar",
"x-component-props": {
"style": {
"marginBottom": "var(--nb-spacing)",
},
},
"x-initializer": "list:configureActions",
},
"list": {
"properties": {
"item": {
"properties": {
"actionBar": {
"type": "void",
"x-align": "left",
"x-component": "ActionBar",
"x-component-props": {
"layout": "one-column",
},
"x-initializer": "list:configureItemActions",
"x-use-component-props": "useListActionBarProps",
},
"grid": {
"properties": {
"age": {
"type": "number",
},
"name": {
"type": "string",
},
},
"type": "object",
},
},
"type": "object",
"x-component": "List.Item",
"x-read-pretty": true,
"x-use-component-props": "useListItemProps",
},
},
"type": "array",
"x-component": "List",
"x-use-component-props": "useListBlockProps",
},
},
"type": "void",
"x-acl-action": "user:view",
"x-component": "CardItem",
"x-decorator": "List.Decorator",
"x-decorator-props": {
"action": "list",
"association": "user",
"collection": "users",
"dataSource": "users",
"params": {
"pageSize": 10,
},
"readPretty": true,
"rowKey": "id",
"runWhenParamsChanged": true,
},
"x-settings": "blockSettings:list",
"x-toolbar": "BlockSchemaToolbar",
"x-use-decorator-props": "useListBlockDecoratorProps",
}
`);
});
});

View File

@ -0,0 +1,76 @@
import { ISchema } from '@formily/react';
export const createListBlockSchema = (options: {
dataSource: string;
collectionName?: string;
association?: string;
templateSchema?: ISchema;
rowKey?: string;
}): ISchema => {
const { collectionName, dataSource, association, templateSchema, rowKey } = options;
const resourceName = association || collectionName;
return {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'List.Decorator',
'x-use-decorator-props': 'useListBlockDecoratorProps',
'x-decorator-props': {
collection: collectionName,
dataSource,
association,
readPretty: true,
action: 'list',
params: {
pageSize: 10,
},
runWhenParamsChanged: true,
rowKey,
},
'x-component': 'CardItem',
'x-toolbar': 'BlockSchemaToolbar',
'x-settings': 'blockSettings:list',
properties: {
actionBar: {
type: 'void',
'x-initializer': 'list:configureActions',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
},
list: {
type: 'array',
'x-component': 'List',
'x-use-component-props': 'useListBlockProps',
properties: {
item: {
type: 'object',
'x-component': 'List.Item',
'x-read-pretty': true,
'x-use-component-props': 'useListItemProps',
properties: {
grid: templateSchema || {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'details:configureFields',
},
actionBar: {
type: 'void',
'x-align': 'left',
'x-initializer': 'list:configureItemActions',
'x-component': 'ActionBar',
'x-use-component-props': 'useListActionBarProps',
'x-component-props': {
layout: 'one-column',
},
},
},
},
},
},
},
};
};

View File

@ -0,0 +1,15 @@
import { useDataBlockSourceId } from '../../../../../block-provider/hooks/useDataBlockSourceId';
export function useListBlockDecoratorProps(props) {
let sourceId;
// 因为 association 的值是固定的,所以这里可以使用 hooks
if (props.association) {
// eslint-disable-next-line react-hooks/rules-of-hooks
sourceId = useDataBlockSourceId({ association: props.association });
}
return {
sourceId,
};
}

View File

@ -6,6 +6,7 @@ import { createPortal } from 'react-dom';
import { DndContext } from '../../common'; import { DndContext } from '../../common';
import { useDesignable, useProps } from '../../hooks'; import { useDesignable, useProps } from '../../hooks';
import { useSchemaInitializerRender } from '../../../application'; import { useSchemaInitializerRender } from '../../../application';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
interface ActionBarContextForceProps { interface ActionBarContextForceProps {
layout?: 'one-column' | 'tow-columns'; layout?: 'one-column' | 'tow-columns';
@ -46,10 +47,13 @@ const Portal: React.FC = (props) => {
); );
}; };
export const ActionBar = observer( export const ActionBar = withDynamicSchemaProps(
(props: any) => { observer((props: any) => {
const { forceProps = {} } = useActionBarContext(); const { forceProps = {} } = useActionBarContext();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { layout = 'tow-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any; const { layout = 'tow-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']); const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
const { designable } = useDesignable(); const { designable } = useDesignable();
@ -128,6 +132,6 @@ export const ActionBar = observer(
{render()} {render()}
</div> </div>
); );
}, }),
{ displayName: 'ActionBar' }, { displayName: 'ActionBar' },
); );

View File

@ -5,6 +5,7 @@ import { FormContext, useField } from '@formily/react';
import _ from 'lodash'; import _ from 'lodash';
import React, { createContext, useContext, useEffect, useMemo } from 'react'; import React, { createContext, useContext, useEffect, useMemo } from 'react';
import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider'; import { BlockProvider, useBlockRequestContext, useParsedFilter } from '../../../block-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const ListBlockContext = createContext<any>({}); export const ListBlockContext = createContext<any>({});
ListBlockContext.displayName = 'ListBlockContext'; ListBlockContext.displayName = 'ListBlockContext';
@ -52,7 +53,7 @@ const InternalListBlockProvider = (props) => {
); );
}; };
export const ListBlockProvider = (props) => { export const ListBlockProvider = withDynamicSchemaProps((props) => {
const { params } = props; const { params } = props;
const { filter: parsedFilter } = useParsedFilter({ const { filter: parsedFilter } = useParsedFilter({
filterOption: params?.filter, filterOption: params?.filter,
@ -75,7 +76,7 @@ export const ListBlockProvider = (props) => {
<InternalListBlockProvider {...props} /> <InternalListBlockProvider {...props} />
</BlockProvider> </BlockProvider>
); );
}; });
export const useListBlockContext = () => { export const useListBlockContext = () => {
return useContext(ListBlockContext); return useContext(ListBlockContext);

View File

@ -7,8 +7,9 @@ import { useDesignable } from '../../hooks';
import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider'; import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider';
import { RecordProvider } from '../../../record-provider'; import { RecordProvider } from '../../../record-provider';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
export const ListItem = (props) => { export const ListItem = withDynamicSchemaProps((props) => {
const field = useField<ObjectField>(); const field = useField<ObjectField>();
const { designable } = useDesignable(); const { designable } = useDesignable();
const parentRecordData = useCollectionParentRecordData(); const parentRecordData = useCollectionParentRecordData();
@ -29,4 +30,4 @@ export const ListItem = (props) => {
</RecordProvider> </RecordProvider>
</div> </div>
); );
}; });

View File

@ -11,6 +11,7 @@ import { ListDesigner } from './List.Designer';
import { ListItem } from './List.Item'; import { ListItem } from './List.Item';
import useStyles from './List.style'; import useStyles from './List.style';
import { useListActionBarProps } from './hooks'; import { useListActionBarProps } from './hooks';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const InternalList = (props) => { const InternalList = (props) => {
const { service } = useListBlockContext(); const { service } = useListBlockContext();
@ -93,7 +94,7 @@ const InternalList = (props) => {
); );
}; };
export const List = InternalList as typeof InternalList & { export const List = withDynamicSchemaProps(InternalList) as typeof InternalList & {
Item: typeof ListItem; Item: typeof ListItem;
Designer: typeof ListDesigner; Designer: typeof ListDesigner;
Decorator: typeof ListBlockProvider; Decorator: typeof ListBlockProvider;

View File

@ -4,7 +4,8 @@ import React, { useCallback } from 'react';
import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application'; import { SchemaInitializerItem, useSchemaInitializer, useSchemaInitializerItem } from '../../application';
import { useCollectionManager_deprecated } from '../../collection-manager'; import { useCollectionManager_deprecated } from '../../collection-manager';
import { useSchemaTemplateManager } from '../../schema-templates'; import { useSchemaTemplateManager } from '../../schema-templates';
import { createListBlockSchema, useRecordCollectionDataSourceItems } from '../utils'; import { useRecordCollectionDataSourceItems } from '../utils';
import { createListBlockSchema } from '../../modules/blocks/data-blocks/list/createListBlockSchema';
export const RecordAssociationListBlockInitializer = () => { export const RecordAssociationListBlockInitializer = () => {
const itemConfig = useSchemaInitializerItem(); const itemConfig = useSchemaInitializerItem();
@ -28,11 +29,8 @@ export const RecordAssociationListBlockInitializer = () => {
insert( insert(
createListBlockSchema({ createListBlockSchema({
rowKey: collection.filterTargetKey, rowKey: collection.filterTargetKey,
collection: field.target,
resource,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: resource, association: resource,
settings: 'blockSettings:list',
}), }),
); );
} }
@ -54,10 +52,8 @@ export function useCreateAssociationListBlock() {
insert( insert(
createListBlockSchema({ createListBlockSchema({
rowKey: collection.filterTargetKey, rowKey: collection.filterTargetKey,
collection: field.target,
dataSource: collection.dataSource, dataSource: collection.dataSource,
association: `${field.collectionName}.${field.name}`, association: `${field.collectionName}.${field.name}`,
settings: 'blockSettings:list',
}), }),
); );
}, },

View File

@ -972,94 +972,6 @@ export const createDetailsBlockSchema = (options) => {
return schema; return schema;
}; };
export const createListBlockSchema = (options) => {
const {
formItemInitializers = 'details:configureFields',
actionInitializers = 'list:configureActions',
itemActionInitializers = 'list:configureItemActions',
collection,
dataSource,
association,
template,
settings,
...others
} = options;
const resourceName = association || collection;
const schema: ISchema = {
type: 'void',
'x-acl-action': `${resourceName}:view`,
'x-decorator': 'List.Decorator',
'x-decorator-props': {
collection,
dataSource,
association,
readPretty: true,
action: 'list',
params: {
pageSize: 10,
},
runWhenParamsChanged: true,
...others,
},
'x-component': 'CardItem',
'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': 'List',
'x-component-props': {
props: '{{ useListBlockProps }}',
},
properties: {
item: {
type: 'object',
'x-component': 'List.Item',
'x-read-pretty': true,
'x-component-props': {
useProps: '{{ useListItemProps }}',
},
properties: {
grid: template || {
type: 'void',
'x-component': 'Grid',
'x-initializer': formItemInitializers,
'x-initializer-props': {
useProps: '{{ useListItemInitializerProps }}',
},
properties: {},
},
actionBar: {
type: 'void',
'x-align': 'left',
'x-initializer': itemActionInitializers,
'x-component': 'ActionBar',
'x-component-props': {
useProps: '{{ useListActionBarProps }}',
layout: 'one-column',
},
properties: {},
},
},
},
},
},
},
};
return schema;
};
export const createGridCardBlockSchema = (options) => { export const createGridCardBlockSchema = (options) => {
const { const {
formItemInitializers = 'details:configureFields', formItemInitializers = 'details:configureFields',