feat(client): improve schema Initializer

This commit is contained in:
chenos 2022-02-07 21:52:51 +08:00
parent 3b5f43ea09
commit ce52361ac4
12 changed files with 788 additions and 17 deletions

View File

@ -15,6 +15,7 @@ export * from './plugin-manager';
export * from './record-provider';
export * from './route-switch';
export * from './schema-component';
export * from './schema-initializer';
export * from './system-settings';
export * from './user';

View File

@ -2,6 +2,7 @@ import { ArrayField } from '@formily/core';
import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react';
import { Table, TableColumnProps } from 'antd';
import React from 'react';
import { TableColumnInitializeButton } from './Initializer';
const isColumnComponent = (schema: Schema) => {
return schema['x-component']?.endsWith('.Column') > -1;
@ -27,7 +28,14 @@ const useTableColumns = () => {
} as TableColumnProps<any>;
});
console.log(columns);
if (!schema['x-column-initializer']) {
return columns;
}
return columns.concat({
title: <TableColumnInitializeButton />,
dataIndex: 'TableColumnInitializeButton',
key: 'TableColumnInitializeButton',
});
};
type ArrayTableType = React.FC<any> & {
@ -41,7 +49,7 @@ export const ArrayTable: ArrayTableType = observer((props) => {
const { onChange, ...others } = props;
return (
<div>
<Table {...others} columns={columns} dataSource={field.value?.slice()}/>
<Table {...others} columns={columns} dataSource={field.value?.slice()} />
</div>
);
});

View File

@ -0,0 +1,61 @@
import { observer, useFieldSchema } from '@formily/react';
import React from 'react';
import { SchemaInitializer } from '../../../schema-initializer';
const useTableColumnInitializerFields = () => {
const fieldSchema = useFieldSchema();
const fields = [
{
key: 'field1',
title: 'Field1',
component: fieldSchema['x-column-initializer'],
},
{
key: 'field2',
title: 'Field2',
component: fieldSchema['x-column-initializer'],
},
].filter((field) => field.component);
return fields;
};
export const TableColumnInitializeButton = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={(schema) => schema}
insertPosition={'beforeEnd'}
items={[
{
title: 'Display fields',
children: useTableColumnInitializerFields(),
},
]}
>
Configure columns
</SchemaInitializer.Button>
);
});
export const ArrayTableColumnInitializer = (props) => {
const { title, insert } = props;
return (
<SchemaInitializer.Item
onClick={(info) => {
insert({
type: 'void',
title: 'Name',
'x-component': 'ArrayTable.Column',
properties: {
name: {
type: 'string',
'x-component': 'Input',
'x-read-pretty': true,
},
},
});
}}
>
{title}
</SchemaInitializer.Item>
);
};

View File

@ -3,7 +3,7 @@
*/
import { FormItem } from '@formily/antd';
import { ISchema } from '@formily/react';
import { ArrayTable, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { ArrayTable, ArrayTableColumnInitializer, Input, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import React from 'react';
const schema: ISchema = {
@ -19,6 +19,7 @@ const schema: ISchema = {
],
'x-decorator': 'FormItem',
'x-component': 'ArrayTable',
'x-column-initializer': 'ArrayTableColumnInitializer',
'x-component-props': {
rowKey: 'id',
},
@ -60,7 +61,7 @@ const schema: ISchema = {
export default () => {
return (
<SchemaComponentProvider components={{ Input, ArrayTable, FormItem }}>
<SchemaComponentProvider components={{ Input, ArrayTable, FormItem, ArrayTableColumnInitializer }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
);

View File

@ -1 +1,3 @@
export * from './ArrayTable';
export * from './Initializer';

View File

@ -0,0 +1,146 @@
import { observer, Schema, SchemaOptionsContext } from '@formily/react';
import { useDesignable } from '@nocobase/client';
import { Button, Dropdown, Menu } from 'antd';
import { get } from 'lodash';
import React, { createContext, useContext, useState } from 'react';
const defaultWrap = (s: Schema) => s;
export const SchemaInitializerItemContext = createContext(null);
export const SchemaInitializer = () => null;
SchemaInitializer.Button = observer((props: any) => {
const { wrap = defaultWrap, items = [], insertPosition, dropdown, ...others } = props;
const { insertAdjacent } = useDesignable();
const { components } = useContext(SchemaOptionsContext);
const menu = (
<Menu>
{items?.map((item, indexA) => {
if (item.type === 'divider') {
return <Menu.Divider key={`item-${indexA}`} />;
}
if (item.component) {
const Component = typeof item.component === 'object' ? item.component : get(components, item.component);
return (
Component && (
<SchemaInitializerItemContext.Provider
key={`item-${indexA}`}
value={{
index: indexA,
item,
info: item,
insert: (schema) => {
insertAdjacent(insertPosition, wrap(schema));
},
}}
>
<Component
{...item}
insert={(schema) => {
insertAdjacent(insertPosition, wrap(schema));
}}
/>
</SchemaInitializerItemContext.Provider>
)
);
}
return (
item?.children?.length > 0 && (
<Menu.ItemGroup key={`item-group-${indexA}`} title={item.title}>
{item?.children?.map((child, indexB) => {
if (!child.component) {
return null;
}
const Component =
typeof child.component === 'object' ? child.component : get(components, child.component);
return (
Component && (
<SchemaInitializerItemContext.Provider
key={`item-${indexB}`}
value={{
index: indexB,
item: child,
info: child,
insert: (schema) => {
insertAdjacent(insertPosition, wrap(schema));
},
}}
>
<Component
{...child}
insert={(schema) => {
insertAdjacent(insertPosition, wrap(schema));
}}
/>
</SchemaInitializerItemContext.Provider>
)
);
})}
</Menu.ItemGroup>
)
);
})}
</Menu>
);
const [visible, setVisible] = useState(false);
return (
<Dropdown
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
{...dropdown}
overlay={menu}
>
<Button {...others} />
</Dropdown>
);
});
SchemaInitializer.Item = (props) => {
const { items = [], children, icon, onClick, ...others } = props;
const { index, info } = useContext(SchemaInitializerItemContext);
if (items?.length > 0) {
const renderMenuItem = (items) => {
if (!items?.length) {
return null;
}
return items.map((item, indexA) => {
if (item.type === 'itemGroup') {
return (
<Menu.ItemGroup eventKey={indexA} key={indexA} title={item.title}>
{renderMenuItem(item.children)}
</Menu.ItemGroup>
);
}
if (item.type === 'subMenu') {
return (
// @ts-ignore
<Menu.SubMenu eventKey={indexA} key={indexA} title={item.title}>
{renderMenuItem(item.children)}
</Menu.SubMenu>
);
}
return (
<Menu.Item eventKey={item.key} key={item.key} onClick={onClick}>
{item.title}
</Menu.Item>
);
});
};
return (
// @ts-ignore
<Menu.SubMenu eventKey={index} key={index} title={children} icon={icon}>
{renderMenuItem(items)}
</Menu.SubMenu>
);
}
return (
<Menu.Item eventKey={info.key || index} icon={icon} onClick={onClick} {...others}>
{children}
</Menu.Item>
);
};

View File

@ -0,0 +1,123 @@
import { FormOutlined, TableOutlined } from '@ant-design/icons';
import { Field } from '@formily/core';
import { observer, useField } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider, SchemaInitializer } from '@nocobase/client';
import React from 'react';
const Hello = observer((props) => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
const InitializerButton = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={(schema) => schema}
insertPosition={'beforeBegin'}
items={[
{
title: 'Data blocks',
children: [
{
title: 'Table',
component: 'TableBlockInitializer',
},
{
title: 'Form',
component: 'FormBlockInitializer',
},
],
},
]}
>
Create block
</SchemaInitializer.Button>
);
});
const TableBlockInitializer = (props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<TableOutlined />}
onClick={(info) => {
console.log({ info });
insert({
type: 'void',
title: info.key,
'x-component': 'Hello',
});
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: [
{
key: 'users',
title: 'Users',
},
{
key: 'posts',
title: 'Posts',
},
],
},
]}
>
Table
</SchemaInitializer.Item>
);
};
const FormBlockInitializer = (props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<FormOutlined />}
onClick={(info) => {
insert({
type: 'void',
title: 'form',
'x-component': 'Hello',
});
}}
>
Form
</SchemaInitializer.Item>
);
};
export default function App() {
return (
<SchemaComponentProvider components={{ Hello, InitializerButton, TableBlockInitializer, FormBlockInitializer }}>
<SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'div',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
initializer: {
type: 'void',
'x-component': 'InitializerButton',
},
},
}}
/>
</SchemaComponentProvider>
);
}

View File

@ -0,0 +1,123 @@
import { FormOutlined, TableOutlined } from '@ant-design/icons';
import { Field } from '@formily/core';
import { observer, useField } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider, SchemaInitializer } from '@nocobase/client';
import React from 'react';
const Hello = observer((props) => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
const InitializerDemo = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={(schema) => schema}
insertPosition={'beforeBegin'}
items={[
{
title: 'Data blocks',
children: [
{
title: 'Table',
component: 'TableBlockInitializer',
},
{
title: 'Form',
component: 'FormBlockInitializer',
},
],
},
]}
>
Configure actions
</SchemaInitializer.Button>
);
});
const TableBlockInitializer = (props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<TableOutlined />}
onClick={(info) => {
console.log({ info });
insert({
type: 'void',
title: info.key,
'x-component': 'Hello',
});
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: [
{
key: 'users',
title: 'Users',
},
{
key: 'posts',
title: 'Posts',
},
],
},
]}
>
Table
</SchemaInitializer.Item>
);
};
const FormBlockInitializer = (props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<FormOutlined />}
onClick={(info) => {
insert({
type: 'void',
title: 'form',
'x-component': 'Hello',
});
}}
>
Form
</SchemaInitializer.Item>
);
};
export default function App() {
return (
<SchemaComponentProvider components={{ Hello, InitializerDemo, TableBlockInitializer, FormBlockInitializer }}>
<SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'div',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
initializer: {
type: 'void',
'x-component': 'InitializerDemo',
},
},
}}
/>
</SchemaComponentProvider>
);
}

View File

@ -0,0 +1,123 @@
import { Field } from '@formily/core';
import { observer, useField } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider, SchemaInitializer } from '@nocobase/client';
import React from 'react';
const Hello = observer((props) => {
const field = useField<Field>();
return (
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
{field.title}
</div>
);
});
const useFormItemInitializerFields = () => {
return [
{
key: 'field1',
title: 'Field1',
component: 'FormItemInitializer',
},
{
key: 'field2',
title: 'Field2',
component: 'FormItemInitializer',
},
];
};
const InitializerButton = observer((props: any) => {
return (
<SchemaInitializer.Button
wrap={(schema) => schema}
insertPosition={'beforeEnd'}
items={[
{
title: 'Display fields',
children: useFormItemInitializerFields(),
},
{
type: 'divider',
},
{
title: 'Add text',
component: 'AddTextFormItemInitializer',
},
]}
>
Configure fields
</SchemaInitializer.Button>
);
});
const FormItemInitializer = (props) => {
const { title, insert } = props;
return (
<SchemaInitializer.Item
onClick={(info) => {
insert({
type: 'void',
title: info.key,
'x-component': 'Hello',
});
}}
>
{title}
</SchemaInitializer.Item>
);
};
const AddTextFormItemInitializer = (props) => {
const { title, insert } = props;
return (
<SchemaInitializer.Item
onClick={(info) => {
insert({
type: 'void',
title: 'Text',
'x-component': 'Hello',
});
}}
>
{title}
</SchemaInitializer.Item>
);
};
const Page = (props) => {
return (
<div>
{props.children}
<InitializerButton />
</div>
);
};
export default function App() {
return (
<SchemaComponentProvider
components={{ Page, Hello, InitializerButton, FormItemInitializer, AddTextFormItemInitializer }}
>
<SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
properties: {
hello1: {
type: 'void',
title: 'Test1',
'x-component': 'Hello',
},
hello2: {
type: 'void',
title: 'Test2',
'x-component': 'Hello',
},
},
}}
/>
</SchemaComponentProvider>
);
}

View File

@ -0,0 +1,49 @@
import {
ArrayTable,
ArrayTableColumnInitializer,
Input,
SchemaComponent,
SchemaComponentProvider
} from '@nocobase/client';
import React from 'react';
const schema = {
type: 'object',
properties: {
input: {
type: 'array',
default: [
{ id: 1, name: 'Name1' },
{ id: 2, name: 'Name2' },
{ id: 3, name: 'Name3' },
],
'x-component': 'ArrayTable',
'x-column-initializer': 'ArrayTableColumnInitializer',
'x-component-props': {
rowKey: 'id',
},
properties: {
column1: {
type: 'void',
title: 'Name',
'x-component': 'ArrayTable.Column',
properties: {
name: {
type: 'string',
'x-component': 'Input',
'x-read-pretty': true,
},
},
},
},
},
},
};
export default function App() {
return (
<SchemaComponentProvider components={{ Input, ArrayTable, ArrayTableColumnInitializer }}>
<SchemaComponent schema={schema} />
</SchemaComponentProvider>
);
}

View File

@ -5,12 +5,38 @@ group:
path: /client
---
# SchemaInitializer <Badge>待定</Badge>
# SchemaInitializer
用于配置各种 schema 的初始化
## SchemaInitializerProvider
```tsx | pure
const actions = {
<SchemaInitializerProvider
shortcuts={{
Block: [
{
title: 'Data blocks',
children: [
{
title: 'Table',
component: 'TableBlockInitializer',
},
{
title: 'Form',
component: 'FormBlockInitializer',
},
],
},
]
}}
></SchemaInitializerProvider>
```
目前可能需要配置 shortcuts 的有:
```ts
const shortcuts = {
// 表单可选操作
FormAction: [],
// 抽屉表单可选操作
@ -25,9 +51,6 @@ const actions = {
TableRowAction: [],
// 日历可选操作
CalendarAction: [],
};
const blocks = {
// 普通页面可添加区块
Block: [],
// 当前数据页面可添加区块
@ -37,12 +60,122 @@ const blocks = {
// 可添加的表格列
TableColumn: [],
};
const initializer = new SchemaInitializer({ actions, blocks });
initializer.addActionSchema({});
initializer.addBlockSchema({});
<SchemaInitializerProvider initializer={initializer}>
</SchemaInitializerProvider>
```
## SchemaInitializer.Button
常规区块的初始化按钮
```tsx | pure
<SchemaInitializer.Button
// 待插入的节点wrap 处理
wrap={(schema) => schema}
// 插入位置
insertPosition={'beforeBegin'}
// 从上下文获取 shortcuts 里配置的 items
shortcut={'Block'}
items={[
{
title: 'Data blocks',
children: [
{
title: 'Table',
component: 'TableBlockInitializer',
},
{
title: 'Form',
component: 'FormBlockInitializer',
},
],
},
]}
>Create block</SchemaInitializer.Button>
```
动态字段的配置
```tsx | pure
const useFormItemInitializerFields = () => {
const { fields } = useCollection();
return fields.map(field => {
return {
key: field.name,
component: 'FormItemInitializer'
}
});
}
<SchemaInitializer.Button
// 待插入的节点wrap 处理
wrap={(schema) => schema}
// 插入位置
insertPosition={'beforeBegin'}
// 从上下文获取 shortcuts 里配置的 items
shortcut={'Block'}
items={[
{
title: 'Display fields',
children: useFormItemInitializerFields(),
},
]}
>Configure fields</SchemaInitializer.Button>
```
## SchemaInitializer.Item
扩展项
```tsx | pure
const TableBlockInitializer = (props) => {
const { insert } = props;
return (
<SchemaInitializer.Item
icon={<TableOutlined />}
onClick={(info) => {
console.log({ info });
insert({
type: 'void',
title: info.key,
'x-component': 'Hello',
});
}}
items={[
{
type: 'itemGroup',
title: 'select a data source',
children: [
{
key: 'users',
title: 'Users',
},
{
key: 'posts',
title: 'Posts',
},
],
},
]}
>
Table
</SchemaInitializer.Item>
);
};
```
## Examples
### Block
<code src="./demos/demo1.tsx" />
### Action
<code src="./demos/demo2.tsx" />
### FormItem
<code src="./demos/demo3.tsx" />
### Table.Column
<code src="./demos/demo4.tsx" />

View File

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