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 * as bp from './hooks';
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`
export const BlockSchemaComponentProvider: React.FC = (props) => {
@ -52,6 +53,7 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
useTableBlockProps,
useTableSelectorProps,
useTableBlockDecoratorProps,
useListBlockDecoratorProps,
}}
>
{props.children}
@ -100,6 +102,7 @@ export class BlockSchemaComponentPlugin extends Plugin {
useTableBlockProps,
useTableSelectorProps,
useTableBlockDecoratorProps,
useListBlockDecoratorProps,
});
}
}

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 { createListBlockSchema } from '../../../../schema-initializer/utils';
import { createListBlockSchema } from './createListBlockSchema';
import { DataBlockInitializer } from '../../../../schema-initializer/items/DataBlockInitializer';
import { Collection, CollectionFieldOptions } from '../../../../data-source/collection/Collection';
@ -48,10 +48,9 @@ export const ListBlockInitializer = ({
const collection = getCollection(item.name, item.dataSource);
const schema = createListBlockSchema({
collection: item.name,
collectionName: item.name,
dataSource: item.dataSource,
rowKey: collection.filterTargetKey || 'id',
settings: 'blockSettings:list',
});
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 { useDesignable, useProps } from '../../hooks';
import { useSchemaInitializerRender } from '../../../application';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
interface ActionBarContextForceProps {
layout?: 'one-column' | 'tow-columns';
@ -46,10 +47,13 @@ const Portal: React.FC = (props) => {
);
};
export const ActionBar = observer(
(props: any) => {
export const ActionBar = withDynamicSchemaProps(
observer((props: any) => {
const { forceProps = {} } = useActionBarContext();
// 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { layout = 'tow-columns', style, spaceProps, ...others } = { ...useProps(props), ...forceProps } as any;
const fieldSchema = useFieldSchema();
const { render } = useSchemaInitializerRender(fieldSchema['x-initializer'], fieldSchema['x-initializer-props']);
const { designable } = useDesignable();
@ -128,6 +132,6 @@ export const ActionBar = observer(
{render()}
</div>
);
},
}),
{ displayName: 'ActionBar' },
);

View File

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

View File

@ -7,8 +7,9 @@ import { useDesignable } from '../../hooks';
import { useCollectionParentRecordData } from '../../../data-source/collection-record/CollectionRecordProvider';
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 { designable } = useDesignable();
const parentRecordData = useCollectionParentRecordData();
@ -29,4 +30,4 @@ export const ListItem = (props) => {
</RecordProvider>
</div>
);
};
});

View File

@ -11,6 +11,7 @@ import { ListDesigner } from './List.Designer';
import { ListItem } from './List.Item';
import useStyles from './List.style';
import { useListActionBarProps } from './hooks';
import { withDynamicSchemaProps } from '../../../application/hoc/withDynamicSchemaProps';
const InternalList = (props) => {
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;
Designer: typeof ListDesigner;
Decorator: typeof ListBlockProvider;

View File

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

View File

@ -972,94 +972,6 @@ export const createDetailsBlockSchema = (options) => {
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) => {
const {
formItemInitializers = 'details:configureFields',