feat: upgrade formily (#1880)

* feat: upgrade formily

* fix: upgrade @formily/json-schema

* fix: optimizing performance

* fix: performance code

* refactor: `React.memo` and `observer` component add `displayName` property

* fix: add cache to Schema.compile

* feat: 优化代码

* fix: 修复卡顿最终的问题

* Update SchemaComponentProvider.tsx

* feat: 再次优化代码

* feat: optimized code

---------

Co-authored-by: dream2023 <1098626505@qq.com>
This commit is contained in:
chenos 2023-06-06 19:33:04 +08:00 committed by GitHub
parent 36d16bc015
commit e6a2a292b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 6226 additions and 5660 deletions

View File

@ -42,6 +42,7 @@ const HelloProvider = React.memo((props) => {
} }
return <>{props.children}</> return <>{props.children}</>
}); });
HelloProvider.displayName = 'HelloProvider'
app.use(HelloProvider); app.use(HelloProvider);
``` ```

View File

@ -4,27 +4,30 @@ import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { Table, TableColumnType } from 'antd'; import { Table, TableColumnType } from 'antd';
import React from 'react'; import React from 'react';
const ArrayTable = observer((props: any) => { const ArrayTable = observer(
const { rowKey } = props; (props: any) => {
const field = useField<ArrayField>(); const { rowKey } = props;
const schema = useFieldSchema(); const field = useField<ArrayField>();
const columnSchemas = schema.reduceProperties((buf, s) => { const schema = useFieldSchema();
if (s['x-component'] === 'ArrayTable.Column') { const columnSchemas = schema.reduceProperties((buf, s) => {
buf.push(s); if (s['x-component'] === 'ArrayTable.Column') {
} buf.push(s);
return buf; }
}, []); return buf;
}, []);
const columns = columnSchemas.map((s) => { const columns = columnSchemas.map((s) => {
return { return {
render: (value, record) => { render: (value, record) => {
return <RecursionField name={record.__path} schema={s} onlyRenderProperties />; return <RecursionField name={record.__path} schema={s} onlyRenderProperties />;
}, },
} as TableColumnType<any>; } as TableColumnType<any>;
}); });
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); },
{ displayName: 'ArrayTable' },
);
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;

View File

@ -180,11 +180,11 @@ const Hello = observer((props) => {
<div style={{ margin: 50 }}>{props.children}</div> <div style={{ margin: 50 }}>{props.children}</div>
</div> </div>
); );
}); }, { displayName: 'Hello' });
const Page = observer((props) => { const Page = observer((props) => {
return <div>{props.children}</div>; return <div>{props.children}</div>;
}); }, { displayName: 'Page' });
export default () => { export default () => {
return ( return (
@ -240,7 +240,7 @@ const useDragEnd = () => {
const Page = observer((props) => { const Page = observer((props) => {
return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>; return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>;
}); }, { displayName: 'Page' });
function Draggable(props) { function Draggable(props) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ const { attributes, listeners, setNodeRef, transform } = useDraggable({
@ -289,7 +289,7 @@ const Block = observer((props) => {
</div> </div>
</Droppable> </Droppable>
); );
}); }, { displayName: 'Block' });
export default function App() { export default function App() {
return ( return (
@ -316,7 +316,7 @@ export default function App() {
); );
} }
``` ```
## Applications of `x-designer` ## Applications of `x-designer`
`x-designer` is usually used only in wrapper components such as BlockItem, CardItem, FormItem, etc. `x-designer` is usually used only in wrapper components such as BlockItem, CardItem, FormItem, etc.

View File

@ -51,13 +51,13 @@ const ReadPretty = (props) => {
}; };
const SingleText = connect( const SingleText = connect(
Input, Input,
mapProps((props, field) => { mapProps((props, field) => {
return { return {
...props, ...props,
suffix: '后缀', suffix: '后缀',
} }
}), }),
mapReadPretty(ReadPretty), mapReadPretty(ReadPretty),
); );
@ -103,7 +103,7 @@ const SingleText = connect(Input);
const UsedObserver = observer((props) => { const UsedObserver = observer((props) => {
const form = useForm(); const form = useForm();
return <div>UsedObserver: {form.values.t1}</div> return <div>UsedObserver: {form.values.t1}</div>
}); }, { displayName: 'UsedObserver' });
const NotUsedObserver = (props) => { const NotUsedObserver = (props) => {
const form = useForm(); const form = useForm();
@ -192,7 +192,7 @@ export default () => {
}; };
``` ```
Comparison of rendering results by property type Comparison of rendering results by property type
```tsx ```tsx
import React from 'react'; import React from 'react';
@ -310,7 +310,7 @@ const ArrayList = observer((props) => {
</ul> </ul>
</> </>
); );
}); }, { displayName: 'ArrayList' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li> return <li>value: {props.value}</li>
@ -367,7 +367,7 @@ const ArrayList = observer((props) => {
})} })}
</ul> </ul>
); );
}); }, { displayName: 'ArrayList' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li> return <li>value: {props.value}</li>
@ -432,7 +432,7 @@ const ArrayTable = observer((props: any) => {
}); });
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); }, { displayName: 'ArrayTable' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;

View File

@ -116,11 +116,11 @@ const Hello = observer((props) => {
<div style={{ margin: 50 }}>{props.children}</div> <div style={{ margin: 50 }}>{props.children}</div>
</div> </div>
); );
}); }, { displayName: 'Hello' });
const Page = observer((props) => { const Page = observer((props) => {
return <div>{props.children}</div>; return <div>{props.children}</div>;
}); }, { displayName: 'Page' });
export default () => { export default () => {
return ( return (

View File

@ -182,6 +182,7 @@ const HelloProvider = React.memo((props) => {
} }
return <>{props.children}</> return <>{props.children}</>
}); });
HelloProvider.displayName = 'HelloProvider'
app.use(HelloProvider); app.use(HelloProvider);

View File

@ -4,27 +4,30 @@ import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { Table, TableColumnType } from 'antd'; import { Table, TableColumnType } from 'antd';
import React from 'react'; import React from 'react';
const ArrayTable = observer((props: any) => { const ArrayTable = observer(
const { rowKey } = props; (props: any) => {
const field = useField<ArrayField>(); const { rowKey } = props;
const schema = useFieldSchema(); const field = useField<ArrayField>();
const columnSchemas = schema.reduceProperties((buf, s) => { const schema = useFieldSchema();
if (s['x-component'] === 'ArrayTable.Column') { const columnSchemas = schema.reduceProperties((buf, s) => {
buf.push(s); if (s['x-component'] === 'ArrayTable.Column') {
} buf.push(s);
return buf; }
}, []); return buf;
}, []);
const columns = columnSchemas.map((s) => { const columns = columnSchemas.map((s) => {
return { return {
render: (value, record) => { render: (value, record) => {
return <RecursionField name={record.__path} schema={s} onlyRenderProperties />; return <RecursionField name={record.__path} schema={s} onlyRenderProperties />;
}, },
} as TableColumnType<any>; } as TableColumnType<any>;
}); });
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); },
{ displayName: 'ArrayTable' },
);
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;

View File

@ -183,11 +183,11 @@ const Hello = observer((props) => {
<div style={{ margin: 50 }}>{props.children}</div> <div style={{ margin: 50 }}>{props.children}</div>
</div> </div>
); );
}); }, { displayName: 'Hello' });
const Page = observer((props) => { const Page = observer((props) => {
return <div>{props.children}</div>; return <div>{props.children}</div>;
}); }, { displayName: 'Page' });
export default () => { export default () => {
return ( return (
@ -243,7 +243,7 @@ const useDragEnd = () => {
const Page = observer((props) => { const Page = observer((props) => {
return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>; return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>;
}); }, { displayName: 'Page' });
function Draggable(props) { function Draggable(props) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ const { attributes, listeners, setNodeRef, transform } = useDraggable({
@ -292,7 +292,7 @@ const Block = observer((props) => {
</div> </div>
</Droppable> </Droppable>
); );
}); }, { displayName: 'Block' });
export default function App() { export default function App() {
return ( return (

View File

@ -51,13 +51,13 @@ const ReadPretty = (props) => {
}; };
const SingleText = connect( const SingleText = connect(
Input, Input,
mapProps((props, field) => { mapProps((props, field) => {
return { return {
...props, ...props,
suffix: '后缀', suffix: '后缀',
} }
}), }),
mapReadPretty(ReadPretty), mapReadPretty(ReadPretty),
); );
@ -103,7 +103,7 @@ const SingleText = connect(Input);
const UsedObserver = observer((props) => { const UsedObserver = observer((props) => {
const form = useForm(); const form = useForm();
return <div>UsedObserver: {form.values.t1}</div> return <div>UsedObserver: {form.values.t1}</div>
}); }, { displayName: 'UsedObserver' });
const NotUsedObserver = (props) => { const NotUsedObserver = (props) => {
const form = useForm(); const form = useForm();
@ -193,7 +193,7 @@ export default () => {
}; };
``` ```
各类型 properties 渲染结果对比 各类型 properties 渲染结果对比
```tsx ```tsx
import React from 'react'; import React from 'react';
@ -312,7 +312,7 @@ const ArrayList = observer((props) => {
</ul> </ul>
</> </>
); );
}); }, { displayName: 'ArrayList' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li> return <li>value: {props.value}</li>
@ -369,7 +369,7 @@ const ArrayList = observer((props) => {
})} })}
</ul> </ul>
); );
}); }, { displayName: 'ArrayList' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li> return <li>value: {props.value}</li>
@ -434,7 +434,7 @@ const ArrayTable = observer((props: any) => {
}); });
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); }, { displayName: 'ArrayTable' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;
@ -515,4 +515,4 @@ export default () => {
</SchemaComponentProvider> </SchemaComponentProvider>
); );
}; };
``` ```

View File

@ -116,11 +116,11 @@ const Hello = observer((props) => {
<div style={{ margin: 50 }}>{props.children}</div> <div style={{ margin: 50 }}>{props.children}</div>
</div> </div>
); );
}); }, { displayName: 'Hello' });
const Page = observer((props) => { const Page = observer((props) => {
return <div>{props.children}</div>; return <div>{props.children}</div>;
}); }, { displayName: 'Page' });
export default () => { export default () => {
return ( return (

View File

@ -42,6 +42,7 @@ const HelloProvider = React.memo((props) => {
} }
return <>{props.children}</> return <>{props.children}</>
}); });
HelloProvider.displayName = 'HelloProvider'
app.use(HelloProvider); app.use(HelloProvider);
``` ```

View File

@ -4,27 +4,30 @@ import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { Table, TableColumnType } from 'antd'; import { Table, TableColumnType } from 'antd';
import React from 'react'; import React from 'react';
const ArrayTable = observer((props: any) => { const ArrayTable = observer(
const { rowKey } = props; (props: any) => {
const field = useField<ArrayField>(); const { rowKey } = props;
const schema = useFieldSchema(); const field = useField<ArrayField>();
const columnSchemas = schema.reduceProperties((buf, s) => { const schema = useFieldSchema();
if (s['x-component'] === 'ArrayTable.Column') { const columnSchemas = schema.reduceProperties((buf, s) => {
buf.push(s); if (s['x-component'] === 'ArrayTable.Column') {
} buf.push(s);
return buf; }
}, []); return buf;
}, []);
const columns = columnSchemas.map((s) => { const columns = columnSchemas.map((s) => {
return { return {
render: (value, record) => { render: (value, record) => {
return <RecursionField name={record.__path} schema={s} onlyRenderProperties />; return <RecursionField name={record.__path} schema={s} onlyRenderProperties />;
}, },
} as TableColumnType<any>; } as TableColumnType<any>;
}); });
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); },
{ displayName: 'ArrayTable' },
);
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;

View File

@ -183,11 +183,11 @@ const Hello = observer((props) => {
<div style={{ margin: 50 }}>{props.children}</div> <div style={{ margin: 50 }}>{props.children}</div>
</div> </div>
); );
}); }, { displayName: 'Hello' });
const Page = observer((props) => { const Page = observer((props) => {
return <div>{props.children}</div>; return <div>{props.children}</div>;
}); }, { displayName: 'Page' });
export default () => { export default () => {
return ( return (
@ -243,7 +243,7 @@ const useDragEnd = () => {
const Page = observer((props) => { const Page = observer((props) => {
return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>; return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>;
}); }, { displayName: 'Page' });
function Draggable(props) { function Draggable(props) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ const { attributes, listeners, setNodeRef, transform } = useDraggable({
@ -292,7 +292,7 @@ const Block = observer((props) => {
</div> </div>
</Droppable> </Droppable>
); );
}); }, { displayName: 'Block' });
export default function App() { export default function App() {
return ( return (

View File

@ -51,13 +51,13 @@ const ReadPretty = (props) => {
}; };
const SingleText = connect( const SingleText = connect(
Input, Input,
mapProps((props, field) => { mapProps((props, field) => {
return { return {
...props, ...props,
suffix: '后缀', suffix: '后缀',
} }
}), }),
mapReadPretty(ReadPretty), mapReadPretty(ReadPretty),
); );
@ -103,7 +103,7 @@ const SingleText = connect(Input);
const UsedObserver = observer((props) => { const UsedObserver = observer((props) => {
const form = useForm(); const form = useForm();
return <div>UsedObserver: {form.values.t1}</div> return <div>UsedObserver: {form.values.t1}</div>
}); }, { displayName: 'UsedObserver' });
const NotUsedObserver = (props) => { const NotUsedObserver = (props) => {
const form = useForm(); const form = useForm();
@ -193,7 +193,7 @@ export default () => {
}; };
``` ```
各类型 properties 渲染结果对比 各类型 properties 渲染结果对比
```tsx ```tsx
import React from 'react'; import React from 'react';
@ -312,7 +312,7 @@ const ArrayList = observer((props) => {
</ul> </ul>
</> </>
); );
}); }, { displayName: 'ArrayList' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li> return <li>value: {props.value}</li>
@ -369,7 +369,7 @@ const ArrayList = observer((props) => {
})} })}
</ul> </ul>
); );
}); }, { displayName: 'ArrayList' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li> return <li>value: {props.value}</li>
@ -434,7 +434,7 @@ const ArrayTable = observer((props: any) => {
}); });
return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); }, { displayName: 'ArrayTable' });
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;
@ -515,4 +515,4 @@ export default () => {
</SchemaComponentProvider> </SchemaComponentProvider>
); );
}; };
``` ```

View File

@ -116,11 +116,11 @@ const Hello = observer((props) => {
<div style={{ margin: 50 }}>{props.children}</div> <div style={{ margin: 50 }}>{props.children}</div>
</div> </div>
); );
}); }, { displayName: 'Hello' });
const Page = observer((props) => { const Page = observer((props) => {
return <div>{props.children}</div>; return <div>{props.children}</div>;
}); }, { displayName: 'Page' });
export default () => { export default () => {
return ( return (

View File

@ -176,6 +176,7 @@ const HelloProvider = React.memo((props) => {
} }
return <>{props.children}</> return <>{props.children}</>
}); });
HelloProvider.displayName = 'HelloProvider'
app.use(HelloProvider); app.use(HelloProvider);
``` ```

View File

@ -1,5 +1,8 @@
import React from 'react'; import React from 'react';
export default React.memo((props) => { const CommonMemo = React.memo((props) => {
return <>{props.children}</>; return <>{props.children}</>;
}); });
CommonMemo.displayName = 'CommonMemo';
export default CommonMemo;

View File

@ -168,11 +168,11 @@ const App: React.FC = () => {
<Button type="primary" onClick={showDrawer}> <Button type="primary" onClick={showDrawer}>
Open Open
</Button> </Button>
<Drawer <Drawer
title="Basic Drawer" title="Basic Drawer"
placement="right" placement="right"
onClose={onClose} onClose={onClose}
visible={visible} visible={visible}
footer={ footer={
<Button onClick={onClose}>关闭</Button> <Button onClick={onClose}>关闭</Button>
} }
@ -262,8 +262,8 @@ export default function App() {
}); });
}; };
return ( return (
<SchemaComponentProvider <SchemaComponentProvider
form={form} form={form}
components={{ Drawer, Button }} components={{ Drawer, Button }}
> >
<SchemaComponent schema={schema} scope={{ showDrawer, onClose }} /> <SchemaComponent schema={schema} scope={{ showDrawer, onClose }} />
@ -374,7 +374,7 @@ const Action: any = observer((props: any) => {
<RecursionField basePath={field.address} schema={schema} onlyRenderProperties /> <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />
</VisibleContext.Provider> </VisibleContext.Provider>
); );
}); }, { displayName: 'Action' });
Action.Drawer = observer((props: any) => { Action.Drawer = observer((props: any) => {
const [visible, setVisible] = useContext(VisibleContext); const [visible, setVisible] = useContext(VisibleContext);
@ -411,13 +411,13 @@ Action.Drawer = observer((props: any) => {
)} )}
</> </>
); );
}); }, { displayName: 'Action.Drawer' });
Action.Drawer.Footer = observer((props: any) => { Action.Drawer.Footer = observer((props: any) => {
const field = useField(); const field = useField();
const schema = useFieldSchema(); const schema = useFieldSchema();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />; return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
}); }, { displayName: 'Action.Drawer.Footer' });
const schema: ISchema = { const schema: ISchema = {
type: 'object', type: 'object',
@ -500,7 +500,7 @@ const Hello = observer(({ name }) => {
>更新</Button> >更新</Button>
</div> </div>
) )
}); }, { displayName: 'Hello' });
const RouteSchemaComponent = (props) => { const RouteSchemaComponent = (props) => {
const route = useRoute(); const route = useRoute();
@ -678,7 +678,7 @@ const useDragEnd = () => {
const Page = observer((props) => { const Page = observer((props) => {
return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>; return <DndContext onDragEnd={useDragEnd()}>{props.children}</DndContext>;
}); }, { displayName: 'Page' });
function Draggable(props) { function Draggable(props) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ const { attributes, listeners, setNodeRef, transform } = useDraggable({
@ -727,7 +727,7 @@ const Block = observer((props) => {
</div> </div>
</Droppable> </Droppable>
); );
}); }, { displayName: 'Block' });
export default function App() { export default function App() {
return ( return (
@ -856,4 +856,4 @@ await respond(ctx);
- Provider 的顺序怎么处理 - Provider 的顺序怎么处理
- 如何动态的加载前端模块 - 如何动态的加载前端模块
未完待续... 未完待续...

View File

@ -10,9 +10,9 @@
"@dnd-kit/core": "^5.0.1", "@dnd-kit/core": "^5.0.1",
"@dnd-kit/sortable": "^6.0.0", "@dnd-kit/sortable": "^6.0.0",
"@emotion/css": "^11.7.1", "@emotion/css": "^11.7.1",
"@formily/antd": "2.0.20", "@formily/antd": "2.2.24",
"@formily/core": "2.0.20", "@formily/core": "2.2.24",
"@formily/react": "2.0.20", "@formily/react": "2.2.24",
"@nocobase/evaluators": "0.9.4-alpha.2", "@nocobase/evaluators": "0.9.4-alpha.2",
"@nocobase/sdk": "0.9.4-alpha.2", "@nocobase/sdk": "0.9.4-alpha.2",
"@nocobase/utils": "0.9.4-alpha.2", "@nocobase/utils": "0.9.4-alpha.2",

View File

@ -61,6 +61,8 @@ const App = React.memo((props: any) => {
return <C />; return <C />;
}); });
App.displayName = 'App';
export class Application { export class Application {
providers = []; providers = [];
mainComponent = null; mainComponent = null;

View File

@ -3,18 +3,21 @@ import { useDesignable } from '@nocobase/client';
import { Button } from 'antd'; import { Button } from 'antd';
import { observer } from '@formily/react'; import { observer } from '@formily/react';
export const Hello: React.FC<any> = observer(({ name }) => { export const Hello: React.FC<any> = observer(
const { patch, remove } = useDesignable(); ({ name }) => {
return ( const { patch, remove } = useDesignable();
<div> return (
<h1>Hello {name}!</h1> <div>
<Button <h1>Hello {name}!</h1>
onClick={() => { <Button
patch('x-component-props.name', Math.random()); onClick={() => {
}} patch('x-component-props.name', Math.random());
> }}
>
</Button>
</div> </Button>
); </div>
}); );
},
{ displayName: 'Hello' },
);

View File

@ -31,36 +31,39 @@ const useDef = (options, props) => {
} }
}; };
export const CollectionFieldsTable: React.FC<TableVoidProps> = observer((props) => { export const CollectionFieldsTable: React.FC<TableVoidProps> = observer(
const { rowKey = 'id', useDataSource = useDef, useSelectedRowKeys = useDefSelectedRowKeys } = props; (props) => {
const field = useField<Field>(); const { rowKey = 'id', useDataSource = useDef, useSelectedRowKeys = useDefSelectedRowKeys } = props;
const fieldSchema = useFieldSchema(); const field = useField<Field>();
const form = useMemo(() => createForm(), []); const fieldSchema = useFieldSchema();
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' })); const form = useMemo(() => createForm(), []);
const result = useDataSource( const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
{ const result = useDataSource(
uid: fieldSchema['x-uid'], {
onSuccess(data) { uid: fieldSchema['x-uid'],
form.setValues({ onSuccess(data) {
[fieldSchema.name]: data?.data, form.setValues({
}); [fieldSchema.name]: data?.data,
});
},
}, },
}, props,
props, );
); return (
return ( <AsyncDataProvider value={result}>
<AsyncDataProvider value={result}> <FormContext.Provider value={form}>
<FormContext.Provider value={form}> <FieldContext.Provider value={f}>
<FieldContext.Provider value={f}> <CollectionFieldsTableArray
<CollectionFieldsTableArray {...props}
{...props} rowKey={rowKey}
rowKey={rowKey} loading={result?.['loading']}
loading={result?.['loading']} useSelectedRowKeys={useSelectedRowKeys}
useSelectedRowKeys={useSelectedRowKeys} pagination={false}
pagination={false} />
/> </FieldContext.Provider>
</FieldContext.Provider> </FormContext.Provider>
</FormContext.Provider> </AsyncDataProvider>
</AsyncDataProvider> );
); },
}); { displayName: 'CollectionFieldsTable' },
);

View File

@ -79,149 +79,150 @@ interface CategorizeDataItem {
data: Array<any>; data: Array<any>;
} }
export const CollectionFieldsTableArray: React.FC<any> = observer((props) => { export const CollectionFieldsTableArray: React.FC<any> = observer(
const sortKeyArr: Array<CategorizeKey> = ['primaryAndForeignKey', 'relation', 'basic', 'systemInfo']; (props) => {
const field = useField<ArrayField>(); const sortKeyArr: Array<CategorizeKey> = ['primaryAndForeignKey', 'relation', 'basic', 'systemInfo'];
const { name } = useRecord(); const field = useField<ArrayField>();
const { t } = useTranslation(); const { name } = useRecord();
const compile = useCompile(); const { t } = useTranslation();
const { getInterface, getInheritCollections, getCollection, getCurrentCollectionFields, getInheritedFields } = const compile = useCompile();
useCollectionManager(); const { getInterface, getInheritCollections, getCollection, getCurrentCollectionFields, getInheritedFields } =
const { useCollectionManager();
showIndex = true, const {
useSelectedRowKeys = useDef, showIndex = true,
useDataSource = useDefDataSource, useSelectedRowKeys = useDef,
onChange, useDataSource = useDefDataSource,
...others onChange,
} = props; ...others
const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys(); } = props;
const [categorizeData, setCategorizeData] = useState<Array<CategorizeDataItem>>([]); const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys();
const [expandedKeys, setExpendedKeys] = useState(selectedRowKeys); const [categorizeData, setCategorizeData] = useState<Array<CategorizeDataItem>>([]);
const inherits = getInheritCollections(name); const [expandedKeys, setExpendedKeys] = useState(selectedRowKeys);
const currentFields = getCurrentCollectionFields(name); const inherits = getInheritCollections(name);
useDataSource({ const currentFields = getCurrentCollectionFields(name);
onSuccess(data) { useDataSource({
field.value = data?.data || []; onSuccess(data) {
const tmpData: Array<CategorizeDataItem> = []; field.value = data?.data || [];
const categorizeMap = new Map<CategorizeKey, any>(); const tmpData: Array<CategorizeDataItem> = [];
const addCategorizeVal = (categorizeKey: CategorizeKey, val) => { const categorizeMap = new Map<CategorizeKey, any>();
let fieldArr = categorizeMap.get(categorizeKey); const addCategorizeVal = (categorizeKey: CategorizeKey, val) => {
if (!fieldArr) { let fieldArr = categorizeMap.get(categorizeKey);
fieldArr = []; if (!fieldArr) {
} fieldArr = [];
fieldArr.push(val); }
categorizeMap.set(categorizeKey, fieldArr); fieldArr.push(val);
}; categorizeMap.set(categorizeKey, fieldArr);
field.value.forEach((item) => { };
const itemInterface = getInterface(item?.interface); field.value.forEach((item) => {
if (item?.primaryKey || item.isForeignKey) { const itemInterface = getInterface(item?.interface);
addCategorizeVal('primaryAndForeignKey', item); if (item?.primaryKey || item.isForeignKey) {
return; addCategorizeVal('primaryAndForeignKey', item);
} return;
const group = itemInterface?.group as CategorizeKey; }
switch (group) { const group = itemInterface?.group as CategorizeKey;
case 'systemInfo': switch (group) {
case 'relation': case 'systemInfo':
addCategorizeVal(group, item); case 'relation':
break; addCategorizeVal(group, item);
default: break;
addCategorizeVal('basic', item); default:
} addCategorizeVal('basic', item);
}); }
if (inherits) {
inherits.forEach((v) => {
sortKeyArr.push(v);
const parentCollection = getCollection(v);
parentCollection.fields.map((k) => {
if (k.interface) {
addCategorizeVal(v, new Proxy(k, {}));
field.value.push(new Proxy(k, {}));
}
});
}); });
} if (inherits) {
sortKeyArr.forEach((key) => { inherits.forEach((v) => {
if (categorizeMap.get(key)?.length > 0) { sortKeyArr.push(v);
const parentCollection = getCollection(key); const parentCollection = getCollection(v);
tmpData.push({ parentCollection.fields.map((k) => {
key, if (k.interface) {
name: addCategorizeVal(v, new Proxy(k, {}));
t(CategorizeKeyNameMap.get(key)) || field.value.push(new Proxy(k, {}));
t(`Parent collection fields`) + `(${compile(parentCollection.title)})`, }
data: categorizeMap.get(key), });
}); });
} }
}); sortKeyArr.forEach((key) => {
setExpendedKeys(sortKeyArr); if (categorizeMap.get(key)?.length > 0) {
setCategorizeData(tmpData); const parentCollection = getCollection(key);
}, tmpData.push({
}); key,
const useTableColumns = () => { name:
const schema = useFieldSchema(); t(CategorizeKeyNameMap.get(key)) ||
const { exists, render } = useSchemaInitializer(schema['x-initializer']); t(`Parent collection fields`) + `(${compile(parentCollection.title)})`,
const columns = schema data: categorizeMap.get(key),
.reduceProperties((buf, s) => { });
if (isColumnComponent(s)) { }
return buf.concat([s]); });
} setExpendedKeys(sortKeyArr);
}, []) setCategorizeData(tmpData);
.map((s: Schema) => { },
return {
title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
dataIndex: s.name,
key: s.name,
render: (v, record) => {
const index = findIndex(field.value, record);
return (
<RecordIndexProvider index={index}>
<RecordProvider record={record}>
<RecursionField schema={s} name={index} onlyRenderProperties />
</RecordProvider>
</RecordIndexProvider>
);
},
} as TableColumnProps<any>;
});
if (!exists) {
return columns;
}
return columns.concat({
title: render(),
dataIndex: 'TABLE_COLUMN_INITIALIZER',
key: 'TABLE_COLUMN_INITIALIZER',
}); });
}; const useTableColumns = () => {
const schema = useFieldSchema();
const ExpandedRowRender = (record: CategorizeDataItem, index, indent, expanded) => { const { exists, render } = useSchemaInitializer(schema['x-initializer']);
const columns = useTableColumns(); const columns = schema
if (!props.loading) { .reduceProperties((buf, s) => {
if (inherits.includes(record.key)) { if (isColumnComponent(s)) {
columns.pop(); return buf.concat([s]);
columns.push({ }
title: <RecursionField name={'column4'} schema={overridingSchema as Schema} onlyRenderSelf />, }, [])
dataIndex: 'column4', .map((s: Schema) => {
key: 'column4', return {
render: (v, record) => { title: <RecursionField name={s.name} schema={s} onlyRenderSelf />,
const index = findIndex(field.value, record); dataIndex: s.name,
return ( key: s.name,
<RecordIndexProvider index={index}> render: (v, record) => {
<RecordProvider record={record}> const index = findIndex(field.value, record);
<SchemaComponent return (
scope={{ currentCollection: name }} <RecordIndexProvider index={index}>
schema={overridingSchema as Schema} <RecordProvider record={record}>
name={index} <RecursionField schema={s} name={index} onlyRenderProperties />
onlyRenderProperties </RecordProvider>
/> </RecordIndexProvider>
</RecordProvider> );
</RecordIndexProvider> },
); } as TableColumnProps<any>;
},
}); });
if (!exists) {
return columns;
} }
const restProps = { return columns.concat({
rowSelection: title: render(),
props.rowSelection && !inherits.includes(record.key) dataIndex: 'TABLE_COLUMN_INITIALIZER',
? { key: 'TABLE_COLUMN_INITIALIZER',
});
};
const ExpandedRowRender = (record: CategorizeDataItem, index, indent, expanded) => {
const columns = useTableColumns();
if (!props.loading) {
if (inherits.includes(record.key)) {
columns.pop();
columns.push({
title: <RecursionField name={'column4'} schema={overridingSchema as Schema} onlyRenderSelf />,
dataIndex: 'column4',
key: 'column4',
render: (v, record) => {
const index = findIndex(field.value, record);
return (
<RecordIndexProvider index={index}>
<RecordProvider record={record}>
<SchemaComponent
scope={{ currentCollection: name }}
schema={overridingSchema as Schema}
name={index}
onlyRenderProperties
/>
</RecordProvider>
</RecordIndexProvider>
);
},
});
}
const restProps = {
rowSelection:
props.rowSelection && !inherits.includes(record.key)
? {
type: 'checkbox', type: 'checkbox',
selectedRowKeys, selectedRowKeys,
onChange(selectedRowKeys: any[]) { onChange(selectedRowKeys: any[]) {
@ -229,52 +230,54 @@ export const CollectionFieldsTableArray: React.FC<any> = observer((props) => {
}, },
...props.rowSelection, ...props.rowSelection,
} }
: undefined, : undefined,
}; };
return ( return (
<Table <Table
{...others} {...others}
{...restProps} {...restProps}
components={components} components={components}
showHeader={true} showHeader={true}
columns={columns} columns={columns}
dataSource={record.data} dataSource={record.data}
pagination={false} pagination={false}
/> />
); );
} }
}; };
return ( return (
<div <div
className={css` className={css`
.ant-table { .ant-table {
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
}
`}
>
<Table
showHeader={false}
loading={props?.loading}
columns={groupColumns}
dataSource={categorizeData}
pagination={false}
expandable={{
expandedRowRender: ExpandedRowRender,
expandedRowKeys: expandedKeys,
}}
onExpand={(expanded, record) => {
let keys = [];
if (expanded) {
keys = expandedKeys.concat([record.key]);
} else {
keys = expandedKeys.filter((v) => {
return v !== record.key;
});
} }
setExpendedKeys(keys); `}
}} >
/> <Table
</div> showHeader={false}
); loading={props?.loading}
}); columns={groupColumns}
dataSource={categorizeData}
pagination={false}
expandable={{
expandedRowRender: ExpandedRowRender,
expandedRowKeys: expandedKeys,
}}
onExpand={(expanded, record) => {
let keys = [];
if (expanded) {
keys = expandedKeys.concat([record.key]);
} else {
keys = expandedKeys.filter((v) => {
return v !== record.key;
});
}
setExpendedKeys(keys);
}}
/>
</div>
);
},
{ displayName: 'CollectionFieldsTableArray' },
);

View File

@ -50,17 +50,20 @@ function Droppable(props) {
); );
} }
const TabTitle = observer(({ item }: { item: any }) => { const TabTitle = observer(
return ( ({ item }: { item: any }) => {
<Droppable id={item.id.toString()} data={item}> return (
<div> <Droppable id={item.id.toString()} data={item}>
<Draggable id={item.id.toString()} data={item}> <div>
<TabBar item={item} /> <Draggable id={item.id.toString()} data={item}>
</Draggable> <TabBar item={item} />
</div> </Draggable>
</Droppable> </div>
); </Droppable>
}); );
},
{ displayName: 'TabTitle' },
);
const TabBar = ({ item }) => { const TabBar = ({ item }) => {
const compile = useCompile(); const compile = useCompile();
@ -71,45 +74,48 @@ const TabBar = ({ item }) => {
</span> </span>
); );
}; };
const DndProvider = observer((props) => { const DndProvider = observer(
const [activeTab, setActiveId] = useState(null); (props) => {
const { refresh } = useContext(CollectionCategroriesContext); const [activeTab, setActiveId] = useState(null);
const { refresh: refreshCM } = useResourceActionContext(); const { refresh } = useContext(CollectionCategroriesContext);
const api = useAPIClient(); const { refresh: refreshCM } = useResourceActionContext();
const onDragEnd = async (props: DragEndEvent) => { const api = useAPIClient();
const { active, over } = props; const onDragEnd = async (props: DragEndEvent) => {
setTimeout(() => { const { active, over } = props;
setActiveId(null); setTimeout(() => {
}); setActiveId(null);
if (over && over.id !== active.id) {
await api.resource('collectionCategories').move({
sourceId: active.id,
targetId: over.id,
}); });
await refresh(); if (over && over.id !== active.id) {
await refreshCM(); await api.resource('collectionCategories').move({
sourceId: active.id,
targetId: over.id,
});
await refresh();
await refreshCM();
}
};
function onDragStart(event) {
setActiveId(event.active?.data.current);
} }
};
function onDragStart(event) { const mouseSensor = useSensor(MouseSensor, {
setActiveId(event.active?.data.current); activationConstraint: {
} distance: 10,
},
const mouseSensor = useSensor(MouseSensor, { });
activationConstraint: { const sensors = useSensors(mouseSensor);
distance: 10, return (
}, <DndContext sensors={sensors} onDragEnd={onDragEnd} onDragStart={onDragStart}>
}); {props.children}
const sensors = useSensors(mouseSensor); <DragOverlay>
return ( {activeTab ? <span style={{ whiteSpace: 'nowrap' }}>{<TabBar item={activeTab} />}</span> : null}
<DndContext sensors={sensors} onDragEnd={onDragEnd} onDragStart={onDragStart}> </DragOverlay>
{props.children} </DndContext>
<DragOverlay> );
{activeTab ? <span style={{ whiteSpace: 'nowrap' }}>{<TabBar item={activeTab} />}</span> : null} },
</DragOverlay> { displayName: 'DndProvider' },
</DndContext> );
);
});
export const ConfigurationTabs = () => { export const ConfigurationTabs = () => {
const { data, refresh } = useContext(CollectionCategroriesContext); const { data, refresh } = useContext(CollectionCategroriesContext);
const { refresh: refreshCM, run, defaultRequest, setState } = useResourceActionContext(); const { refresh: refreshCM, run, defaultRequest, setState } = useResourceActionContext();

View File

@ -3,14 +3,17 @@ import { Tag } from 'antd';
import React from 'react'; import React from 'react';
import { useCompile } from '../../../schema-component'; import { useCompile } from '../../../schema-component';
export const CollectionCategory = observer((props: any) => { export const CollectionCategory = observer(
const { value } = props; (props: any) => {
const compile = useCompile(); const { value } = props;
return ( const compile = useCompile();
<> return (
{value.map((item) => { <>
return <Tag color={item.color}>{compile(item?.name)}</Tag>; {value.map((item) => {
})} return <Tag color={item.color}>{compile(item?.name)}</Tag>;
</> })}
); </>
}); );
},
{ displayName: 'CollectionCategory' },
);

View File

@ -4,13 +4,16 @@ import React from 'react';
import { useCompile } from '../../../schema-component'; import { useCompile } from '../../../schema-component';
import { useCollectionManager } from '../../hooks'; import { useCollectionManager } from '../../hooks';
export const CollectionFieldInterface = observer((props: any) => { export const CollectionFieldInterface = observer(
const { value } = props; (props: any) => {
const { getInterface } = useCollectionManager(); const { value } = props;
const compile = useCompile(); const { getInterface } = useCollectionManager();
const schema = getInterface(value); const compile = useCompile();
const schema = getInterface(value);
if (!schema) return null; if (!schema) return null;
return <Tag>{compile(schema.title)}</Tag>; return <Tag>{compile(schema.title)}</Tag>;
}); },
{ displayName: 'CollectionFieldInterface' },
);

View File

@ -4,11 +4,14 @@ import React from 'react';
import { useCompile } from '../../../schema-component'; import { useCompile } from '../../../schema-component';
import { useCollectionManager } from '../../hooks'; import { useCollectionManager } from '../../hooks';
export const CollectionTemplate = observer((props: any) => { export const CollectionTemplate = observer(
const { value } = props; (props: any) => {
const { getTemplate } = useCollectionManager(); const { value } = props;
const compile = useCompile(); const { getTemplate } = useCollectionManager();
const schema = getTemplate(value); const compile = useCompile();
const schema = getTemplate(value);
return <Tag>{compile(schema?.title || '{{t("General collection")}}')}</Tag>; return <Tag>{compile(schema?.title || '{{t("General collection")}}')}</Tag>;
}); },
{ displayName: 'CollectionTemplate' },
);

View File

@ -6,36 +6,39 @@ import { useTranslation } from 'react-i18next';
import { useCompile } from '../../../schema-component'; import { useCompile } from '../../../schema-component';
import { useCollectionManager } from '../../hooks'; import { useCollectionManager } from '../../hooks';
export const FieldSummary = observer((props: any) => { export const FieldSummary = observer(
const { schemaKey } = props; (props: any) => {
const { getInterface } = useCollectionManager(); const { schemaKey } = props;
const compile = useCompile(); const { getInterface } = useCollectionManager();
const { t } = useTranslation(); const compile = useCompile();
const schema = getInterface(schemaKey); const { t } = useTranslation();
const schema = getInterface(schemaKey);
if (!schema) return null; if (!schema) return null;
return ( return (
<div <div
className={css` className={css`
background: #f6f6f6; background: #f6f6f6;
margin-bottom: 24px; margin-bottom: 24px;
padding: 16px; padding: 16px;
`} `}
> >
<div className={css``}> <div className={css``}>
{t('Field interface')}: <Tag>{compile(schema.title)}</Tag> {t('Field interface')}: <Tag>{compile(schema.title)}</Tag>
</div>
{schema.description ? (
<div
className={css`
margin-top: 8px;
color: rgba(0, 0, 0, 0.45);
`}
>
{compile(schema.description)}
</div> </div>
) : null} {schema.description ? (
</div> <div
); className={css`
}); margin-top: 8px;
color: rgba(0, 0, 0, 0.45);
`}
>
{compile(schema.description)}
</div>
) : null}
</div>
);
},
{ displayName: 'FieldSummary' },
);

View File

@ -6,36 +6,39 @@ import { useTranslation } from 'react-i18next';
import { useCompile } from '../../../schema-component'; import { useCompile } from '../../../schema-component';
import { useCollectionManager } from '../../hooks'; import { useCollectionManager } from '../../hooks';
export const TemplateSummay = observer((props: any) => { export const TemplateSummay = observer(
const { schemaKey } = props; (props: any) => {
const { getTemplate } = useCollectionManager(); const { schemaKey } = props;
const compile = useCompile(); const { getTemplate } = useCollectionManager();
const { t } = useTranslation(); const compile = useCompile();
const schema = getTemplate(schemaKey); const { t } = useTranslation();
const schema = getTemplate(schemaKey);
if (!schema) return null; if (!schema) return null;
return ( return (
<div <div
className={css` className={css`
background: #f6f6f6; background: #f6f6f6;
margin-bottom: 24px; margin-bottom: 24px;
padding: 16px; padding: 16px;
`} `}
> >
<div className={css``}> <div className={css``}>
{t('Collection template')}: <Tag>{compile(schema.title)}</Tag> {t('Collection template')}: <Tag>{compile(schema.title)}</Tag>
</div>
{schema.description ? (
<div
className={css`
margin-top: 8px;
color: rgba(0, 0, 0, 0.45);
`}
>
{compile(schema.description)}
</div> </div>
) : null} {schema.description ? (
</div> <div
); className={css`
}); margin-top: 8px;
color: rgba(0, 0, 0, 0.45);
`}
>
{compile(schema.description)}
</div>
) : null}
</div>
);
},
{ displayName: 'TemplateSummay' },
);

View File

@ -6,109 +6,127 @@ import { useRecord } from '../../../record-provider';
import { useCompile } from '../../../schema-component'; import { useCompile } from '../../../schema-component';
import { useCollectionManager } from '../../hooks'; import { useCollectionManager } from '../../hooks';
export const SourceForeignKey = observer(() => { export const SourceForeignKey = observer(
const record = useRecord(); () => {
const { getCollection } = useCollectionManager(); const record = useRecord();
const collection = record?.collectionName ? getCollection(record.collectionName) : record; const { getCollection } = useCollectionManager();
const field = useField<Field>(); const collection = record?.collectionName ? getCollection(record.collectionName) : record;
const form = useForm(); const field = useField<Field>();
const { getCollectionFields } = useCollectionManager(); const form = useForm();
return ( const { getCollectionFields } = useCollectionManager();
<div> return (
<Select <div>
allowClear <Select
placeholder={'留空时,自动生成 FK 字段'} allowClear
disabled={field.disabled} placeholder={'留空时,自动生成 FK 字段'}
value={field.value} disabled={field.disabled}
options={getCollectionFields(collection.name) value={field.value}
.filter((field) => field.type) options={getCollectionFields(collection.name)
.map((field) => { .filter((field) => field.type)
return { .map((field) => {
label: field?.uiSchema?.title || field.name, return {
value: field.name, label: field?.uiSchema?.title || field.name,
}; value: field.name,
})} };
/> })}
</div> />
); </div>
}); );
},
{ displayName: 'SourceForeignKey' },
);
export const ThroughForeignKey = observer(() => { export const ThroughForeignKey = observer(
const field = useField<Field>(); () => {
const form = useForm(); const field = useField<Field>();
const { getCollectionFields } = useCollectionManager(); const form = useForm();
return ( const { getCollectionFields } = useCollectionManager();
<div> return (
<Select <div>
allowClear <Select
placeholder={'留空时,自动生成 FK 字段'} allowClear
disabled={field.disabled} placeholder={'留空时,自动生成 FK 字段'}
value={field.value} disabled={field.disabled}
options={getCollectionFields(form.values.through) value={field.value}
.filter((field) => field.type) options={getCollectionFields(form.values.through)
.map((field) => { .filter((field) => field.type)
return { .map((field) => {
label: field?.uiSchema?.title || field.name, return {
value: field.name, label: field?.uiSchema?.title || field.name,
}; value: field.name,
})} };
/> })}
</div> />
); </div>
}); );
},
{ displayName: 'ThroughForeignKey' },
);
export const TargetForeignKey = observer(() => { export const TargetForeignKey = observer(
const field = useField<Field>(); () => {
const form = useForm(); const field = useField<Field>();
const { getCollectionFields } = useCollectionManager(); const form = useForm();
return ( const { getCollectionFields } = useCollectionManager();
<div> return (
<Select <div>
allowClear <Select
placeholder={'留空时,自动生成 FK 字段'} allowClear
disabled={field.disabled} placeholder={'留空时,自动生成 FK 字段'}
value={field.value} disabled={field.disabled}
options={getCollectionFields(form.values.target) value={field.value}
.filter((field) => field.type) options={getCollectionFields(form.values.target)
.map((field) => { .filter((field) => field.type)
return { .map((field) => {
label: field?.uiSchema?.title || field.name, return {
value: field.name, label: field?.uiSchema?.title || field.name,
}; value: field.name,
})} };
/> })}
</div> />
); </div>
}); );
},
{ displayName: 'TargetForeignKey' },
);
export const SourceCollection = observer(() => { export const SourceCollection = observer(
const record = useRecord(); () => {
const { getCollection } = useCollectionManager(); const record = useRecord();
const collection = record?.collectionName ? getCollection(record.collectionName) : record; const { getCollection } = useCollectionManager();
const compile = useCompile(); const collection = record?.collectionName ? getCollection(record.collectionName) : record;
return ( const compile = useCompile();
<div> return (
<Select <div>
disabled <Select
value={collection.name} disabled
options={[{ value: collection.name, label: compile(collection.title) }]} value={collection.name}
/> options={[{ value: collection.name, label: compile(collection.title) }]}
</div> />
); </div>
}); );
},
{ displayName: 'SourceCollection' },
);
export const SourceKey = observer(() => { export const SourceKey = observer(
return ( () => {
<div> return (
<Select disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} /> <div>
</div> <Select disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} />
); </div>
}); );
},
{ displayName: 'SourceKey' },
);
export const TargetKey = observer(() => { export const TargetKey = observer(
return ( () => {
<div> return (
<Select disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} /> <div>
</div> <Select disabled value={'id'} options={[{ value: 'id', label: 'ID' }]} />
); </div>
}); );
},
{ displayName: 'TargetKey' },
);

View File

@ -166,107 +166,113 @@ export const ds = {
useDestroyAction, useDestroyAction,
}; };
export const SubFieldDataSourceProvider = observer((props) => { export const SubFieldDataSourceProvider = observer(
const [selectedRowKeys, setSelectedRowKeys] = useState([]); (props) => {
const [dataSource, setDataSource] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const record = useRecord(); const [dataSource, setDataSource] = useState([]);
const api = useAPIClient(); const record = useRecord();
const service = useRequest( const api = useAPIClient();
() => { const service = useRequest(
if (record.interface === 'subTable') { () => {
if (record?.children?.length) { if (record.interface === 'subTable') {
return Promise.resolve({ if (record?.children?.length) {
data: record?.children || [], return Promise.resolve({
}); data: record?.children || [],
});
}
return api
.resource('fields')
.list({
paginate: false,
// appends: ['uiSchema'],
sort: 'sort',
filter: {
parentKey: record.key,
},
})
.then((res) => res?.data);
} }
return api return Promise.resolve({
.resource('fields') data: [],
.list({ });
paginate: false,
// appends: ['uiSchema'],
sort: 'sort',
filter: {
parentKey: record.key,
},
})
.then((res) => res?.data);
}
return Promise.resolve({
data: [],
});
},
{
onSuccess(data) {
console.log('dataSource1', data?.data);
setDataSource(data?.data);
}, },
}, {
); onSuccess(data) {
return ( console.log('dataSource1', data?.data);
<CollectionProvider collection={collection}> setDataSource(data?.data);
<DataSourceContext.Provider },
value={{ },
rowKey: 'name', );
service, return (
dataSource, <CollectionProvider collection={collection}>
setDataSource, <DataSourceContext.Provider
selectedRowKeys, value={{
setSelectedRowKeys, rowKey: 'name',
}} service,
> dataSource,
{props.children} setDataSource,
</DataSourceContext.Provider> selectedRowKeys,
</CollectionProvider> setSelectedRowKeys,
); }}
}); >
{props.children}
</DataSourceContext.Provider>
</CollectionProvider>
);
},
{ displayName: 'SubFieldDataSourceProvider' },
);
export const DataSourceProvider = observer((props: any) => { export const DataSourceProvider = observer(
const { rowKey, collection, association } = props; (props: any) => {
const [selectedRowKeys, setSelectedRowKeys] = useState([]); const { rowKey, collection, association } = props;
const [dataSource, setDataSource] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const record = useRecord(); const [dataSource, setDataSource] = useState([]);
const api = useAPIClient(); const record = useRecord();
const { getCollection } = useCollectionManager(); const api = useAPIClient();
const coll = getCollection(collection); const { getCollection } = useCollectionManager();
const resourceOf = record?.[association.targetKey || 'id']; const coll = getCollection(collection);
const service = useRequest( const resourceOf = record?.[association.targetKey || 'id'];
() => { const service = useRequest(
if (resourceOf) { () => {
return api if (resourceOf) {
.request({ return api
resource: `${association.collectionName}.${association.name}`, .request({
resourceOf, resource: `${association.collectionName}.${association.name}`,
action: 'list', resourceOf,
params: { action: 'list',
appends: coll?.fields?.filter((field) => field.target)?.map((field) => field.name), params: {
}, appends: coll?.fields?.filter((field) => field.target)?.map((field) => field.name),
}) },
.then((res) => res.data); })
} .then((res) => res.data);
return Promise.resolve({ }
data: record?.[association.name] || [], return Promise.resolve({
}); data: record?.[association.name] || [],
}, });
{
onSuccess(data) {
setDataSource(data?.data);
}, },
}, {
); onSuccess(data) {
return ( setDataSource(data?.data);
<CollectionProvider collection={coll}> },
<DataSourceContext.Provider },
value={{ );
rowKey, return (
service, <CollectionProvider collection={coll}>
dataSource, <DataSourceContext.Provider
setDataSource, value={{
selectedRowKeys, rowKey,
setSelectedRowKeys, service,
}} dataSource,
> setDataSource,
{props.children} selectedRowKeys,
</DataSourceContext.Provider> setSelectedRowKeys,
</CollectionProvider> }}
); >
}); {props.children}
</DataSourceContext.Provider>
</CollectionProvider>
);
},
{ displayName: 'DataSourceProvider' },
);

View File

@ -217,3 +217,4 @@ function areEqual(prevProps, nextProps) {
} }
export const PreviewFields = React.memo(PreviewCom, areEqual); export const PreviewFields = React.memo(PreviewCom, areEqual);
PreviewFields.displayName = 'PreviewFields';

View File

@ -6,21 +6,27 @@ import { ActionModal } from './Action.Modal';
import { ActionPage } from './Action.Page'; import { ActionPage } from './Action.Page';
import { ComposedActionDrawer } from './types'; import { ComposedActionDrawer } from './types';
export const ActionContainer: ComposedActionDrawer = observer((props: any) => { export const ActionContainer: ComposedActionDrawer = observer(
const { openMode } = useActionContext(); (props: any) => {
if (openMode === 'drawer') { const { openMode } = useActionContext();
return <ActionDrawer footerNodeName={'Action.Container.Footer'} {...props} />; if (openMode === 'drawer') {
} return <ActionDrawer footerNodeName={'Action.Container.Footer'} {...props} />;
if (openMode === 'modal') { }
return <ActionModal footerNodeName={'Action.Container.Footer'} {...props} />; if (openMode === 'modal') {
} return <ActionModal footerNodeName={'Action.Container.Footer'} {...props} />;
return <ActionPage footerNodeName={'Action.Container.Footer'} {...props} />; }
}); return <ActionPage footerNodeName={'Action.Container.Footer'} {...props} />;
},
{ displayName: 'ActionContainer' },
);
ActionContainer.Footer = observer(() => { ActionContainer.Footer = observer(
const field = useField(); () => {
const schema = useFieldSchema(); const field = useField();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />; const schema = useFieldSchema();
}); return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
},
{ displayName: 'ActionContainer.Footer' },
);
export default ActionContainer; export default ActionContainer;

View File

@ -14,111 +14,117 @@ const openSizeWidthMap = new Map<OpenSize, string>([
['middle', '50%'], ['middle', '50%'],
['large', '70%'], ['large', '70%'],
]); ]);
export const ActionDrawer: ComposedActionDrawer = observer((props) => { export const ActionDrawer: ComposedActionDrawer = observer(
const { footerNodeName = 'Action.Drawer.Footer', ...others } = props; (props) => {
const { t } = useTranslation(); const { footerNodeName = 'Action.Drawer.Footer', ...others } = props;
const { visible, setVisible, openSize = 'middle' } = useActionContext(); const { t } = useTranslation();
const schema = useFieldSchema(); const { visible, setVisible, openSize = 'middle' } = useActionContext();
const field = useField(); const schema = useFieldSchema();
const openSizeFromParent = schema.parent?.['x-component-props']?.['openSize']; const field = useField();
const finalOpenSize = openSizeFromParent || openSize; const openSizeFromParent = schema.parent?.['x-component-props']?.['openSize'];
const footerSchema = schema.reduceProperties((buf, s) => { const finalOpenSize = openSizeFromParent || openSize;
if (s['x-component'] === footerNodeName) { const footerSchema = schema.reduceProperties((buf, s) => {
return s; if (s['x-component'] === footerNodeName) {
} return s;
return buf; }
}); return buf;
return ( });
<> return (
{createPortal( <>
<div {createPortal(
onClick={(e) => { <div
e.stopPropagation(); onClick={(e) => {
}} e.stopPropagation();
> }}
<Drawer
width={openSizeWidthMap.get(finalOpenSize)}
title={field.title}
{...others}
destroyOnClose
open={visible}
onClose={() => setVisible(false, true)}
className={classNames(
others.className,
css`
&.nb-action-popup {
.ant-drawer-header {
display: none;
}
.ant-drawer-body {
padding-top: 14px;
}
.ant-drawer-content {
background: #f0f2f5;
}
}
&.nb-record-picker-selector {
.nb-block-item {
margin-bottom: 24px;
.general-schema-designer {
top: -8px;
bottom: -8px;
left: -8px;
right: -8px;
}
}
}
`,
)}
footer={
footerSchema && (
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
.ant-btn {
margin-right: 8px;
}
`}
>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === footerNodeName;
}}
/>
</div>
)
}
> >
<RecursionField <Drawer
basePath={field.address} width={openSizeWidthMap.get(finalOpenSize)}
schema={schema} title={field.title}
onlyRenderProperties {...others}
filterProperties={(s) => { destroyOnClose
return s['x-component'] !== footerNodeName; open={visible}
}} onClose={() => setVisible(false, true)}
/> className={classNames(
</Drawer> others.className,
</div>, css`
document.body, &.nb-action-popup {
)} .ant-drawer-header {
</> display: none;
); }
});
ActionDrawer.Footer = observer(() => { .ant-drawer-body {
const field = useField(); padding-top: 14px;
const schema = useFieldSchema(); }
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
}); .ant-drawer-content {
background: #f0f2f5;
}
}
&.nb-record-picker-selector {
.nb-block-item {
margin-bottom: 24px;
.general-schema-designer {
top: -8px;
bottom: -8px;
left: -8px;
right: -8px;
}
}
}
`,
)}
footer={
footerSchema && (
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
.ant-btn {
margin-right: 8px;
}
`}
>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === footerNodeName;
}}
/>
</div>
)
}
>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] !== footerNodeName;
}}
/>
</Drawer>
</div>,
document.body,
)}
</>
);
},
{ displayName: 'ActionDrawer' },
);
ActionDrawer.Footer = observer(
() => {
const field = useField();
const schema = useFieldSchema();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
},
{ displayName: 'ActionDrawer.Footer' },
);
export default ActionDrawer; export default ActionDrawer;

View File

@ -3,6 +3,9 @@ import React from 'react';
import Action from './Action'; import Action from './Action';
import { ComposedAction } from './types'; import { ComposedAction } from './types';
export const ActionLink: ComposedAction = observer((props: any) => { export const ActionLink: ComposedAction = observer(
return <Action {...props} component={'a'} className={'nb-action-link'} />; (props: any) => {
}); return <Action {...props} component={'a'} className={'nb-action-link'} />;
},
{ displayName: 'ActionLink' },
);

View File

@ -12,104 +12,110 @@ const openSizeWidthMap = new Map<OpenSize, string>([
['middle', '60%'], ['middle', '60%'],
['large', '80%'], ['large', '80%'],
]); ]);
export const ActionModal: ComposedActionDrawer<ModalProps> = observer((props) => { export const ActionModal: ComposedActionDrawer<ModalProps> = observer(
const { footerNodeName = 'Action.Modal.Footer', width, ...others } = props; (props) => {
const { visible, setVisible, openSize = 'large' } = useActionContext(); const { footerNodeName = 'Action.Modal.Footer', width, ...others } = props;
const actualWidth = width ?? openSizeWidthMap.get(openSize); const { visible, setVisible, openSize = 'large' } = useActionContext();
const schema = useFieldSchema(); const actualWidth = width ?? openSizeWidthMap.get(openSize);
const field = useField(); const schema = useFieldSchema();
const footerSchema = schema.reduceProperties((buf, s) => { const field = useField();
if (s['x-component'] === footerNodeName) { const footerSchema = schema.reduceProperties((buf, s) => {
return s; if (s['x-component'] === footerNodeName) {
} return s;
return buf; }
}); return buf;
return ( });
<> return (
{createPortal( <>
<div {createPortal(
onClick={(e) => { <div
e.stopPropagation(); onClick={(e) => {
}} e.stopPropagation();
> }}
<Modal
width={actualWidth}
title={field.title}
{...(others as ModalProps)}
destroyOnClose
visible={visible}
onCancel={() => setVisible(false, true)}
className={classNames(
others.className,
css`
&.nb-action-popup {
.ant-modal-header {
display: none;
}
.ant-modal-body {
padding-top: 16px;
}
.ant-modal-body {
background: #f0f2f5;
}
.ant-modal-close-x {
width: 32px;
height: 32px;
line-height: 32px;
}
}
`,
)}
footer={
footerSchema ? (
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
.ant-btn {
margin-right: 8px;
}
`}
>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === footerNodeName;
}}
/>
</div>
) : (
false
)
}
> >
<RecursionField <Modal
basePath={field.address} width={actualWidth}
schema={schema} title={field.title}
onlyRenderProperties {...(others as ModalProps)}
filterProperties={(s) => { destroyOnClose
return s['x-component'] !== footerNodeName; visible={visible}
}} onCancel={() => setVisible(false, true)}
/> className={classNames(
</Modal> others.className,
</div>, css`
document.body, &.nb-action-popup {
)} .ant-modal-header {
</> display: none;
); }
});
ActionModal.Footer = observer(() => { .ant-modal-body {
const field = useField(); padding-top: 16px;
const schema = useFieldSchema(); }
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
}); .ant-modal-body {
background: #f0f2f5;
}
.ant-modal-close-x {
width: 32px;
height: 32px;
line-height: 32px;
}
}
`,
)}
footer={
footerSchema ? (
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
.ant-btn {
margin-right: 8px;
}
`}
>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === footerNodeName;
}}
/>
</div>
) : (
false
)
}
>
<RecursionField
basePath={field.address}
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] !== footerNodeName;
}}
/>
</Modal>
</div>,
document.body,
)}
</>
);
},
{ displayName: 'ActionModal' },
);
ActionModal.Footer = observer(
() => {
const field = useField();
const schema = useFieldSchema();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
},
{ displayName: 'ActionModal.Footer' },
);
export default ActionModal; export default ActionModal;

View File

@ -10,65 +10,71 @@ const useScope = (key: string) => {
return scope[key]; return scope[key];
}; };
export const ActionPage: ComposedActionDrawer = observer((props: any) => { export const ActionPage: ComposedActionDrawer = observer(
const { footerNodeName = 'Action.Page.Footer', ...others } = props; (props: any) => {
const { containerRefKey, visible, setVisible } = useActionContext(); const { footerNodeName = 'Action.Page.Footer', ...others } = props;
const containerRef = useScope(containerRefKey); const { containerRefKey, visible, setVisible } = useActionContext();
const schema = useFieldSchema(); const containerRef = useScope(containerRefKey);
const field = useField(); const schema = useFieldSchema();
const footerSchema = schema.reduceProperties((buf, s) => { const field = useField();
if (s['x-component'] === footerNodeName) { const footerSchema = schema.reduceProperties((buf, s) => {
return s; if (s['x-component'] === footerNodeName) {
} return s;
return buf; }
}); return buf;
return ( });
<> return (
{containerRef?.current && <>
visible && {containerRef?.current &&
createPortal( visible &&
<div> createPortal(
<RecursionField <div>
basePath={field.address} <RecursionField
schema={schema} basePath={field.address}
onlyRenderProperties schema={schema}
filterProperties={(s) => { onlyRenderProperties
return s['x-component'] !== footerNodeName; filterProperties={(s) => {
}} return s['x-component'] !== footerNodeName;
/> }}
{footerSchema && ( />
<div {footerSchema && (
className={css` <div
display: flex; className={css`
/* justify-content: flex-end; */ display: flex;
/* flex-direction: row-reverse; */ /* justify-content: flex-end; */
width: 100%; /* flex-direction: row-reverse; */
.ant-btn { width: 100%;
margin-right: 8px; .ant-btn {
} margin-right: 8px;
`} }
> `}
<RecursionField >
basePath={field.address} <RecursionField
schema={schema} basePath={field.address}
onlyRenderProperties schema={schema}
filterProperties={(s) => { onlyRenderProperties
return s['x-component'] === footerNodeName; filterProperties={(s) => {
}} return s['x-component'] === footerNodeName;
/> }}
</div> />
)} </div>
</div>, )}
containerRef?.current, </div>,
)} containerRef?.current,
</> )}
); </>
}); );
},
{ displayName: 'ActionPage' },
);
ActionPage.Footer = observer(() => { ActionPage.Footer = observer(
const field = useField(); () => {
const schema = useFieldSchema(); const field = useField();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />; const schema = useFieldSchema();
}); return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
},
{ displayName: 'ActionPage.Footer' },
);
export default ActionPage; export default ActionPage;

View File

@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react'; import { observer, RecursionField, useField, useFieldSchema, useForm } from '@formily/react';
import { Button, Modal, Popover } from 'antd'; import { Button, Modal, Popover } from 'antd';
import classnames from 'classnames'; import classnames from 'classnames';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useActionContext } from '../..'; import { useActionContext } from '../..';
import { useDesignable } from '../../'; import { useDesignable } from '../../';
import { Icon } from '../../../icon'; import { Icon } from '../../../icon';
@ -64,139 +64,158 @@ export const actionDesignerCss = css`
} }
`; `;
export const Action: ComposedAction = observer((props: any) => { export const Action: ComposedAction = observer(
const { (props: any) => {
popover, const {
confirm, popover,
openMode: om, confirm,
containerRefKey, openMode: om,
component, containerRefKey,
useAction = useA, component,
className, useAction = useA,
icon, className,
title, icon,
...others title,
} = props; ...others
const { onClick } = useProps(props); } = props;
const [visible, setVisible] = useState(false); const { onClick } = useProps(props);
const [formValueChanged, setFormValueChanged] = useState(false); const [visible, setVisible] = useState(false);
const Designer = useDesigner(); const [formValueChanged, setFormValueChanged] = useState(false);
const field = useField<any>(); const Designer = useDesigner();
const { run } = useAction(); const field = useField<any>();
const fieldSchema = useFieldSchema(); const { run } = useAction();
const compile = useCompile(); const fieldSchema = useFieldSchema();
const form = useForm(); const compile = useCompile();
const values = useRecord(); const form = useForm();
const designerProps = fieldSchema['x-designer-props']; const values = useRecord();
const openMode = fieldSchema?.['x-component-props']?.['openMode']; const designerProps = fieldSchema['x-designer-props'];
const disabled = form.disabled || field.disabled || props.disabled; const openMode = fieldSchema?.['x-component-props']?.['openMode'];
const openSize = fieldSchema?.['x-component-props']?.['openSize']; const disabled = form.disabled || field.disabled || props.disabled;
const linkageRules = fieldSchema?.['x-linkage-rules'] || []; const openSize = fieldSchema?.['x-component-props']?.['openSize'];
const { designable } = useDesignable(); const linkageRules = fieldSchema?.['x-linkage-rules'] || [];
const tarComponent = useComponent(component) || component; const { designable } = useDesignable();
useEffect(() => { const tarComponent = useComponent(component) || component;
field.linkageProperty = {}; const renderTitle = useMemo(() => {
linkageRules if (title) {
.filter((k) => !k.disabled) return title;
.forEach((v) => { }
return v.actions?.map((h) => { if (fieldSchema.title) {
linkageAction(h.operator, field, v.condition, values); return compile(fieldSchema.title);
}); }
});
}, [linkageRules, values, designable]);
const renderButton = () => {
if (!designable && field?.data?.hidden) {
return null; return null;
} }, [compile, fieldSchema.title, title]);
return (
<SortableItem useEffect(() => {
{...others} field.linkageProperty = {};
loading={field?.data?.loading} linkageRules
icon={<Icon type={icon} />} .filter((k) => !k.disabled)
disabled={disabled} .forEach((v) => {
style={{ return v.actions?.map((h) => {
opacity: designable && field?.data?.hidden && 0.1, linkageAction(h.operator, field, v.condition, values);
}} });
onClick={(e: React.MouseEvent) => { });
if (!disabled) { }, [linkageRules, values, designable]);
e.preventDefault();
e.stopPropagation(); const renderButton = () => {
const onOk = () => { if (!designable && field?.data?.hidden) {
onClick?.(e); return null;
setVisible(true); }
run(); return (
}; <SortableItem
if (confirm) { {...others}
Modal.confirm({ loading={field?.data?.loading}
...confirm, icon={<Icon type={icon} />}
onOk, disabled={disabled}
}); style={{
} else { opacity: designable && field?.data?.hidden && 0.1,
onOk(); }}
onClick={(e: React.MouseEvent) => {
if (!disabled) {
e.preventDefault();
e.stopPropagation();
const onOk = () => {
onClick?.(e);
setVisible(true);
run();
};
if (confirm) {
Modal.confirm({
...confirm,
onOk,
});
} else {
onOk();
}
} }
} }}
component={tarComponent || Button}
className={classnames(className, actionDesignerCss)}
>
{renderTitle}
<Designer {...designerProps} />
</SortableItem>
);
};
return (
<ActionContext.Provider
value={{
button: renderButton(),
visible,
setVisible,
formValueChanged,
setFormValueChanged,
openMode,
openSize,
containerRefKey,
fieldSchema,
}} }}
component={tarComponent || Button}
className={classnames(className, actionDesignerCss)}
> >
{title || compile(fieldSchema.title)} {popover && <RecursionField basePath={field.address} onlyRenderProperties schema={fieldSchema} />}
<Designer {...designerProps} /> {!popover && renderButton()}
</SortableItem> {!popover && props.children}
</ActionContext.Provider>
); );
}; },
{ displayName: 'Action' },
);
return ( Action.Popover = observer(
<ActionContext.Provider (props) => {
value={{ const { button, visible, setVisible } = useActionContext();
button: renderButton(), return (
visible, <Popover
setVisible, {...props}
formValueChanged, destroyTooltipOnHide
setFormValueChanged, open={visible}
openMode, onOpenChange={(visible) => {
openSize, setVisible(visible);
containerRefKey, }}
fieldSchema, content={props.children}
}} >
> {button}
{popover && <RecursionField basePath={field.address} onlyRenderProperties schema={fieldSchema} />} </Popover>
{!popover && renderButton()} );
{!popover && props.children} },
</ActionContext.Provider> { displayName: 'Action.Popover' },
); );
});
Action.Popover = observer((props) => { Action.Popover.Footer = observer(
const { button, visible, setVisible } = useActionContext(); (props) => {
return ( return (
<Popover <div
{...props} className={css`
destroyTooltipOnHide display: flex;
open={visible} justify-content: flex-end;
onOpenChange={(visible) => { width: 100%;
setVisible(visible); `}
}} >
content={props.children} {props.children}
> </div>
{button} );
</Popover> },
); { displayName: 'Action.Popover.Footer' },
}); );
Action.Popover.Footer = observer((props) => {
return (
<div
className={css`
display: flex;
justify-content: flex-end;
width: 100%;
`}
>
{props.children}
</div>
);
});
Action.Link = ActionLink; Action.Link = ActionLink;
Action.Designer = ActionDesigner; Action.Designer = ActionDesigner;

View File

@ -6,40 +6,41 @@ import { useSchemaInitializer } from '../../../schema-initializer';
import { DndContext } from '../../common'; import { DndContext } from '../../common';
import { useDesignable, useProps } from '../../hooks'; import { useDesignable, useProps } from '../../hooks';
export const ActionBar = observer((props: any) => { export const ActionBar = observer(
const { layout = 'tow-columns', style, spaceProps, ...others } = useProps(props); (props: any) => {
const fieldSchema = useFieldSchema(); const { layout = 'tow-columns', style, spaceProps, ...others } = useProps(props);
const { InitializerComponent } = useSchemaInitializer(fieldSchema['x-initializer']); const fieldSchema = useFieldSchema();
const { designable } = useDesignable(); const { InitializerComponent } = useSchemaInitializer(fieldSchema['x-initializer']);
if (layout === 'one-column') { const { designable } = useDesignable();
if (layout === 'one-column') {
return (
<DndContext>
<div
style={{ display: 'flex', alignItems: 'center', ...style }}
{...others}
className={cx(others.className, 'nb-action-bar')}
>
{props.children && (
<div style={{ marginRight: 8 }}>
<Space {...spaceProps}>
{fieldSchema.mapProperties((schema, key) => {
return <RecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</div>
)}
<InitializerComponent />
</div>
</DndContext>
);
}
const hasActions = Object.keys(fieldSchema.properties ?? {}).length > 0;
return ( return (
<DndContext> <div
<div style={
style={{ display: 'flex', alignItems: 'center', ...style }} !designable && !hasActions
{...others} ? undefined
className={cx(others.className, 'nb-action-bar')} : {
>
{props.children && (
<div style={{ marginRight: 8 }}>
<Space {...spaceProps}>
{fieldSchema.mapProperties((schema, key) => {
return <RecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</div>
)}
<InitializerComponent />
</div>
</DndContext>
);
}
const hasActions = Object.keys(fieldSchema.properties ?? {}).length > 0;
return (
<div
style={
!designable && !hasActions
? undefined
: {
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
@ -47,38 +48,40 @@ export const ActionBar = observer((props: any) => {
flexShrink: 0, flexShrink: 0,
...style, ...style,
} }
} }
{...others} {...others}
className={cx(others.className, 'nb-action-bar')} className={cx(others.className, 'nb-action-bar')}
>
<div
className={css`
.ant-space:last-child {
margin-left: 8px;
}
`}
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}
> >
<DndContext> <div
<Space {...spaceProps}> className={css`
{fieldSchema.mapProperties((schema, key) => { .ant-space:last-child {
if (schema['x-align'] !== 'left') { margin-left: 8px;
return null; }
} `}
return <RecursionField key={key} name={key} schema={schema} />; style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}
})} >
</Space> <DndContext>
<Space {...spaceProps}> <Space {...spaceProps}>
{fieldSchema.mapProperties((schema, key) => { {fieldSchema.mapProperties((schema, key) => {
if (schema['x-align'] === 'left') { if (schema['x-align'] !== 'left') {
return null; return null;
} }
return <RecursionField key={key} name={key} schema={schema} />; return <RecursionField key={key} name={key} schema={schema} />;
})} })}
</Space> </Space>
</DndContext> <Space {...spaceProps}>
{fieldSchema.mapProperties((schema, key) => {
if (schema['x-align'] === 'left') {
return null;
}
return <RecursionField key={key} name={key} schema={schema} />;
})}
</Space>
</DndContext>
</div>
<InitializerComponent />
</div> </div>
<InitializerComponent /> );
</div> },
); { displayName: 'ActionBar' },
}); );

View File

@ -18,30 +18,33 @@ const useCloseAction = () => {
}; };
}; };
const Editable = observer((props) => { const Editable = observer(
const field = useField<Field>(); (props) => {
const schema = useFieldSchema(); const field = useField<Field>();
return ( const schema = useFieldSchema();
<div> return (
<div style={{ marginBottom: 24 }}> <div>
<div style={{ marginBottom: 24 }}>
<Select
value={field.componentProps.openMode} <Select
style={{ width: 100 }} value={field.componentProps.openMode}
onChange={(value) => { style={{ width: 100 }}
field.componentProps.openMode = value; onChange={(value) => {
schema['x-component-props']['openMode'] = value; field.componentProps.openMode = value;
}} schema['x-component-props']['openMode'] = value;
> }}
<Select.Option value={'drawer'}>Drawer</Select.Option> >
<Select.Option value={'modal'}>Modal</Select.Option> <Select.Option value={'drawer'}>Drawer</Select.Option>
<Select.Option value={'page'}>Page</Select.Option> <Select.Option value={'modal'}>Modal</Select.Option>
</Select> <Select.Option value={'page'}>Page</Select.Option>
</Select>
</div>
{props.children}
</div> </div>
{props.children} );
</div> },
); { displayName: 'Editable' },
}); );
const schema: ISchema = { const schema: ISchema = {
type: 'object', type: 'object',

View File

@ -71,52 +71,55 @@ function TypeSelect(props) {
); );
} }
const EditOptions = observer((props) => { const EditOptions = observer(
const record = ArrayTable.useRecord(); (props) => {
const field = useField<Field>(); const record = ArrayTable.useRecord();
console.log(record.type); const field = useField<Field>();
return ( console.log(record.type);
<div> return (
<a <div>
onClick={() => { <a
FormDrawer('Pop-up form', () => { onClick={() => {
return ( FormDrawer('Pop-up form', () => {
<FormLayout labelCol={6} wrapperCol={10}> return (
<SchemaField <FormLayout labelCol={6} wrapperCol={10}>
schema={{ <SchemaField
type: 'object', schema={{
properties: record.type ? types[record.type] || {} : {}, type: 'object',
}} properties: record.type ? types[record.type] || {} : {},
/> }}
<FormDrawer.Footer> />
<FormButtonGroup align="right"> <FormDrawer.Footer>
<Submit <FormButtonGroup align="right">
onSubmit={() => { <Submit
return new Promise((resolve) => { onSubmit={() => {
setTimeout(resolve, 1000); return new Promise((resolve) => {
}); setTimeout(resolve, 1000);
}} });
> }}
Submit >
</Submit> Submit
</FormButtonGroup> </Submit>
</FormDrawer.Footer> </FormButtonGroup>
</FormLayout> </FormDrawer.Footer>
); </FormLayout>
}) );
.open({
initialValues: field.value,
}) })
.then((values) => { .open({
field.value = values; initialValues: field.value,
}); })
}} .then((values) => {
> field.value = values;
Edit });
</a> }}
</div> >
); Edit
}); </a>
</div>
);
},
{ displayName: 'EditOptions' },
);
const SchemaField = createSchemaField({ const SchemaField = createSchemaField({
components: { components: {

View File

@ -4,63 +4,66 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useCollectionManager } from '../../../collection-manager'; import { useCollectionManager } from '../../../collection-manager';
import { AssociationFieldContext } from './context'; import { AssociationFieldContext } from './context';
export const AssociationFieldProvider = observer((props) => { export const AssociationFieldProvider = observer(
const field = useField<Field>(); (props) => {
const { getCollectionJoinField, getCollection } = useCollectionManager(); const field = useField<Field>();
const fieldSchema = useFieldSchema(); const { getCollectionJoinField, getCollection } = useCollectionManager();
const allowMultiple = fieldSchema['x-component-props']?.multiple !== false; const fieldSchema = useFieldSchema();
const allowDissociate = fieldSchema['x-component-props']?.allowDissociate !== false; const allowMultiple = fieldSchema['x-component-props']?.multiple !== false;
const allowDissociate = fieldSchema['x-component-props']?.allowDissociate !== false;
const collectionField = useMemo( const collectionField = useMemo(
() => getCollectionJoinField(fieldSchema['x-collection-field']), () => getCollectionJoinField(fieldSchema['x-collection-field']),
[fieldSchema['x-collection-field'], fieldSchema.name], [fieldSchema['x-collection-field'], fieldSchema.name],
); );
const isFileCollection = useMemo( const isFileCollection = useMemo(
() => getCollection(collectionField?.target)?.template === 'file', () => getCollection(collectionField?.target)?.template === 'file',
[fieldSchema['x-collection-field']], [fieldSchema['x-collection-field']],
); );
const currentMode = useMemo( const currentMode = useMemo(
() => fieldSchema['x-component-props']?.mode || (isFileCollection ? 'FileManager' : 'Select'), () => fieldSchema['x-component-props']?.mode || (isFileCollection ? 'FileManager' : 'Select'),
[fieldSchema['x-component-props']?.mode], [fieldSchema['x-component-props']?.mode],
); );
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { useEffect(() => {
setLoading(true); setLoading(true);
if (!collectionField) { if (!collectionField) {
setLoading(false); setLoading(false);
return; return;
} }
if (field.value !== null && field.value !== undefined) { if (field.value !== null && field.value !== undefined) {
// Nester 子表单时,如果没数据初始化一个 [null] 的占位 // Nester 子表单时,如果没数据初始化一个 [null] 的占位
if (currentMode === 'Nester' && Array.isArray(field.value)) { if (currentMode === 'Nester' && Array.isArray(field.value)) {
if (field.value.length === 0 && ['belongsToMany', 'hasMany'].includes(collectionField.type)) { if (field.value.length === 0 && ['belongsToMany', 'hasMany'].includes(collectionField.type)) {
field.value = [{}];
}
}
setLoading(false);
return;
}
if (currentMode === 'Nester') {
if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
field.value = {};
} else if (['belongsToMany', 'hasMany'].includes(collectionField.type)) {
field.value = [{}]; field.value = [{}];
} }
} }
setLoading(false); setLoading(false);
return; }, [currentMode, collectionField, field.value]);
}
if (currentMode === 'Nester') {
if (['belongsTo', 'hasOne'].includes(collectionField.type)) {
field.value = {};
} else if (['belongsToMany', 'hasMany'].includes(collectionField.type)) {
field.value = [{}];
}
}
setLoading(false);
}, [currentMode, collectionField, field.value]);
if (loading) { if (loading) {
return null; return null;
} }
return collectionField ? ( return collectionField ? (
<AssociationFieldContext.Provider <AssociationFieldContext.Provider
value={{ options: collectionField, field, allowMultiple, allowDissociate, currentMode }} value={{ options: collectionField, field, allowMultiple, allowDissociate, currentMode }}
> >
{props.children} {props.children}
</AssociationFieldContext.Provider> </AssociationFieldContext.Provider>
) : null; ) : null;
}); },
{ displayName: 'AssociationFieldProvider' },
);

View File

@ -11,41 +11,44 @@ export type AssociationSelectProps<P = any> = RemoteSelectProps<P> & {
multiple?: boolean; multiple?: boolean;
}; };
const InternalAssociationSelect = observer((props: AssociationSelectProps) => { const InternalAssociationSelect = observer(
const { fieldNames, objectValue = true } = props; (props: AssociationSelectProps) => {
const field: any = useField(); const { fieldNames, objectValue = true } = props;
const fieldSchema = useFieldSchema(); const field: any = useField();
const service = useServiceOptions(props); const fieldSchema = useFieldSchema();
const isAllowAddNew = fieldSchema['x-add-new']; const service = useServiceOptions(props);
const value = Array.isArray(props.value) ? props.value.filter(Boolean) : props.value; const isAllowAddNew = fieldSchema['x-add-new'];
const value = Array.isArray(props.value) ? props.value.filter(Boolean) : props.value;
return ( return (
<div key={fieldSchema.name}> <div key={fieldSchema.name}>
<Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}> <Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
<RemoteSelect <RemoteSelect
style={{ width: '100%' }} style={{ width: '100%' }}
{...props} {...props}
objectValue={objectValue} objectValue={objectValue}
value={value} value={value}
service={service} service={service}
></RemoteSelect> ></RemoteSelect>
{isAllowAddNew && ( {isAllowAddNew && (
<RecordProvider record={null}> <RecordProvider record={null}>
<RecursionField <RecursionField
onlyRenderProperties onlyRenderProperties
basePath={field.address} basePath={field.address}
schema={fieldSchema} schema={fieldSchema}
filterProperties={(s) => { filterProperties={(s) => {
return s['x-component'] === 'Action'; return s['x-component'] === 'Action';
}} }}
/> />
</RecordProvider> </RecordProvider>
)} )}
</Input.Group> </Input.Group>
</div> </div>
); );
}); },
{ displayName: 'InternalAssociationSelect' },
);
interface AssociationSelectInterface { interface AssociationSelectInterface {
(props: any): React.ReactElement; (props: any): React.ReactElement;

View File

@ -12,50 +12,56 @@ import { InternalSubTable } from './InternalSubTable';
import { CreateRecordAction } from './components/CreateRecordAction'; import { CreateRecordAction } from './components/CreateRecordAction';
import { useAssociationFieldContext } from './hooks'; import { useAssociationFieldContext } from './hooks';
const EditableAssociationField = observer((props: any) => { const EditableAssociationField = observer(
const { multiple } = props; (props: any) => {
const field: Field = useField(); const { multiple } = props;
const form = useForm(); const field: Field = useField();
const fieldSchema = useFieldSchema(); const form = useForm();
const { options: collectionField, currentMode } = useAssociationFieldContext(); const fieldSchema = useFieldSchema();
const { options: collectionField, currentMode } = useAssociationFieldContext();
const useCreateActionProps = () => { const useCreateActionProps = () => {
const { onClick } = useCAP(); const { onClick } = useCAP();
const actionField: any = useField(); const actionField: any = useField();
return { return {
async onClick() { async onClick() {
await onClick(); await onClick();
const { data } = actionField.data?.data?.data || {}; const { data } = actionField.data?.data?.data || {};
if (data) { if (data) {
if (['m2m', 'o2m'].includes(collectionField?.interface) && multiple !== false) { if (['m2m', 'o2m'].includes(collectionField?.interface) && multiple !== false) {
const values = form.getValuesIn(field.path) || []; const values = form.getValuesIn(field.path) || [];
values.push(data); values.push(data);
form.setValuesIn(field.path, values); form.setValuesIn(field.path, values);
field.onInput(values); field.onInput(values);
} else { } else {
form.setValuesIn(field.path, data); form.setValuesIn(field.path, data);
field.onInput(data); field.onInput(data);
}
} }
} },
}, };
}; };
};
return ( return (
<SchemaComponentOptions scope={{ useCreateActionProps }} components={{ CreateRecordAction }}> <SchemaComponentOptions scope={{ useCreateActionProps }} components={{ CreateRecordAction }}>
{currentMode === 'Picker' && <InternalPicker {...props} />} {currentMode === 'Picker' && <InternalPicker {...props} />}
{currentMode === 'Nester' && <InternalNester {...props} />} {currentMode === 'Nester' && <InternalNester {...props} />}
{currentMode === 'Select' && <AssociationSelect {...props} />} {currentMode === 'Select' && <AssociationSelect {...props} />}
{currentMode === 'SubTable' && <InternalSubTable {...props} />} {currentMode === 'SubTable' && <InternalSubTable {...props} />}
{currentMode === 'FileManager' && <InternalFileManager {...props} />} {currentMode === 'FileManager' && <InternalFileManager {...props} />}
</SchemaComponentOptions> </SchemaComponentOptions>
); );
}); },
{ displayName: 'EditableAssociationField' },
);
export const Editable = observer((props) => { export const Editable = observer(
return ( (props) => {
<AssociationFieldProvider> return (
<EditableAssociationField {...props} /> <AssociationFieldProvider>
</AssociationFieldProvider> <EditableAssociationField {...props} />
); </AssociationFieldProvider>
}); );
},
{ displayName: 'Editable' },
);

View File

@ -60,138 +60,145 @@ const useTableSelectorProps = () => {
}; };
}; };
export const InternalPicker = observer((props: any) => { export const InternalPicker = observer(
const { value, multiple, onChange, quickUpload, selectFile, ...others } = props; (props: any) => {
const field: any = useField(); const { value, multiple, onChange, quickUpload, selectFile, ...others } = props;
const fieldNames = useFieldNames(props); const field: any = useField();
const [visibleSelector, setVisibleSelector] = useState(false); const fieldNames = useFieldNames(props);
const fieldSchema = useFieldSchema(); const [visibleSelector, setVisibleSelector] = useState(false);
const insertSelector = useInsertSchema('Selector'); const fieldSchema = useFieldSchema();
const { options: collectionField } = useAssociationFieldContext(); const insertSelector = useInsertSchema('Selector');
const compile = useCompile(); const { options: collectionField } = useAssociationFieldContext();
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); const compile = useCompile();
const isAllowAddNew = fieldSchema['x-add-new']; const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
const [selectedRows, setSelectedRows] = useState([]); const isAllowAddNew = fieldSchema['x-add-new'];
const options = useMemo(() => { const [selectedRows, setSelectedRows] = useState([]);
if (value && Object.keys(value).length > 0) { const options = useMemo(() => {
const opts = (Array.isArray(value) ? value : value ? [value] : []).filter(Boolean).map((option) => { if (value && Object.keys(value).length > 0) {
const label = option?.[fieldNames.label]; const opts = (Array.isArray(value) ? value : value ? [value] : []).filter(Boolean).map((option) => {
return { const label = option?.[fieldNames.label];
...option, return {
[fieldNames.label]: getLabelFormatValue(compile(labelUiSchema), compile(label)), ...option,
}; [fieldNames.label]: getLabelFormatValue(compile(labelUiSchema), compile(label)),
}); };
return opts; });
} return opts;
return []; }
}, [value, fieldNames?.label]); return [];
}, [value, fieldNames?.label]);
const pickerProps = { const pickerProps = {
size: 'small', size: 'small',
fieldNames, fieldNames,
multiple: multiple !== false && ['o2m', 'm2m'].includes(collectionField?.interface), multiple: multiple !== false && ['o2m', 'm2m'].includes(collectionField?.interface),
association: { association: {
target: collectionField?.target, target: collectionField?.target,
},
options,
onChange: props?.onChange,
selectedRows,
setSelectedRows,
collectionField,
};
const getValue = () => {
if (multiple == null) return null;
return Array.isArray(value) ? value.filter(Boolean)?.map((v) => v?.[fieldNames.value]) : value?.[fieldNames.value];
};
const getFilter = () => {
const targetKey = collectionField?.targetKey || 'id';
const list = options.map((option) => option[targetKey]).filter(Boolean);
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
return filter;
};
const usePickActionProps = () => {
const { setVisible } = useActionContext();
const { multiple, selectedRows, onChange, options, collectionField } = useContext(RecordPickerContext);
return {
onClick() {
if (multiple) {
onChange(unionBy(selectedRows, options, collectionField?.targetKey || 'id'));
} else {
onChange(selectedRows?.[0] || null);
}
setVisible(false);
}, },
options,
onChange: props?.onChange,
selectedRows,
setSelectedRows,
collectionField,
}; };
};
return ( const getValue = () => {
<> if (multiple == null) return null;
<Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}> return Array.isArray(value)
<div style={{ width: '100%' }}> ? value.filter(Boolean)?.map((v) => v?.[fieldNames.value])
<Select : value?.[fieldNames.value];
style={{ width: '100%' }} };
{...others} const getFilter = () => {
mode={multiple ? 'multiple' : props.mode} const targetKey = collectionField?.targetKey || 'id';
fieldNames={fieldNames} const list = options.map((option) => option[targetKey]).filter(Boolean);
onDropdownVisibleChange={(open) => { const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
insertSelector(schema.Selector); return filter;
setVisibleSelector(true); };
}} const usePickActionProps = () => {
allowClear const { setVisible } = useActionContext();
onChange={(changed: any) => { const { multiple, selectedRows, onChange, options, collectionField } = useContext(RecordPickerContext);
if (!changed) { return {
const value = multiple ? [] : null; onClick() {
onChange(value); if (multiple) {
setSelectedRows(value); onChange(unionBy(selectedRows, options, collectionField?.targetKey || 'id'));
} else if (Array.isArray(changed)) { } else {
if (!changed.length) { onChange(selectedRows?.[0] || null);
onChange([]); }
setSelectedRows([]); setVisible(false);
return; },
} };
const values = options?.filter((option) => changed.includes(option[fieldNames.value])); };
onChange(values); return (
setSelectedRows(values); <>
} <Input.Group compact style={{ display: 'flex', lineHeight: '32px' }}>
}} <div style={{ width: '100%' }}>
options={options} <Select
value={getValue()} style={{ width: '100%' }}
open={false} {...others}
/> mode={multiple ? 'multiple' : props.mode}
</div> fieldNames={fieldNames}
{isAllowAddNew && ( onDropdownVisibleChange={(open) => {
<RecordProvider record={null}> insertSelector(schema.Selector);
<RecursionField setVisibleSelector(true);
onlyRenderProperties
basePath={field.address}
schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] === 'Action';
}} }}
allowClear
onChange={(changed: any) => {
if (!changed) {
const value = multiple ? [] : null;
onChange(value);
setSelectedRows(value);
} else if (Array.isArray(changed)) {
if (!changed.length) {
onChange([]);
setSelectedRows([]);
return;
}
const values = options?.filter((option) => changed.includes(option[fieldNames.value]));
onChange(values);
setSelectedRows(values);
}
}}
options={options}
value={getValue()}
open={false}
/> />
</RecordProvider> </div>
)} {isAllowAddNew && (
</Input.Group> <RecordProvider record={null}>
<ActionContext.Provider value={{ openMode: 'drawer', visible: visibleSelector, setVisible: setVisibleSelector }}> <RecursionField
<RecordPickerProvider {...pickerProps}> onlyRenderProperties
<CollectionProvider name={collectionField?.target}> basePath={field.address}
<FormProvider> schema={fieldSchema}
<TableSelectorParamsProvider params={{ filter: getFilter() }}> filterProperties={(s) => {
<SchemaComponentOptions scope={{ usePickActionProps, useTableSelectorProps }}> return s['x-component'] === 'Action';
<RecursionField }}
onlyRenderProperties />
basePath={field.address} </RecordProvider>
schema={fieldSchema} )}
filterProperties={(s) => { </Input.Group>
return s['x-component'] === 'AssociationField.Selector'; <ActionContext.Provider
}} value={{ openMode: 'drawer', visible: visibleSelector, setVisible: setVisibleSelector }}
/> >
</SchemaComponentOptions> <RecordPickerProvider {...pickerProps}>
</TableSelectorParamsProvider> <CollectionProvider name={collectionField?.target}>
</FormProvider> <FormProvider>
</CollectionProvider> <TableSelectorParamsProvider params={{ filter: getFilter() }}>
</RecordPickerProvider> <SchemaComponentOptions scope={{ usePickActionProps, useTableSelectorProps }}>
</ActionContext.Provider> <RecursionField
</> onlyRenderProperties
); basePath={field.address}
}); schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] === 'AssociationField.Selector';
}}
/>
</SchemaComponentOptions>
</TableSelectorParamsProvider>
</FormProvider>
</CollectionProvider>
</RecordPickerProvider>
</ActionContext.Provider>
</>
);
},
{ displayName: 'InternalPicker' },
);

View File

@ -22,94 +22,97 @@ const toValue = (value, placeholder) => {
} }
return value; return value;
}; };
export const ReadPrettyInternalViewer: React.FC = observer((props: any) => { export const ReadPrettyInternalViewer: React.FC = observer(
const fieldSchema = useFieldSchema(); (props: any) => {
const recordCtx = useRecord(); const fieldSchema = useFieldSchema();
const { enableLink } = fieldSchema['x-component-props']; const recordCtx = useRecord();
// value 做了转换,但 props.value 和原来 useField().value 的值不一致 const { enableLink } = fieldSchema['x-component-props'];
const field = useField(); // value 做了转换,但 props.value 和原来 useField().value 的值不一致
const fieldNames = useFieldNames(props); const field = useField();
const [visible, setVisible] = useState(false); const fieldNames = useFieldNames(props);
const insertViewer = useInsertSchema('Viewer'); const [visible, setVisible] = useState(false);
const { options: collectionField } = useAssociationFieldContext(); const insertViewer = useInsertSchema('Viewer');
const [record, setRecord] = useState({}); const { options: collectionField } = useAssociationFieldContext();
const compile = useCompile(); const [record, setRecord] = useState({});
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); const compile = useCompile();
const { snapshot } = useActionContext(); const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>(); const { snapshot } = useActionContext();
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
const renderRecords = () => const renderRecords = () =>
toArr(props.value).map((record, index, arr) => { toArr(props.value).map((record, index, arr) => {
const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A'); const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A');
const text = getLabelFormatValue(compile(labelUiSchema), val, true); const text = getLabelFormatValue(compile(labelUiSchema), val, true);
return ( return (
<Fragment key={`${record.id}_${index}`}> <Fragment key={`${record.id}_${index}`}>
<span> <span>
{snapshot ? ( {snapshot ? (
text text
) : enableLink !== false ? ( ) : enableLink !== false ? (
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
insertViewer(schema.Viewer); insertViewer(schema.Viewer);
setVisible(true); setVisible(true);
setRecord(record); setRecord(record);
ellipsisWithTooltipRef?.current?.setPopoverVisible(false); ellipsisWithTooltipRef?.current?.setPopoverVisible(false);
}} }}
> >
{text} {text}
</a> </a>
) : ( ) : (
text text
)} )}
</span> </span>
{index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null} {index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null}
</Fragment> </Fragment>
); );
}); });
const renderWithoutTableFieldResourceProvider = () => ( const renderWithoutTableFieldResourceProvider = () => (
<WithoutTableFieldResource.Provider value={true}> <WithoutTableFieldResource.Provider value={true}>
<FormProvider> <FormProvider>
<RecursionField <RecursionField
schema={fieldSchema} schema={fieldSchema}
onlyRenderProperties onlyRenderProperties
basePath={field.address} basePath={field.address}
filterProperties={(s) => { filterProperties={(s) => {
return s['x-component'] === 'AssociationField.Viewer'; return s['x-component'] === 'AssociationField.Viewer';
}} }}
/> />
</FormProvider> </FormProvider>
</WithoutTableFieldResource.Provider> </WithoutTableFieldResource.Provider>
);
const renderRecordProvider = () => {
const collectionFieldNames = fieldSchema?.['x-collection-field']?.split('.');
return collectionFieldNames && collectionFieldNames.length > 2 ? (
<RecordProvider record={recordCtx[collectionFieldNames[1]]}>
<RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
</RecordProvider>
) : (
<RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
); );
};
return ( const renderRecordProvider = () => {
<div> const collectionFieldNames = fieldSchema?.['x-collection-field']?.split('.');
<BlockAssociationContext.Provider value={`${collectionField?.collectionName}.${collectionField?.name}`}>
<CollectionProvider name={collectionField?.target ?? collectionField?.targetCollection}> return collectionFieldNames && collectionFieldNames.length > 2 ? (
<EllipsisWithTooltip ellipsis={true} ref={ellipsisWithTooltipRef}> <RecordProvider record={recordCtx[collectionFieldNames[1]]}>
{renderRecords()} <RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
</EllipsisWithTooltip> </RecordProvider>
<ActionContext.Provider ) : (
value={{ visible, setVisible, openMode: 'drawer', snapshot: collectionField?.interface === 'snapshot' }} <RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
> );
{renderRecordProvider()} };
</ActionContext.Provider>
</CollectionProvider> return (
</BlockAssociationContext.Provider> <div>
</div> <BlockAssociationContext.Provider value={`${collectionField?.collectionName}.${collectionField?.name}`}>
); <CollectionProvider name={collectionField?.target ?? collectionField?.targetCollection}>
}); <EllipsisWithTooltip ellipsis={true} ref={ellipsisWithTooltipRef}>
{renderRecords()}
</EllipsisWithTooltip>
<ActionContext.Provider
value={{ visible, setVisible, openMode: 'drawer', snapshot: collectionField?.interface === 'snapshot' }}
>
{renderRecordProvider()}
</ActionContext.Provider>
</CollectionProvider>
</BlockAssociationContext.Provider>
</div>
);
},
{ displayName: 'ReadPrettyInternalViewer' },
);

View File

@ -25,92 +25,95 @@ const ToOneNester = (props) => {
return <Card bordered={true}>{props.children}</Card>; return <Card bordered={true}>{props.children}</Card>;
}; };
const ToManyNester = observer((props) => { const ToManyNester = observer(
const fieldSchema = useFieldSchema(); (props) => {
const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>(); const fieldSchema = useFieldSchema();
const { t } = useTranslation(); const { options, field, allowMultiple, allowDissociate } = useAssociationFieldContext<ArrayField>();
return (field.value || []).length > 0 ? ( const { t } = useTranslation();
<Card return (field.value || []).length > 0 ? (
bordered={true} <Card
style={{ position: 'relative' }} bordered={true}
className={css` style={{ position: 'relative' }}
> .ant-card-body > .ant-divider:last-child { className={css`
display: none; > .ant-card-body > .ant-divider:last-child {
} display: none;
`} }
> `}
{(field.value || []).map((value, index) => { >
let allowed = allowDissociate; {(field.value || []).map((value, index) => {
if (!allowDissociate) { let allowed = allowDissociate;
allowed = !value?.[options.targetKey]; if (!allowDissociate) {
} allowed = !value?.[options.targetKey];
return ( }
<> return (
<div style={{ textAlign: 'right' }}> <>
{field.editable && allowMultiple && ( <div style={{ textAlign: 'right' }}>
<Tooltip key={'add'} title={t('Add new')}> {field.editable && allowMultiple && (
<PlusOutlined <Tooltip key={'add'} title={t('Add new')}>
style={{ zIndex: 1000, marginRight: '10px', color: '#a8a3a3' }} <PlusOutlined
onClick={() => { style={{ zIndex: 1000, marginRight: '10px', color: '#a8a3a3' }}
action(() => { onClick={() => {
if (!Array.isArray(field.value)) { action(() => {
field.value = []; if (!Array.isArray(field.value)) {
} field.value = [];
spliceArrayState(field as any, { }
startIndex: index + 1, spliceArrayState(field as any, {
insertCount: 1, startIndex: index + 1,
insertCount: 1,
});
field.value.splice(index + 1, 0, {});
return field.onInput(field.value);
}); });
field.value.splice(index + 1, 0, {}); }}
return field.onInput(field.value); />
}); </Tooltip>
}} )}
/> {!field.readPretty && allowed && (
</Tooltip> <Tooltip key={'remove'} title={t('Remove')}>
)} <CloseOutlined
{!field.readPretty && allowed && ( style={{ zIndex: 1000, color: '#a8a3a3' }}
<Tooltip key={'remove'} title={t('Remove')}> onClick={() => {
<CloseOutlined action(() => {
style={{ zIndex: 1000, color: '#a8a3a3' }} spliceArrayState(field as any, {
onClick={() => { startIndex: index,
action(() => { deleteCount: 1,
spliceArrayState(field as any, { });
startIndex: index, field.value.splice(index, 1);
deleteCount: 1, return field.onInput(field.value);
}); });
field.value.splice(index, 1); }}
return field.onInput(field.value); />
}); </Tooltip>
}} )}
/> </div>
</Tooltip> <RecursionField onlyRenderProperties basePath={field.address.concat(index)} schema={fieldSchema} />
)} <Divider />
</div> </>
<RecursionField onlyRenderProperties basePath={field.address.concat(index)} schema={fieldSchema} /> );
<Divider /> })}
</> </Card>
); ) : (
})} <>
</Card> {field.editable && allowMultiple && (
) : ( <Tooltip key={'add'} title={t('Add new')}>
<> <Button
{field.editable && allowMultiple && ( type={'default'}
<Tooltip key={'add'} title={t('Add new')}> className={css`
<Button border: 1px solid #f0f0f0 !important;
type={'default'} box-shadow: none;
className={css` `}
border: 1px solid #f0f0f0 !important; block
box-shadow: none; icon={<PlusOutlined />}
`} onClick={() => {
block const result = field.value;
icon={<PlusOutlined />} result.push({});
onClick={() => { field.value = result;
const result = field.value; }}
result.push({}); ></Button>
field.value = result; </Tooltip>
}} )}
></Button> </>
</Tooltip> );
)} },
</> { displayName: 'ToManyNester' },
); );
});

View File

@ -7,23 +7,29 @@ import { InternalSubTable } from './InternalSubTable';
import { FileManageReadPretty } from './FileManager'; import { FileManageReadPretty } from './FileManager';
import { useAssociationFieldContext } from './hooks'; import { useAssociationFieldContext } from './hooks';
const ReadPrettyAssociationField = observer((props: any) => { const ReadPrettyAssociationField = observer(
const { currentMode } = useAssociationFieldContext(); (props: any) => {
const { currentMode } = useAssociationFieldContext();
return ( return (
<> <>
{['Select', 'Picker'].includes(currentMode) && <ReadPrettyInternalViewer {...props} />} {['Select', 'Picker'].includes(currentMode) && <ReadPrettyInternalViewer {...props} />}
{currentMode === 'Nester' && <InternalNester {...props} />} {currentMode === 'Nester' && <InternalNester {...props} />}
{currentMode === 'SubTable' && <InternalSubTable {...props} />} {currentMode === 'SubTable' && <InternalSubTable {...props} />}
{currentMode === 'FileManager' && <FileManageReadPretty {...props} />} {currentMode === 'FileManager' && <FileManageReadPretty {...props} />}
</> </>
); );
}); },
{ displayName: 'ReadPrettyAssociationField' },
);
export const ReadPretty = observer((props) => { export const ReadPretty = observer(
return ( (props) => {
<AssociationFieldProvider> return (
<ReadPrettyAssociationField {...props} /> <AssociationFieldProvider>
</AssociationFieldProvider> <ReadPrettyAssociationField {...props} />
); </AssociationFieldProvider>
}); );
},
{ displayName: 'ReadPretty' },
);

View File

@ -5,24 +5,27 @@ import React from 'react';
import { Table } from '../table-v2/Table'; import { Table } from '../table-v2/Table';
import { useAssociationFieldContext } from './hooks'; import { useAssociationFieldContext } from './hooks';
export const SubTable: any = observer((props) => { export const SubTable: any = observer(
const { field } = useAssociationFieldContext<ArrayField>(); (props) => {
return ( const { field } = useAssociationFieldContext<ArrayField>();
<div> return (
<Table size={'small'} showIndex field={field} pagination={false} /> <div>
{field.editable && ( <Table size={'small'} showIndex field={field} pagination={false} />
<Button {field.editable && (
type={'dashed'} <Button
block type={'dashed'}
style={{ marginTop: 12 }} block
onClick={() => { style={{ marginTop: 12 }}
field.value = field.value || []; onClick={() => {
field.value.push({}); field.value = field.value || [];
}} field.value.push({});
> }}
Add new >
</Button> Add new
)} </Button>
</div> )}
); </div>
}); );
},
{ displayName: 'SubTable' },
);

View File

@ -6,34 +6,37 @@ import { ActionContext, useActionContext } from '../../action';
import { useAssociationFieldContext, useInsertSchema } from '../hooks'; import { useAssociationFieldContext, useInsertSchema } from '../hooks';
import schema from '../schema'; import schema from '../schema';
export const CreateRecordAction = observer((props) => { export const CreateRecordAction = observer(
const field: any = useField(); (props) => {
const fieldSchema = useFieldSchema(); const field: any = useField();
const ctx = useActionContext(); const fieldSchema = useFieldSchema();
const insertAddNewer = useInsertSchema('AddNewer'); const ctx = useActionContext();
const { options: collectionField } = useAssociationFieldContext(); const insertAddNewer = useInsertSchema('AddNewer');
const [visibleAddNewer, setVisibleAddNewer] = useState(false); const { options: collectionField } = useAssociationFieldContext();
const [currentCollection, setCurrentCollection] = useState(collectionField?.target); const [visibleAddNewer, setVisibleAddNewer] = useState(false);
const addbuttonClick = (name) => { const [currentCollection, setCurrentCollection] = useState(collectionField?.target);
insertAddNewer(schema.AddNewer); const addbuttonClick = (name) => {
setVisibleAddNewer(true); insertAddNewer(schema.AddNewer);
setCurrentCollection(name); setVisibleAddNewer(true);
}; setCurrentCollection(name);
return ( };
<CollectionProvider name={collectionField?.target}> return (
<CreateAction {...props} onClick={(arg) => addbuttonClick(arg)} /> <CollectionProvider name={collectionField?.target}>
<ActionContext.Provider value={{ ...ctx, visible: visibleAddNewer, setVisible: setVisibleAddNewer }}> <CreateAction {...props} onClick={(arg) => addbuttonClick(arg)} />
<CollectionProvider name={currentCollection}> <ActionContext.Provider value={{ ...ctx, visible: visibleAddNewer, setVisible: setVisibleAddNewer }}>
<RecursionField <CollectionProvider name={currentCollection}>
onlyRenderProperties <RecursionField
basePath={field.address} onlyRenderProperties
schema={fieldSchema} basePath={field.address}
filterProperties={(s) => { schema={fieldSchema}
return s['x-component'] === 'AssociationField.AddNewer'; filterProperties={(s) => {
}} return s['x-component'] === 'AssociationField.AddNewer';
/> }}
</CollectionProvider> />
</ActionContext.Provider> </CollectionProvider>
</CollectionProvider> </ActionContext.Provider>
); </CollectionProvider>
}); );
},
{ displayName: 'CreateRecordAction' },
);

View File

@ -4,8 +4,11 @@ import { useFieldTitle } from '../../hooks';
import { RemoteSelect } from '../remote-select'; import { RemoteSelect } from '../remote-select';
import useServiceOptions from './useServiceOptions'; import useServiceOptions from './useServiceOptions';
export const ReadPretty = observer((props: any) => { export const ReadPretty = observer(
const service = useServiceOptions(props); (props: any) => {
useFieldTitle(); const service = useServiceOptions(props);
return <RemoteSelect.ReadPretty {...props} service={service}></RemoteSelect.ReadPretty>; useFieldTitle();
}); return <RemoteSelect.ReadPretty {...props} service={service}></RemoteSelect.ReadPretty>;
},
{ displayName: 'ReadPretty' },
);

View File

@ -3,15 +3,18 @@ import { uid } from '@formily/shared';
import { BlockItem, DndContext, DragHandler, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; import { BlockItem, DndContext, DragHandler, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import React from 'react'; import React from 'react';
const Block = observer((props) => { const Block = observer(
const fieldSchema = useFieldSchema(); (props) => {
return ( const fieldSchema = useFieldSchema();
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}> return (
Block {fieldSchema.name} <div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
<DragHandler /> Block {fieldSchema.name}
</div> <DragHandler />
); </div>
}); );
},
{ displayName: 'Block' },
);
export default function App() { export default function App() {
return ( return (

View File

@ -190,71 +190,74 @@ const CalendarRecordViewer = (props) => {
); );
}; };
export const Calendar: any = observer((props: any) => { export const Calendar: any = observer(
const { dataSource, fieldNames, showLunar, fixedBlock } = useProps(props); (props: any) => {
const [date, setDate] = useState<Date>(new Date()); const { dataSource, fieldNames, showLunar, fixedBlock } = useProps(props);
const [view, setView] = useState<(typeof Weeks)[number]>('month'); const [date, setDate] = useState<Date>(new Date());
const events = useEvents(dataSource, fieldNames, date, view); const [view, setView] = useState<(typeof Weeks)[number]>('month');
const [visible, setVisible] = useState(false); const events = useEvents(dataSource, fieldNames, date, view);
const [record, setRecord] = useState<any>({}); const [visible, setVisible] = useState(false);
const [record, setRecord] = useState<any>({});
const components = useMemo(() => { const components = useMemo(() => {
return { return {
toolbar: (props) => <Toolbar {...props} showLunar={showLunar}></Toolbar>, toolbar: (props) => <Toolbar {...props} showLunar={showLunar}></Toolbar>,
week: { week: {
header: (props) => <Header {...props} type="week" showLunar={showLunar}></Header>, header: (props) => <Header {...props} type="week" showLunar={showLunar}></Header>,
}, },
month: { month: {
dateHeader: (props) => <Header {...props} showLunar={showLunar}></Header>, dateHeader: (props) => <Header {...props} showLunar={showLunar}></Header>,
}, },
}; };
}, [showLunar]); }, [showLunar]);
return ( return (
<div style={{ height: fixedBlock ? '100%' : 700 }}> <div style={{ height: fixedBlock ? '100%' : 700 }}>
<CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} /> <CalendarRecordViewer visible={visible} setVisible={setVisible} record={record} />
<BigCalendar <BigCalendar
popup popup
selectable selectable
events={events} events={events}
view={view} view={view}
views={Weeks} views={Weeks}
date={date} date={date}
step={60} step={60}
showMultiDayTimes showMultiDayTimes
messages={messages} messages={messages}
onNavigate={setDate} onNavigate={setDate}
onView={setView} onView={setView}
onSelectSlot={(slotInfo) => { onSelectSlot={(slotInfo) => {
console.log('onSelectSlot', slotInfo); console.log('onSelectSlot', slotInfo);
}} }}
onDoubleClickEvent={(event) => { onDoubleClickEvent={(event) => {
console.log('onDoubleClickEvent'); console.log('onDoubleClickEvent');
}} }}
onSelectEvent={(event) => { onSelectEvent={(event) => {
const record = dataSource?.find((item) => item[fieldNames.id] === event.id); const record = dataSource?.find((item) => item[fieldNames.id] === event.id);
if (!record) { if (!record) {
return; return;
}
record.__event = { ...event, start: formatDate(moment(event.start)), end: formatDate(moment(event.end)) };
setRecord(record);
setVisible(true);
}}
formats={{
monthHeaderFormat: 'Y-M',
agendaDateFormat: 'M-DD',
dayHeaderFormat: 'Y-M-DD',
dayRangeHeaderFormat: ({ start, end }, culture, local) => {
if (dates.eq(start, end, 'month')) {
return local.format(start, 'Y-M', culture);
} }
return `${local.format(start, 'Y-M', culture)} - ${local.format(end, 'Y-M', culture)}`; record.__event = { ...event, start: formatDate(moment(event.start)), end: formatDate(moment(event.end)) };
},
}} setRecord(record);
components={components} setVisible(true);
localizer={localizer} }}
/> formats={{
</div> monthHeaderFormat: 'Y-M',
); agendaDateFormat: 'M-DD',
}); dayHeaderFormat: 'Y-M-DD',
dayRangeHeaderFormat: ({ start, end }, culture, local) => {
if (dates.eq(start, end, 'month')) {
return local.format(start, 'Y-M', culture);
}
return `${local.format(start, 'Y-M', culture)} - ${local.format(end, 'Y-M', culture)}`;
},
}}
components={components}
localizer={localizer}
/>
</div>
);
},
{ displayName: 'Calendar' },
);

View File

@ -11,61 +11,64 @@ import { DeleteEventContext } from './Calendar';
import { formatDate } from './utils'; import { formatDate } from './utils';
const { Text } = Typography; const { Text } = Typography;
export const DeleteEvent = observer(() => { export const DeleteEvent = observer(
const { visible, setVisible } = useActionContext(); () => {
const { exclude = [], cron, ...record } = useRecord(); const { visible, setVisible } = useActionContext();
const { close } = useContext(DeleteEventContext); const { exclude = [], cron, ...record } = useRecord();
const startDate = formatDate(moment(record.__parent.__event.start)); const { close } = useContext(DeleteEventContext);
const filterByTk = useFilterByTk(); const startDate = formatDate(moment(record.__parent.__event.start));
const { resource, service, __parent } = useBlockRequestContext(); const filterByTk = useFilterByTk();
const [value, onChange] = useState(startDate); const { resource, service, __parent } = useBlockRequestContext();
const [loading, setLoading] = useState(false); const [value, onChange] = useState(startDate);
const onOk = async () => { const [loading, setLoading] = useState(false);
setLoading(true); const onOk = async () => {
if (value === 'all' || !cron) { setLoading(true);
await resource.destroy({ if (value === 'all' || !cron) {
filterByTk, await resource.destroy({
}); filterByTk,
} else { });
await resource.update({ } else {
filterByTk, await resource.update({
values: { filterByTk,
exclude: (exclude || []).concat(value), values: {
}, exclude: (exclude || []).concat(value),
}); },
} });
setLoading(false); }
__parent?.service?.refresh?.(); setLoading(false);
service?.refresh?.(); __parent?.service?.refresh?.();
setVisible?.(false, true); service?.refresh?.();
close(); setVisible?.(false, true);
}; close();
};
const { t } = useTranslation(); const { t } = useTranslation();
return createPortal( return createPortal(
<Modal <Modal
title={cron ? t('Delete events') : null} title={cron ? t('Delete events') : null}
visible={visible} visible={visible}
onCancel={() => setVisible(false)} onCancel={() => setVisible(false)}
onOk={() => onOk()} onOk={() => onOk()}
confirmLoading={loading} confirmLoading={loading}
> >
{cron ? ( {cron ? (
<Radio.Group value={value} onChange={(event) => onChange(event.target.value)}> <Radio.Group value={value} onChange={(event) => onChange(event.target.value)}>
<Space direction="vertical"> <Space direction="vertical">
<Radio value={startDate}>{t('This event')}</Radio> <Radio value={startDate}>{t('This event')}</Radio>
<Radio value={`${startDate}_after`}>{t('This and following events')}</Radio> <Radio value={`${startDate}_after`}>{t('This and following events')}</Radio>
<Radio value="all">{t('All events')}</Radio> <Radio value="all">{t('All events')}</Radio>
</Space> </Space>
</Radio.Group> </Radio.Group>
) : ( ) : (
<Text strong style={{ fontSize: '18px' }}> <Text strong style={{ fontSize: '18px' }}>
{t('Delete this event?')} {t('Delete this event?')}
</Text> </Text>
)} )}
</Modal>, </Modal>,
document.body, document.body,
); );
}); },
{ displayName: 'DeleteEvent' },
);
export default DeleteEvent; export default DeleteEvent;

View File

@ -1,6 +1,9 @@
import { observer } from '@formily/react'; import { observer } from '@formily/react';
import React from 'react'; import React from 'react';
export const Event = observer((props) => { export const Event = observer(
return <>{props.children}</>; (props) => {
}); return <>{props.children}</>;
},
{ displayName: 'Event' },
);

View File

@ -6,14 +6,17 @@ import { navigate } from 'react-big-calendar/lib/utils/constants';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { CalendarToolbarContext } from './context'; import { CalendarToolbarContext } from './context';
export const Nav = observer((props) => { export const Nav = observer(
const { DesignableBar } = useDesignable(); (props) => {
const { onNavigate } = useContext(CalendarToolbarContext); const { DesignableBar } = useDesignable();
return ( const { onNavigate } = useContext(CalendarToolbarContext);
<div className="ant-btn-group"> return (
<Button icon={<LeftOutlined />} onClick={() => onNavigate(navigate.PREVIOUS)}></Button> <div className="ant-btn-group">
<Button icon={<RightOutlined />} onClick={() => onNavigate(navigate.NEXT)}></Button> <Button icon={<LeftOutlined />} onClick={() => onNavigate(navigate.PREVIOUS)}></Button>
<DesignableBar /> <Button icon={<RightOutlined />} onClick={() => onNavigate(navigate.NEXT)}></Button>
</div> <DesignableBar />
); </div>
}); );
},
{ displayName: 'Nav' },
);

View File

@ -4,22 +4,25 @@ import { useDesignable } from '../../hooks';
import { CalendarToolbarContext } from './context'; import { CalendarToolbarContext } from './context';
import { getLunarDay } from './utils'; import { getLunarDay } from './utils';
export const Title = observer(() => { export const Title = observer(
const { DesignableBar } = useDesignable(); () => {
const { date, view, label, showLunar } = useContext(CalendarToolbarContext); const { DesignableBar } = useDesignable();
const { date, view, label, showLunar } = useContext(CalendarToolbarContext);
const lunarElement = useMemo(() => { const lunarElement = useMemo(() => {
if (!showLunar || view !== 'day') { if (!showLunar || view !== 'day') {
return; return;
} }
return <span>{getLunarDay(date)}</span>; return <span>{getLunarDay(date)}</span>;
}, [view, date, showLunar]); }, [view, date, showLunar]);
return ( return (
<div className="ant-btn-group" style={{ fontSize: '1.75em', fontWeight: 300 }}> <div className="ant-btn-group" style={{ fontSize: '1.75em', fontWeight: 300 }}>
<span>{label}</span> <span>{label}</span>
<span style={{ marginLeft: '4px' }}>{lunarElement}</span> <span style={{ marginLeft: '4px' }}>{lunarElement}</span>
<DesignableBar /> <DesignableBar />
</div> </div>
); );
}); },
{ displayName: 'Title' },
);

View File

@ -6,18 +6,21 @@ import { useTranslation } from 'react-i18next';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { CalendarToolbarContext } from './context'; import { CalendarToolbarContext } from './context';
export const Today = observer((props) => { export const Today = observer(
const { DesignableBar } = useDesignable(); (props) => {
const { onNavigate } = useContext(CalendarToolbarContext); const { DesignableBar } = useDesignable();
const { t } = useTranslation(); const { onNavigate } = useContext(CalendarToolbarContext);
return ( const { t } = useTranslation();
<Button return (
onClick={() => { <Button
onNavigate(navigate.TODAY); onClick={() => {
}} onNavigate(navigate.TODAY);
> }}
{t('Today')} >
<DesignableBar /> {t('Today')}
</Button> <DesignableBar />
); </Button>
}); );
},
{ displayName: 'Today' },
);

View File

@ -4,22 +4,25 @@ import React, { useContext } from 'react';
import { useDesignable } from '../../hooks'; import { useDesignable } from '../../hooks';
import { CalendarToolbarContext } from './context'; import { CalendarToolbarContext } from './context';
export const ViewSelect = observer((props) => { export const ViewSelect = observer(
const { DesignableBar } = useDesignable(); (props) => {
const { const { DesignableBar } = useDesignable();
views, const {
view, views,
onView, view,
localizer: { messages }, onView,
} = useContext(CalendarToolbarContext); localizer: { messages },
return ( } = useContext(CalendarToolbarContext);
<div className="ant-btn-group"> return (
<Select value={view} onChange={onView}> <div className="ant-btn-group">
{views.map((name) => ( <Select value={view} onChange={onView}>
<Select.Option value={name}>{messages[name]}</Select.Option> {views.map((name) => (
))} <Select.Option value={name}>{messages[name]}</Select.Option>
</Select> ))}
<DesignableBar /> </Select>
</div> <DesignableBar />
); </div>
}); );
},
{ displayName: 'ViewSelect' },
);

View File

@ -58,6 +58,6 @@ export const CollectionSelect = connect(
))} ))}
</div> </div>
); );
}), }, { displayName: 'CollectionSelectObserver' }),
), ),
); );

View File

@ -15,35 +15,38 @@ const useDef = (options) => {
return useRequest(() => Promise.resolve({ data: field.dataSource }), options); return useRequest(() => Promise.resolve({ data: field.dataSource }), options);
}; };
export const Filter: any = observer((props: any) => { export const Filter: any = observer(
const { useDataSource = useDef } = props; (props: any) => {
const { options, dynamicComponent, className } = useProps(props); const { useDataSource = useDef } = props;
const field = useField<ObjectFieldModel>(); const { options, dynamicComponent, className } = useProps(props);
const fieldSchema = useFieldSchema(); const field = useField<ObjectFieldModel>();
useDataSource({ const fieldSchema = useFieldSchema();
onSuccess(data) { useDataSource({
field.dataSource = data?.data || []; onSuccess(data) {
}, field.dataSource = data?.data || [];
}); },
return ( });
<div className={className}> return (
<DatePickerProvider value={{ utc: false }}> <div className={className}>
<FilterContext.Provider <DatePickerProvider value={{ utc: false }}>
value={{ <FilterContext.Provider
field, value={{
fieldSchema, field,
dynamicComponent, fieldSchema,
options: options || field.dataSource || [], dynamicComponent,
disabled: props.disabled, options: options || field.dataSource || [],
}} disabled: props.disabled,
> }}
<FilterGroup {...props} bordered={false} /> >
{/* <pre>{JSON.stringify(field.value, null, 2)}</pre> */} <FilterGroup {...props} bordered={false} />
</FilterContext.Provider> {/* <pre>{JSON.stringify(field.value, null, 2)}</pre> */}
</DatePickerProvider> </FilterContext.Provider>
</div> </DatePickerProvider>
); </div>
}); );
},
{ displayName: 'Filter' },
);
Filter.SaveDefaultValue = SaveDefaultValue; Filter.SaveDefaultValue = SaveDefaultValue;

View File

@ -11,83 +11,86 @@ import { Action } from '../action';
export const FilterActionContext = createContext<any>(null); export const FilterActionContext = createContext<any>(null);
export const FilterAction = observer((props: any) => { export const FilterAction = observer(
const { t } = useTranslation(); (props: any) => {
const field = useField<Field>(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const field = useField<Field>();
const { designable, dn } = useDesignable(); const [visible, setVisible] = useState(false);
const fieldSchema = useFieldSchema(); const { designable, dn } = useDesignable();
const form = useMemo<Form>(() => props.form || createForm(), []); const fieldSchema = useFieldSchema();
const { options, onSubmit, onReset, ...others } = useProps(props); const form = useMemo<Form>(() => props.form || createForm(), []);
return ( const { options, onSubmit, onReset, ...others } = useProps(props);
<FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}> return (
<Popover <FilterActionContext.Provider value={{ field, fieldSchema, designable, dn }}>
destroyTooltipOnHide <Popover
placement={'bottomLeft'} destroyTooltipOnHide
open={visible} placement={'bottomLeft'}
onOpenChange={(visible) => { open={visible}
setVisible(visible); onOpenChange={(visible) => {
}} setVisible(visible);
trigger={'click'} }}
content={ trigger={'click'}
<form> content={
<FormProvider form={form}> <form>
<SchemaComponent <FormProvider form={form}>
schema={{ <SchemaComponent
type: 'object', schema={{
properties: { type: 'object',
filter: { properties: {
type: 'string', filter: {
enum: options || field.dataSource, type: 'string',
default: fieldSchema.default, enum: options || field.dataSource,
'x-component': 'Filter', default: fieldSchema.default,
'x-component-props': {}, 'x-component': 'Filter',
'x-component-props': {},
},
}, },
}, }}
}} />
/> <div
<div className={css`
className={css` display: flex;
display: flex; justify-content: flex-end;
justify-content: flex-end; width: 100%;
width: 100%; `}
`} >
> <Space>
<Space> <SaveConditions />
<SaveConditions /> <Button
<Button onClick={async () => {
onClick={async () => { await form.reset();
await form.reset(); onReset?.(form.values);
onReset?.(form.values); field.title = t('Filter');
field.title = t('Filter'); setVisible(false);
setVisible(false); }}
}} >
> {t('Reset')}
{t('Reset')} </Button>
</Button> <Button
<Button type={'primary'}
type={'primary'} htmlType={'submit'}
htmlType={'submit'} onClick={(e) => {
onClick={(e) => { e.preventDefault();
e.preventDefault(); e.stopPropagation();
e.stopPropagation(); onSubmit?.(form.values);
onSubmit?.(form.values); setVisible(false);
setVisible(false); }}
}} >
> {t('Submit')}
{t('Submit')} </Button>
</Button> </Space>
</Space> </div>
</div> </FormProvider>
</FormProvider> </form>
</form> }
} >
> <Action {...others} title={field.title} />
<Action {...others} title={field.title} /> </Popover>
</Popover> </FilterActionContext.Provider>
</FilterActionContext.Provider> );
); },
}); { displayName: 'FilterAction' },
);
const SaveConditions = () => { const SaveConditions = () => {
const { fieldSchema, field, designable, dn } = useContext(FilterActionContext); const { fieldSchema, field, designable, dn } = useContext(FilterActionContext);

View File

@ -9,56 +9,59 @@ import { RemoveConditionContext } from './context';
import { DynamicComponent } from './DynamicComponent'; import { DynamicComponent } from './DynamicComponent';
import { useValues } from './useValues'; import { useValues } from './useValues';
export const FilterItem = observer((props: any) => { export const FilterItem = observer(
const { t } = useTranslation(); (props: any) => {
const compile = useCompile(); const { t } = useTranslation();
const remove = useContext(RemoveConditionContext); const compile = useCompile();
const { schema, fields, operators, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues(); const remove = useContext(RemoveConditionContext);
return ( const { schema, fields, operators, dataIndex, operator, setDataIndex, setOperator, value, setValue } = useValues();
<div style={{ marginBottom: 8 }}> return (
<Space> <div style={{ marginBottom: 8 }}>
<Cascader <Space>
className={css` <Cascader
width: 160px; className={css`
`} width: 160px;
fieldNames={{ `}
label: 'title', fieldNames={{
value: 'name', label: 'title',
children: 'children', value: 'name',
}} children: 'children',
changeOnSelect={false} }}
value={dataIndex} changeOnSelect={false}
options={compile(fields)} value={dataIndex}
onChange={(value) => { options={compile(fields)}
setDataIndex(value); onChange={(value) => {
}} setDataIndex(value);
placeholder={t('Select Field')} }}
/> placeholder={t('Select Field')}
<Select />
className={css` <Select
min-width: 110px; className={css`
`} min-width: 110px;
value={operator?.value} `}
options={compile(operators)} value={operator?.value}
onChange={(value) => { options={compile(operators)}
setOperator(value); onChange={(value) => {
}} setOperator(value);
placeholder={t('Comparision')} }}
/> placeholder={t('Comparision')}
{!operator?.noValue && />
React.createElement(DynamicComponent, { {!operator?.noValue &&
value: value ? value : undefined, React.createElement(DynamicComponent, {
schema, value: value ? value : undefined,
onChange(value) { schema,
setValue(value); onChange(value) {
}, setValue(value);
})} },
{!props.disabled && ( })}
<a> {!props.disabled && (
<CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} /> <a>
</a> <CloseCircleOutlined onClick={() => remove()} style={{ color: '#bfbfbf' }} />
)} </a>
</Space> )}
</div> </Space>
); </div>
}); );
},
{ displayName: 'FilterItem' },
);

View File

@ -5,17 +5,20 @@ import { RemoveConditionContext } from './context';
import { FilterGroup } from './FilterGroup'; import { FilterGroup } from './FilterGroup';
import { FilterItem } from './FilterItem'; import { FilterItem } from './FilterItem';
export const FilterItems = observer((props) => { export const FilterItems = observer(
const field = useField<ArrayFieldModel>(); (props) => {
return ( const field = useField<ArrayFieldModel>();
<div> return (
{field?.value?.map((item, index) => { <div>
return ( {field?.value?.map((item, index) => {
<RemoveConditionContext.Provider key={index} value={() => field.remove(index)}> return (
<ObjectField name={index} component={[item.$and || item.$or ? FilterGroup : FilterItem]} /> <RemoveConditionContext.Provider key={index} value={() => field.remove(index)}>
</RemoveConditionContext.Provider> <ObjectField name={index} component={[item.$and || item.$or ? FilterGroup : FilterItem]} />
); </RemoveConditionContext.Provider>
})} );
</div> })}
); </div>
}); );
},
{ displayName: 'FilterItems' },
);

View File

@ -53,61 +53,64 @@ const divWrap = (schema: ISchema) => {
}; };
}; };
export const FormItem: any = observer((props: any) => { export const FormItem: any = observer(
useEnsureOperatorsValid(); (props: any) => {
useEnsureOperatorsValid();
const field = useField<Field>(); const field = useField<Field>();
const ctx = useContext(BlockRequestContext); const ctx = useContext(BlockRequestContext);
const schema = useFieldSchema(); const schema = useFieldSchema();
const variablesCtx = useVariablesCtx(); const variablesCtx = useVariablesCtx();
useEffect(() => { useEffect(() => {
if (ctx?.block === 'form') { if (ctx?.block === 'form') {
ctx.field.data = ctx.field.data || {}; ctx.field.data = ctx.field.data || {};
ctx.field.data.activeFields = ctx.field.data.activeFields || new Set(); ctx.field.data.activeFields = ctx.field.data.activeFields || new Set();
ctx.field.data.activeFields.add(schema.name); ctx.field.data.activeFields.add(schema.name);
// 如果默认值是一个变量,则需要解析之后再显示出来 // 如果默认值是一个变量,则需要解析之后再显示出来
if (isVariable(schema?.default)) { if (isVariable(schema?.default)) {
field.setInitialValue?.(parseVariables(schema.default, variablesCtx)); field.setInitialValue?.(parseVariables(schema.default, variablesCtx));
}
} }
} }, []);
}, []); const showTitle = schema['x-decorator-props']?.showTitle ?? true;
const showTitle = schema['x-decorator-props']?.showTitle ?? true; return (
return ( <ACLCollectionFieldProvider>
<ACLCollectionFieldProvider> <BlockItem className={'nb-form-item'}>
<BlockItem className={'nb-form-item'}> <Item
<Item className={cx(
className={cx( css`
css` & .ant-space {
& .ant-space { flex-wrap: wrap;
flex-wrap: wrap;
}
`,
{
[css`
> .ant-formily-item-label {
display: none;
} }
`]: showTitle === false, `,
}, {
)} [css`
{...props} > .ant-formily-item-label {
extra={ display: none;
typeof field.description === 'string' ? ( }
<div `]: showTitle === false,
dangerouslySetInnerHTML={{ },
__html: HTMLEncode(field.description).split('\n').join('<br/>'), )}
}} {...props}
/> extra={
) : ( typeof field.description === 'string' ? (
field.description <div
) dangerouslySetInnerHTML={{
} __html: HTMLEncode(field.description).split('\n').join('<br/>'),
/> }}
</BlockItem> />
</ACLCollectionFieldProvider> ) : (
); field.description
}); )
}
/>
</BlockItem>
</ACLCollectionFieldProvider>
);
},
{ displayName: 'FormItem' },
);
FormItem.Designer = function Designer() { FormItem.Designer = function Designer() {
const { getCollectionFields, getInterface, getCollectionJoinField, getCollection } = useCollectionManager(); const { getCollectionFields, getInterface, getCollectionJoinField, getCollection } = useCollectionManager();

View File

@ -170,21 +170,24 @@ export const Form: React.FC<FormProps> & {
FilterDesigner?: any; FilterDesigner?: any;
ReadPrettyDesigner?: any; ReadPrettyDesigner?: any;
Templates?: any; Templates?: any;
} = observer((props) => { } = observer(
const field = useField<Field>(); (props) => {
const { form, disabled, ...others } = useProps(props); const field = useField<Field>();
const formDisabled = disabled || field.disabled; const { form, disabled, ...others } = useProps(props);
return ( const formDisabled = disabled || field.disabled;
<ConfigProvider componentDisabled={formDisabled}> return (
<form> <ConfigProvider componentDisabled={formDisabled}>
<Spin spinning={field.loading || false}> <form>
{form ? ( <Spin spinning={field.loading || false}>
<WithForm form={form} {...others} disabled={formDisabled} /> {form ? (
) : ( <WithForm form={form} {...others} disabled={formDisabled} />
<WithoutForm {...others} disabled={formDisabled} /> ) : (
)} <WithoutForm {...others} disabled={formDisabled} />
</Spin> )}
</form> </Spin>
</ConfigProvider> </form>
); </ConfigProvider>
}); );
},
{ displayName: 'Form' },
);

View File

@ -4,21 +4,24 @@ import { useFormBlockContext } from '../../../block-provider';
import { useCollection } from '../../../collection-manager'; import { useCollection } from '../../../collection-manager';
import { useCompile } from '../../hooks'; import { useCompile } from '../../hooks';
export const FormField: any = observer((props) => { export const FormField: any = observer(
const fieldSchema = useFieldSchema(); (props) => {
const { getField } = useCollection(); const fieldSchema = useFieldSchema();
const field = useField(); const { getField } = useCollection();
const collectionField = getField(fieldSchema.name); const field = useField();
const compile = useCompile(); const collectionField = getField(fieldSchema.name);
const ctx = useFormBlockContext(); const compile = useCompile();
useEffect(() => { const ctx = useFormBlockContext();
if (!field.title) { useEffect(() => {
field.title = compile(collectionField?.uiSchema?.title); if (!field.title) {
} field.title = compile(collectionField?.uiSchema?.title);
if (ctx?.field) { }
ctx.field.added = ctx.field.added || new Set(); if (ctx?.field) {
ctx.field.added.add(fieldSchema.name); ctx.field.added = ctx.field.added || new Set();
} ctx.field.added.add(fieldSchema.name);
}, []); }
return <div>{props.children}</div>; }, []);
}); return <div>{props.children}</div>;
},
{ displayName: 'FormField' },
);

View File

@ -83,34 +83,37 @@ const useDefaultValues = (opts: any = {}, props: FormProps = {}) => {
const FormBlockContext = createContext<any>(null); const FormBlockContext = createContext<any>(null);
export const Form: React.FC<FormProps> & { Designer?: any } = observer((props) => { export const Form: React.FC<FormProps> & { Designer?: any } = observer(
const { request, effects, initialValue, useValues = useDefaultValues, ...others } = props; (props) => {
const fieldSchema = useFieldSchema(); const { request, effects, initialValue, useValues = useDefaultValues, ...others } = props;
const field = useField(); const fieldSchema = useFieldSchema();
const form = useMemo(() => createForm({ effects }), []); const field = useField();
const result = useValues( const form = useMemo(() => createForm({ effects }), []);
{ const result = useValues(
uid: fieldSchema['x-uid'], {
async onSuccess(data) { uid: fieldSchema['x-uid'],
await form.reset(); async onSuccess(data) {
form.setValues(data?.data); await form.reset();
form.setValues(data?.data);
},
}, },
}, props,
props, );
); const parent = useContext(FormBlockContext);
const parent = useContext(FormBlockContext); return (
return ( <FormBlockContext.Provider value={{ parent, form, result, field, fieldSchema }}>
<FormBlockContext.Provider value={{ parent, form, result, field, fieldSchema }}> <Spin spinning={result?.loading || false}>
<Spin spinning={result?.loading || false}> {fieldSchema['x-decorator'] === 'Form' ? (
{fieldSchema['x-decorator'] === 'Form' ? ( <FormDecorator form={form} {...others} />
<FormDecorator form={form} {...others} /> ) : (
) : ( <FormComponent form={form} {...others} />
<FormComponent form={form} {...others} /> )}
)} </Spin>
</Spin> </FormBlockContext.Provider>
</FormBlockContext.Provider> );
); },
}); { displayName: 'Form' },
);
Form.Designer = () => { Form.Designer = () => {
const { name, title } = useCollection(); const { name, title } = useCollection();

View File

@ -49,10 +49,13 @@ export default observer(() => {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
return ( return (
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}> <SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}>

View File

@ -38,10 +38,13 @@ export default observer(() => {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
const useSubmit = () => { const useSubmit = () => {
const form = useForm(); const form = useForm();

View File

@ -38,10 +38,13 @@ export default observer(() => {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
const useSubmit = () => { const useSubmit = () => {
const form = useForm(); const form = useForm();

View File

@ -54,10 +54,13 @@ export default observer(() => {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
return ( return (
<SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}> <SchemaComponentProvider scope={{ useCloseAction }} components={{ Output, Form, Action, Input, FormItem }}>

View File

@ -69,10 +69,13 @@ export default observer(() => {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
const useSubmit = () => { const useSubmit = () => {
const form = useForm(); const form = useForm();

View File

@ -39,10 +39,13 @@ export default observer(() => {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
const useSubmit = () => { const useSubmit = () => {
const form = useForm(); const form = useForm();

View File

@ -36,10 +36,13 @@ const schema: ISchema = {
}, },
}; };
const Output = observer(() => { const Output = observer(
const form = useForm(); () => {
return <pre>{JSON.stringify(form.values, null, 2)}</pre>; const form = useForm();
}); return <pre>{JSON.stringify(form.values, null, 2)}</pre>;
},
{ displayName: 'Output' },
);
const useSubmit = () => { const useSubmit = () => {
const form = useForm(); const form = useForm();

View File

@ -127,51 +127,54 @@ export const G2PlotRenderer = forwardRef(function <O = any>(props: ReactG2PlotPr
return <div className={cls(['g2plot', className])} ref={containerRef} />; return <div className={cls(['g2plot', className])} ref={containerRef} />;
}); });
export const G2Plot: any = observer((props: any) => { export const G2Plot: any = observer(
const { plot, config } = props; (props: any) => {
const field = useField<Field>(); const { plot, config } = props;
const { t } = useTranslation(); const field = useField<Field>();
const api = useAPIClient(); const { t } = useTranslation();
useEffect(() => { const api = useAPIClient();
field.data = field.data || {}; useEffect(() => {
field.data.loading = true; field.data = field.data || {};
const fn = config?.data; field.data.loading = true;
if (typeof fn === 'function') { const fn = config?.data;
const result = fn.bind({ api })(); if (typeof fn === 'function') {
if (result?.then) { const result = fn.bind({ api })();
result.then((data) => { if (result?.then) {
if (Array.isArray(data)) { result.then((data) => {
field.componentProps.config.data = data; if (Array.isArray(data)) {
} field.componentProps.config.data = data;
}
field.data.loading = false;
});
} else {
field.data.loading = false; field.data.loading = false;
}); }
} else { } else {
field.data.loading = false; field.data.loading = false;
} }
} else { }, []);
field.data.loading = false;
}
}, []);
if (!plot || !config) { if (!plot || !config) {
return <div style={{ opacity: 0.3 }}>{t('In configuration')}...</div>; return <div style={{ opacity: 0.3 }}>{t('In configuration')}...</div>;
} }
if (field?.data?.loading !== false) { if (field?.data?.loading !== false) {
return <Spin />; return <Spin />;
} }
return ( return (
<div> <div>
{field.title && <h2>{field.title}</h2>} {field.title && <h2>{field.title}</h2>}
<G2PlotRenderer <G2PlotRenderer
plot={plots[plot]} plot={plots[plot]}
config={{ config={{
...config, ...config,
data: Array.isArray(config?.data) ? config.data : [], data: Array.isArray(config?.data) ? config.data : [],
}} }}
/> />
</div> </div>
); );
}); },
{ displayName: 'G2Plot' },
);
G2Plot.Designer = G2PlotDesigner; G2Plot.Designer = G2PlotDesigner;
G2Plot.plots = plots; G2Plot.plots = plots;

View File

@ -1,6 +1,9 @@
import { observer } from '@formily/react'; import { observer } from '@formily/react';
import React from 'react'; import React from 'react';
export const Event = observer((props) => { export const Event = observer(
return <>{props.children}</>; (props) => {
}); return <>{props.children}</>;
},
{ displayName: 'Event' },
);

View File

@ -2,12 +2,15 @@ import { observer, useFieldSchema } from '@formily/react';
import React from 'react'; import React from 'react';
import { DragHandler } from '../../'; import { DragHandler } from '../../';
export const Block = observer((props) => { export const Block = observer(
const fieldSchema = useFieldSchema(); (props) => {
return ( const fieldSchema = useFieldSchema();
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}> return (
Block {fieldSchema.title} <div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
<DragHandler /> Block {fieldSchema.title}
</div> <DragHandler />
); </div>
}); );
},
{ displayName: 'Block' },
);

View File

@ -330,45 +330,108 @@ export const useGridRowContext = () => {
return useContext(GridRowContext); return useContext(GridRowContext);
}; };
export const Grid: any = observer((props: any) => { export const Grid: any = observer(
const gridRef = useRef(null); (props: any) => {
const field = useField(); const gridRef = useRef(null);
const fieldSchema = useFieldSchema(); const field = useField();
const { render, InitializerComponent } = useSchemaInitializer(fieldSchema['x-initializer']); const fieldSchema = useFieldSchema();
const addr = field.address.toString(); const { render, InitializerComponent } = useSchemaInitializer(fieldSchema['x-initializer']);
const rows = useRowProperties(); const addr = field.address.toString();
const { setPrintContent } = useFormBlockContext(); const rows = useRowProperties();
const { setPrintContent } = useFormBlockContext();
useEffect(() => { useEffect(() => {
gridRef.current && setPrintContent?.(gridRef.current); gridRef.current && setPrintContent?.(gridRef.current);
}, [gridRef.current]); }, [gridRef.current]);
return ( return (
<GridContext.Provider value={{ ref: gridRef, fieldSchema, renderSchemaInitializer: render, InitializerComponent }}> <GridContext.Provider
<div className={'nb-grid'} style={{ position: 'relative' }} ref={gridRef}> value={{ ref: gridRef, fieldSchema, renderSchemaInitializer: render, InitializerComponent }}
<DndWrapper dndContext={props.dndContext}> >
<RowDivider <div className={'nb-grid'} style={{ position: 'relative' }} ref={gridRef}>
rows={rows} <DndWrapper dndContext={props.dndContext}>
<RowDivider
rows={rows}
first
id={`${addr}_0`}
data={{
breakRemoveOn: breakRemoveOnGrid,
wrapSchema: wrapRowSchema,
insertAdjacent: 'afterBegin',
schema: fieldSchema,
}}
/>
{rows.map((schema, index) => {
return (
<React.Fragment key={index}>
<RecursionField name={schema.name} schema={schema} />
<RowDivider
rows={rows}
index={index}
id={`${addr}_${index + 1}`}
data={{
breakRemoveOn: breakRemoveOnGrid,
wrapSchema: wrapRowSchema,
insertAdjacent: 'afterEnd',
schema,
}}
/>
</React.Fragment>
);
})}
</DndWrapper>
<InitializerComponent />
</div>
</GridContext.Provider>
);
},
{ displayName: 'Grid' },
);
Grid.Row = observer(
() => {
const field = useField();
const fieldSchema = useFieldSchema();
const addr = field.address.toString();
const cols = useColProperties();
return (
<GridRowContext.Provider value={{ schema: fieldSchema, cols }}>
<div
className={cls(
'nb-grid-row',
css`
overflow-x: hidden;
margin: 0 calc(-1 * var(--nb-spacing));
display: flex;
position: relative;
/* z-index: 0; */
`,
)}
>
<ColDivider
cols={cols}
first first
id={`${addr}_0`} id={`${addr}_0`}
data={{ data={{
breakRemoveOn: breakRemoveOnGrid, breakRemoveOn: breakRemoveOnRow,
wrapSchema: wrapRowSchema, wrapSchema: wrapColSchema,
insertAdjacent: 'afterBegin', insertAdjacent: 'afterBegin',
schema: fieldSchema, schema: fieldSchema,
}} }}
/> />
{rows.map((schema, index) => { {cols.map((schema, index) => {
return ( return (
<React.Fragment key={index}> <React.Fragment key={index}>
<RecursionField name={schema.name} schema={schema} /> <RecursionField name={schema.name} schema={schema} />
<RowDivider <ColDivider
rows={rows} cols={cols}
index={index} index={index}
last={index === cols.length - 1}
id={`${addr}_${index + 1}`} id={`${addr}_${index + 1}`}
data={{ data={{
breakRemoveOn: breakRemoveOnGrid, breakRemoveOn: breakRemoveOnRow,
wrapSchema: wrapRowSchema, wrapSchema: wrapColSchema,
insertAdjacent: 'afterEnd', insertAdjacent: 'afterEnd',
schema, schema,
}} }}
@ -376,95 +439,43 @@ export const Grid: any = observer((props: any) => {
</React.Fragment> </React.Fragment>
); );
})} })}
</DndWrapper> </div>
<InitializerComponent /> </GridRowContext.Provider>
</div> );
</GridContext.Provider> },
); { displayName: 'Grid.Row' },
}); );
Grid.Row = observer(() => { Grid.Col = observer(
const field = useField(); (props: any) => {
const fieldSchema = useFieldSchema(); const { cols = [] } = useContext(GridRowContext);
const addr = field.address.toString(); const schema = useFieldSchema();
const cols = useColProperties(); const field = useField();
return ( const width = useMemo(() => {
<GridRowContext.Provider value={{ schema: fieldSchema, cols }}> let width = '';
<div if (cols?.length) {
className={cls( const w = schema?.['x-component-props']?.['width'] || 100 / cols.length;
'nb-grid-row', width = `calc(${w}% - var(--nb-spacing) * ${(cols.length + 1) / cols.length})`;
css` }
overflow-x: hidden; return width;
margin: 0 calc(-1 * var(--nb-spacing)); }, [cols?.length, schema?.['x-component-props']?.['width']]);
display: flex;
position: relative;
/* z-index: 0; */
`,
)}
>
<ColDivider
cols={cols}
first
id={`${addr}_0`}
data={{
breakRemoveOn: breakRemoveOnRow,
wrapSchema: wrapColSchema,
insertAdjacent: 'afterBegin',
schema: fieldSchema,
}}
/>
{cols.map((schema, index) => {
return (
<React.Fragment key={index}>
<RecursionField name={schema.name} schema={schema} />
<ColDivider
cols={cols}
index={index}
last={index === cols.length - 1}
id={`${addr}_${index + 1}`}
data={{
breakRemoveOn: breakRemoveOnRow,
wrapSchema: wrapColSchema,
insertAdjacent: 'afterEnd',
schema,
}}
/>
</React.Fragment>
);
})}
</div>
</GridRowContext.Provider>
);
});
Grid.Col = observer((props: any) => { const { setNodeRef } = useDroppable({
const { cols = [] } = useContext(GridRowContext); id: field.address.toString(),
const schema = useFieldSchema(); data: {
const field = useField(); insertAdjacent: 'beforeEnd',
schema,
const width = useMemo(() => { wrapSchema: (s) => s,
let width = ''; },
if (cols?.length) { });
const w = schema?.['x-component-props']?.['width'] || 100 / cols.length; return (
width = `calc(${w}% - var(--nb-spacing) * ${(cols.length + 1) / cols.length})`; <GridColContext.Provider value={{ cols, schema }}>
} <div ref={setNodeRef} style={{ width }} className={cls('nb-grid-col')}>
return width; {props.children}
}, [cols?.length, schema?.['x-component-props']?.['width']]); </div>
</GridColContext.Provider>
const { setNodeRef } = useDroppable({ );
id: field.address.toString(), },
data: { { displayName: 'Grid.Col' },
insertAdjacent: 'beforeEnd', );
schema,
wrapSchema: (s) => s,
},
});
return (
<GridColContext.Provider value={{ cols, schema }}>
<div ref={setNodeRef} style={{ width }} className={cls('nb-grid-col')}>
{props.children}
</div>
</GridColContext.Provider>
);
});

View File

@ -2,15 +2,18 @@ import { observer, useFieldSchema } from '@formily/react';
import { BlockItem, DragHandler, Grid, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; import { BlockItem, DragHandler, Grid, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import React from 'react'; import React from 'react';
const Block = observer((props) => { const Block = observer(
const fieldSchema = useFieldSchema(); (props) => {
return ( const fieldSchema = useFieldSchema();
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}> return (
Block {fieldSchema.title} <div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
<DragHandler /> Block {fieldSchema.title}
</div> <DragHandler />
); </div>
}); );
},
{ displayName: 'Block' },
);
const schema = { const schema = {
_isJSONSchemaObject: true, _isJSONSchemaObject: true,

View File

@ -10,98 +10,101 @@ import { RecordProvider } from '../../../record-provider';
import { SchemaComponentOptions } from '../../core/SchemaComponentOptions'; import { SchemaComponentOptions } from '../../core/SchemaComponentOptions';
import { KanbanCardContext } from './context'; import { KanbanCardContext } from './context';
export const KanbanCard: any = observer((props: any) => { export const KanbanCard: any = observer(
const { setDisableCardDrag, cardViewerSchema, card, cardField, columnIndex, cardIndex } = (props: any) => {
useContext(KanbanCardContext); const { setDisableCardDrag, cardViewerSchema, card, cardField, columnIndex, cardIndex } =
const fieldSchema = useFieldSchema(); useContext(KanbanCardContext);
const [visible, setVisible] = useState(false); const fieldSchema = useFieldSchema();
const labelDisabled = fieldSchema['x-label-disabled']; const [visible, setVisible] = useState(false);
const labelDisabled = fieldSchema['x-label-disabled'];
return ( return (
<SchemaComponentOptions components={{}} scope={{}}> <SchemaComponentOptions components={{}} scope={{}}>
<Card <Card
onClick={(e) => { onClick={(e) => {
setVisible(true); setVisible(true);
}} }}
bordered={false} bordered={false}
hoverable hoverable
style={{ cursor: 'pointer', overflow: 'hidden' }} style={{ cursor: 'pointer', overflow: 'hidden' }}
// bodyStyle={{ paddingBottom: 0 }} // bodyStyle={{ paddingBottom: 0 }}
className={cls( className={cls(
css` css`
.ant-card-body { .ant-card-body {
padding: 16px; padding: 16px;
}
.nb-row-divider {
height: 16px;
margin-top: -16px;
&:last-child {
margin-top: 0;
} }
} .nb-row-divider {
.ant-description-input { height: 16px;
text-overflow: ellipsis; margin-top: -16px;
width: 100%; &:last-child {
overflow: hidden; margin-top: 0;
} }
.ant-description-textarea { }
text-overflow: ellipsis; .ant-description-input {
width: 100%; text-overflow: ellipsis;
overflow: hidden; width: 100%;
} overflow: hidden;
.ant-formily-item { }
margin-bottom: 12px; .ant-description-textarea {
} text-overflow: ellipsis;
.nb-grid-row:last-of-type { width: 100%;
.nb-grid-col { overflow: hidden;
.nb-form-item:last-of-type { }
.ant-formily-item { .ant-formily-item {
margin-bottom: 0; margin-bottom: 12px;
}
.nb-grid-row:last-of-type {
.nb-grid-col {
.nb-form-item:last-of-type {
.ant-formily-item {
margin-bottom: 0;
}
} }
} }
} }
} `,
`, {
{ 'kanban-no-label': labelDisabled,
'kanban-no-label': labelDisabled, },
}, )}
>
<DndContext
onDragStart={() => {
setDisableCardDrag(true);
}}
onDragEnd={() => {
setDisableCardDrag(false);
}}
>
<FormLayout layout={'vertical'}>
<RecursionField
basePath={cardField.address.concat(`${columnIndex}.cards.${cardIndex}`)}
schema={fieldSchema}
onlyRenderProperties
/>
</FormLayout>
</DndContext>
</Card>
{cardViewerSchema && (
<ActionContext.Provider
value={{
openMode: fieldSchema['x-component-props']?.['openMode'] || 'drawer',
openSize: fieldSchema['x-component-props']?.['openSize'],
visible,
setVisible,
}}
>
<RecordProvider record={card}>
<RecursionField
basePath={cardField.address.concat(`${columnIndex}.cardViewer.${cardIndex}`)}
schema={cardViewerSchema}
onlyRenderProperties
/>
</RecordProvider>
</ActionContext.Provider>
)} )}
> </SchemaComponentOptions>
<DndContext );
onDragStart={() => { },
setDisableCardDrag(true); { displayName: 'KanbanCard' },
}} );
onDragEnd={() => {
setDisableCardDrag(false);
}}
>
<FormLayout layout={'vertical'}>
<RecursionField
basePath={cardField.address.concat(`${columnIndex}.cards.${cardIndex}`)}
schema={fieldSchema}
onlyRenderProperties
/>
</FormLayout>
</DndContext>
</Card>
{cardViewerSchema && (
<ActionContext.Provider
value={{
openMode: fieldSchema['x-component-props']?.['openMode'] || 'drawer',
openSize: fieldSchema['x-component-props']?.['openSize'],
visible,
setVisible,
}}
>
<RecordProvider record={card}>
<RecursionField
basePath={cardField.address.concat(`${columnIndex}.cardViewer.${cardIndex}`)}
schema={cardViewerSchema}
onlyRenderProperties
/>
</RecordProvider>
</ActionContext.Provider>
)}
</SchemaComponentOptions>
);
});

View File

@ -1,5 +1,8 @@
import { observer } from '@formily/react'; import { observer } from '@formily/react';
export const KanbanCardViewer: any = observer((props: any) => { export const KanbanCardViewer: any = observer(
return props.children; (props: any) => {
}); return props.children;
},
{ displayName: 'KanbanCardViewer' },
);

View File

@ -55,103 +55,106 @@ export const toColumns = (groupField: any, dataSource: Array<any> = []) => {
return Object.values(columns); return Object.values(columns);
}; };
export const Kanban: any = observer((props: any) => { export const Kanban: any = observer(
const { groupField, onCardDragEnd, ...restProps } = useProps(props); (props: any) => {
const field = useField<ArrayField>(); const { groupField, onCardDragEnd, ...restProps } = useProps(props);
const fieldSchema = useFieldSchema(); const field = useField<ArrayField>();
const [disableCardDrag, setDisableCardDrag] = useState(false); const fieldSchema = useFieldSchema();
const schemas = useMemo( const [disableCardDrag, setDisableCardDrag] = useState(false);
() => const schemas = useMemo(
fieldSchema.reduceProperties( () =>
(buf, current) => { fieldSchema.reduceProperties(
if (current['x-component'].endsWith('.Card')) { (buf, current) => {
buf.card = current; if (current['x-component'].endsWith('.Card')) {
} else if (current['x-component'].endsWith('.CardAdder')) { buf.card = current;
buf.cardAdder = current; } else if (current['x-component'].endsWith('.CardAdder')) {
} else if (current['x-component'].endsWith('.CardViewer')) { buf.cardAdder = current;
buf.cardViewer = current; } else if (current['x-component'].endsWith('.CardViewer')) {
} buf.cardViewer = current;
return buf; }
}, return buf;
{ card: null, cardAdder: null, cardViewer: null }, },
), { card: null, cardAdder: null, cardViewer: null },
[], ),
); [],
const handleCardRemove = (card, column) => { );
const updatedBoard = Board.removeCard({ columns: field.value }, column, card); const handleCardRemove = (card, column) => {
field.value = updatedBoard.columns; const updatedBoard = Board.removeCard({ columns: field.value }, column, card);
}; field.value = updatedBoard.columns;
const handleCardDragEnd = (card, fromColumn, toColumn) => { };
onCardDragEnd?.({ columns: field.value, groupField }, fromColumn, toColumn); const handleCardDragEnd = (card, fromColumn, toColumn) => {
const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn); onCardDragEnd?.({ columns: field.value, groupField }, fromColumn, toColumn);
field.value = updatedBoard.columns; const updatedBoard = Board.moveCard({ columns: field.value }, fromColumn, toColumn);
}; field.value = updatedBoard.columns;
};
return ( return (
<Spin <Spin
wrapperClassName={css` wrapperClassName={css`
overflow: hidden; overflow: hidden;
height: 100%;
> .ant-spin-container {
height: 100%; height: 100%;
} > .ant-spin-container {
`} height: 100%;
spinning={field.loading || false}
>
<Board
{...restProps}
allowAddCard={!!schemas.cardAdder}
disableColumnDrag
cardAdderPosition={'bottom'}
disableCardDrag={restProps.disableCardDrag || disableCardDrag}
onCardRemove={handleCardRemove}
onCardDragEnd={handleCardDragEnd}
renderColumnHeader={({ title, color }) => (
<div className={'react-kanban-column-header'}>
<Tag color={color}>{title}</Tag>
</div>
)}
renderCard={(card, { column, dragging }) => {
const columnIndex = field.value?.indexOf(column);
const cardIndex = column?.cards?.indexOf(card);
return (
schemas.card && (
<RecordProvider record={card}>
<KanbanCardContext.Provider
value={{
setDisableCardDrag,
cardViewerSchema: schemas.cardViewer,
cardField: field,
card,
column,
dragging,
columnIndex,
cardIndex,
}}
>
<RecursionField name={schemas.card.name} schema={schemas.card} />
</KanbanCardContext.Provider>
</RecordProvider>
)
);
}}
renderCardAdder={({ column }) => {
if (!schemas.cardAdder) {
return null;
} }
return ( `}
<KanbanColumnContext.Provider value={{ column, groupField }}> spinning={field.loading || false}
<SchemaComponentOptions scope={{ useCreateActionProps }}>
<RecursionField name={schemas.cardAdder.name} schema={schemas.cardAdder} />
</SchemaComponentOptions>
</KanbanColumnContext.Provider>
);
}}
> >
{{ <Board
columns: field.value || [], {...restProps}
}} allowAddCard={!!schemas.cardAdder}
</Board> disableColumnDrag
</Spin> cardAdderPosition={'bottom'}
); disableCardDrag={restProps.disableCardDrag || disableCardDrag}
}); onCardRemove={handleCardRemove}
onCardDragEnd={handleCardDragEnd}
renderColumnHeader={({ title, color }) => (
<div className={'react-kanban-column-header'}>
<Tag color={color}>{title}</Tag>
</div>
)}
renderCard={(card, { column, dragging }) => {
const columnIndex = field.value?.indexOf(column);
const cardIndex = column?.cards?.indexOf(card);
return (
schemas.card && (
<RecordProvider record={card}>
<KanbanCardContext.Provider
value={{
setDisableCardDrag,
cardViewerSchema: schemas.cardViewer,
cardField: field,
card,
column,
dragging,
columnIndex,
cardIndex,
}}
>
<RecursionField name={schemas.card.name} schema={schemas.card} />
</KanbanCardContext.Provider>
</RecordProvider>
)
);
}}
renderCardAdder={({ column }) => {
if (!schemas.cardAdder) {
return null;
}
return (
<KanbanColumnContext.Provider value={{ column, groupField }}>
<SchemaComponentOptions scope={{ useCreateActionProps }}>
<RecursionField name={schemas.cardAdder.name} schema={schemas.cardAdder} />
</SchemaComponentOptions>
</KanbanColumnContext.Provider>
);
}}
>
{{
columns: field.value || [],
}}
</Board>
</Spin>
);
},
{ displayName: 'Kanban' },
);

View File

@ -41,44 +41,47 @@ const MarkdownEditor = (props: any) => {
); );
}; };
export const MarkdownVoid: any = observer((props: any) => { export const MarkdownVoid: any = observer(
const { content, className } = props; (props: any) => {
const field = useField(); const { content, className } = props;
const schema = useFieldSchema(); const field = useField();
const { dn } = useDesignable(); const schema = useFieldSchema();
const { onSave, onCancel } = props; const { dn } = useDesignable();
const { html, loading } = useParseMarkdown(content); const { onSave, onCancel } = props;
if (loading) { const { html, loading } = useParseMarkdown(content);
return <Spin />; if (loading) {
} return <Spin />;
return field?.editable ? ( }
<MarkdownEditor return field?.editable ? (
{...props} <MarkdownEditor
className {...props}
defaultValue={content} className
onCancel={() => { defaultValue={content}
field.editable = false; onCancel={() => {
onCancel?.(); field.editable = false;
}} onCancel?.();
onSubmit={async (value) => { }}
field.editable = false; onSubmit={async (value) => {
schema['x-component-props'] ?? (schema['x-component-props'] = {}); field.editable = false;
schema['x-component-props']['content'] = value; schema['x-component-props'] ?? (schema['x-component-props'] = {});
field.componentProps.content = value; schema['x-component-props']['content'] = value;
onSave?.(schema); field.componentProps.content = value;
dn.emit('patch', { onSave?.(schema);
schema: { dn.emit('patch', {
'x-uid': schema['x-uid'], schema: {
'x-component-props': { 'x-uid': schema['x-uid'],
content: value, 'x-component-props': {
content: value,
},
}, },
}, });
}); }}
}} />
/> ) : (
) : ( <div className={cls(['nb-markdown', className])} dangerouslySetInnerHTML={{ __html: html }} />
<div className={cls(['nb-markdown', className])} dangerouslySetInnerHTML={{ __html: html }} /> );
); },
}); { displayName: 'MarkdownVoid' },
);
MarkdownVoid.Designer = MarkdownVoidDesigner; MarkdownVoid.Designer = MarkdownVoidDesigner;

View File

@ -23,24 +23,27 @@ const schema = {
}, },
}; };
const Editable = observer((props: any) => { const Editable = observer(
const filed = useField<any>(); (props: any) => {
if (filed.editable) { const filed = useField<any>();
return props.children; if (filed.editable) {
} return props.children;
return ( }
<div> return (
<Button <div>
onClick={() => { <Button
filed.editable = true; onClick={() => {
}} filed.editable = true;
> }}
>
</Button>
<div>{props.children}</div> </Button>
</div> <div>{props.children}</div>
); </div>
}); );
},
{ displayName: 'Editable' },
);
export default () => { export default () => {
return ( return (

View File

@ -136,148 +136,149 @@ const useSideMenuRef = () => {
const MenuItemDesignerContext = createContext(null); const MenuItemDesignerContext = createContext(null);
export const Menu: ComposedMenu = observer((props) => { export const Menu: ComposedMenu = observer(
const { (props) => {
onSelect, const {
mode, onSelect,
selectedUid, mode,
defaultSelectedUid, selectedUid,
sideMenuRefScopeKey, defaultSelectedUid,
defaultSelectedKeys: dSelectedKeys, sideMenuRefScopeKey,
defaultOpenKeys: dOpenKeys, defaultSelectedKeys: dSelectedKeys,
...others defaultOpenKeys: dOpenKeys,
} = useProps(props); ...others
const { t } = useTranslation(); } = useProps(props);
const Designer = useDesigner(); const { t } = useTranslation();
const schema = useFieldSchema(); const Designer = useDesigner();
const { refresh } = useDesignable(); const schema = useFieldSchema();
const api = useAPIClient(); const { refresh } = useDesignable();
const { render } = useSchemaInitializer(schema['x-initializer']); const api = useAPIClient();
const sideMenuRef = useSideMenuRef(); const { render } = useSchemaInitializer(schema['x-initializer']);
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => { const sideMenuRef = useSideMenuRef();
if (dSelectedKeys) { const [defaultSelectedKeys, setDefaultSelectedKeys] = useState(() => {
return dSelectedKeys; if (dSelectedKeys) {
} return dSelectedKeys;
if (defaultSelectedUid) {
return findKeysByUid(schema, defaultSelectedUid);
}
return [];
});
const [loading, setLoading] = useState(false);
const [defaultOpenKeys, setDefaultOpenKeys] = useState(() => {
if (['inline', 'mix'].includes(mode)) {
return dOpenKeys || defaultSelectedKeys;
}
return dOpenKeys;
});
const [sideMenuSchema, setSideMenuSchema] = useState<Schema>(() => {
const key = defaultSelectedKeys?.[0] || null;
if (mode === 'mix' && key) {
const s = schema.properties?.[key];
if (s['x-component'] === 'Menu.SubMenu') {
return s;
} }
} if (defaultSelectedUid) {
return null; return findKeysByUid(schema, defaultSelectedUid);
}); }
useEffect(() => { return [];
const keys = findKeysByUid(schema, selectedUid); });
setDefaultSelectedKeys(keys); const [loading, setLoading] = useState(false);
if (['inline', 'mix'].includes(mode)) { const [defaultOpenKeys, setDefaultOpenKeys] = useState(() => {
setDefaultOpenKeys(dOpenKeys || keys); if (['inline', 'mix'].includes(mode)) {
} return dOpenKeys || defaultSelectedKeys;
const key = keys?.[0] || null; }
if (mode === 'mix') { return dOpenKeys;
if (key) { });
const [sideMenuSchema, setSideMenuSchema] = useState<Schema>(() => {
const key = defaultSelectedKeys?.[0] || null;
if (mode === 'mix' && key) {
const s = schema.properties?.[key]; const s = schema.properties?.[key];
if (s['x-component'] === 'Menu.SubMenu') { if (s['x-component'] === 'Menu.SubMenu') {
setSideMenuSchema(s); return s;
} }
} else {
setSideMenuSchema(null);
} }
} return null;
}, [selectedUid]); });
useEffect(() => { useEffect(() => {
if (['inline', 'mix'].includes(mode)) { const keys = findKeysByUid(schema, selectedUid);
setDefaultOpenKeys(defaultSelectedKeys); setDefaultSelectedKeys(keys);
} if (['inline', 'mix'].includes(mode)) {
}, [defaultSelectedKeys]); setDefaultOpenKeys(dOpenKeys || keys);
useEffect(() => { }
const sideMenuElement = sideMenuRef?.current as HTMLElement; const key = keys?.[0] || null;
if (!sideMenuElement) { if (mode === 'mix') {
return; if (key) {
} const s = schema.properties?.[key];
sideMenuElement.style.display = sideMenuSchema?.['x-component'] === 'Menu.SubMenu' ? 'block' : 'none'; if (s['x-component'] === 'Menu.SubMenu') {
}, [sideMenuSchema?.name, sideMenuRef]); setSideMenuSchema(s);
const { designable } = useDesignable(); }
return ( } else {
<DndContext> setSideMenuSchema(null);
<MenuItemDesignerContext.Provider value={Designer}> }
<MenuModeContext.Provider value={mode}> }
<AntdMenu }, [selectedUid]);
{...others} useEffect(() => {
style={ if (['inline', 'mix'].includes(mode)) {
{ setDefaultOpenKeys(defaultSelectedKeys);
// width: mode === 'mix' ? '100%' : undefined, }
} }, [defaultSelectedKeys]);
} useEffect(() => {
className={css` const sideMenuElement = sideMenuRef?.current as HTMLElement;
.ant-menu-item:hover { if (!sideMenuElement) {
> .ant-menu-title-content > div { return;
.general-schema-designer { }
display: block; sideMenuElement.style.display = sideMenuSchema?.['x-component'] === 'Menu.SubMenu' ? 'block' : 'none';
} }, [sideMenuSchema?.name, sideMenuRef]);
const { designable } = useDesignable();
return (
<DndContext>
<MenuItemDesignerContext.Provider value={Designer}>
<MenuModeContext.Provider value={mode}>
<AntdMenu
{...others}
style={
{
// width: mode === 'mix' ? '100%' : undefined,
} }
} }
`} className={css`
onSelect={(info: any) => { .ant-menu-item:hover {
const s = schema.properties[info.key]; > .ant-menu-title-content > div {
if (mode === 'mix') { .general-schema-designer {
setSideMenuSchema(s); display: block;
if (s['x-component'] !== 'Menu.SubMenu') { }
onSelect && onSelect(info);
} else {
const menuItemSchema = findMenuItem(s);
if (!menuItemSchema) {
return;
} }
// TODO }
setLoading(true); `}
const keys = findKeysByUid(schema, menuItemSchema['x-uid']); onSelect={(info: any) => {
setDefaultSelectedKeys(keys); const s = schema.properties[info.key];
setTimeout(() => { if (mode === 'mix') {
setLoading(false); setSideMenuSchema(s);
}, 100); if (s['x-component'] !== 'Menu.SubMenu') {
onSelect && onSelect && onSelect(info);
onSelect({ } else {
key: menuItemSchema.name, const menuItemSchema = findMenuItem(s);
item: { if (!menuItemSchema) {
props: { return;
schema: menuItemSchema, }
// TODO
setLoading(true);
const keys = findKeysByUid(schema, menuItemSchema['x-uid']);
setDefaultSelectedKeys(keys);
setTimeout(() => {
setLoading(false);
}, 100);
onSelect &&
onSelect({
key: menuItemSchema.name,
item: {
props: {
schema: menuItemSchema,
},
}, },
}, });
}); }
} else {
onSelect && onSelect(info);
} }
} else { }}
onSelect && onSelect(info); mode={mode === 'mix' ? 'horizontal' : mode}
} defaultOpenKeys={defaultOpenKeys}
}} defaultSelectedKeys={defaultSelectedKeys}
mode={mode === 'mix' ? 'horizontal' : mode} selectedKeys={defaultSelectedKeys}
defaultOpenKeys={defaultOpenKeys} >
defaultSelectedKeys={defaultSelectedKeys} {designable && (
selectedKeys={defaultSelectedKeys} <AntdMenu.Item disabled key="x-designer-button" style={{ padding: '0 8px', order: 9999 }}>
> {render({ style: { background: 'none' } })}
{designable && ( </AntdMenu.Item>
<AntdMenu.Item disabled key="x-designer-button" style={{ padding: '0 8px', order: 9999 }}> )}
{render({ style: { background: 'none' } })} {props.children}
</AntdMenu.Item> </AntdMenu>
)} {loading
{props.children} ? null
</AntdMenu> : mode === 'mix' &&
{loading
? null
: mode === 'mix' &&
sideMenuSchema?.['x-component'] === 'Menu.SubMenu' && sideMenuSchema?.['x-component'] === 'Menu.SubMenu' &&
sideMenuRef?.current?.firstChild && sideMenuRef?.current?.firstChild &&
createPortal( createPortal(
@ -290,37 +291,37 @@ export const Menu: ComposedMenu = observer((props) => {
onSelect && onSelect(info); onSelect && onSelect(info);
}} }}
className={css` className={css`
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
.ant-menu-item { .ant-menu-item {
> .ant-menu-title-content { > .ant-menu-title-content {
margin-left: -24px; margin-left: -24px;
margin-right: -16px; margin-right: -16px;
padding: 0 16px 0 24px; padding: 0 16px 0 24px;
> div { > div {
> .general-schema-designer { > .general-schema-designer {
right: 6px !important; right: 6px !important;
}
} }
} }
} }
} .ant-menu-submenu-title {
.ant-menu-submenu-title { .ant-menu-title-content {
.ant-menu-title-content { margin-left: -24px;
margin-left: -24px; margin-right: -34px;
margin-right: -34px; padding: 0 34px 0 24px;
padding: 0 34px 0 24px; > div {
> div { > .general-schema-designer {
> .general-schema-designer { right: 6px !important;
right: 6px !important; }
} > span.anticon {
> span.anticon { margin-right: 10px;
margin-right: 10px; }
} }
} }
} }
} `}
`}
> >
<RecursionField schema={sideMenuSchema} onlyRenderProperties /> <RecursionField schema={sideMenuSchema} onlyRenderProperties />
{render({ {render({
@ -340,117 +341,128 @@ export const Menu: ComposedMenu = observer((props) => {
</MenuModeContext.Provider>, </MenuModeContext.Provider>,
sideMenuRef.current.firstChild, sideMenuRef.current.firstChild,
)} )}
</MenuModeContext.Provider> </MenuModeContext.Provider>
</MenuItemDesignerContext.Provider> </MenuItemDesignerContext.Provider>
</DndContext> </DndContext>
); );
}); },
{ displayName: 'Menu' },
);
Menu.Item = observer((props) => { Menu.Item = observer(
const { icon, ...others } = props; (props) => {
const schema = useFieldSchema(); const { icon, ...others } = props;
const field = useField(); const schema = useFieldSchema();
const Designer = useContext(MenuItemDesignerContext); const field = useField();
return ( const Designer = useContext(MenuItemDesignerContext);
<AntdMenu.Item return (
{...others} <AntdMenu.Item
className={css` {...others}
:active { className={css`
background: inherit; :active {
} background: inherit;
`} }
key={schema.name} `}
eventKey={schema.name} key={schema.name}
schema={schema} eventKey={schema.name}
> schema={schema}
<SortableItem className={designerCss} removeParentsIfNoChildren={false}> >
<Icon type={icon} /> <SortableItem className={designerCss} removeParentsIfNoChildren={false}>
<span
className={css`
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
vertical-align: middle;
`}
>
{field.title}
</span>
<Designer />
</SortableItem>
</AntdMenu.Item>
);
});
Menu.URL = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
return (
<AntdMenu.Item
{...others}
className={css`
:active {
background: inherit;
}
`}
key={schema.name}
eventKey={schema.name}
schema={schema}
onClick={() => {
window.open(props.href, '_blank');
}}
>
<SortableItem className={designerCss} removeParentsIfNoChildren={false}>
<Icon type={icon} />
<span
className={css`
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
vertical-align: middle;
`}
>
{field.title}
</span>
<Designer />
</SortableItem>
</AntdMenu.Item>
);
});
Menu.SubMenu = observer((props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const mode = useContext(MenuModeContext);
const Designer = useContext(MenuItemDesignerContext);
if (mode === 'mix') {
return <Menu.Item {...props} />;
}
return (
<AntdMenu.SubMenu
{...others}
className={css`
:active {
background: inherit;
}
`}
key={schema.name}
eventKey={schema.name}
title={
<SortableItem className={subMenuDesignerCss} removeParentsIfNoChildren={false}>
<Icon type={icon} /> <Icon type={icon} />
{field.title} <span
className={css`
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
vertical-align: middle;
`}
>
{field.title}
</span>
<Designer /> <Designer />
</SortableItem> </SortableItem>
} </AntdMenu.Item>
> );
<RecursionField schema={schema} onlyRenderProperties /> },
</AntdMenu.SubMenu> { displayName: 'MenuItem' },
); );
});
Menu.URL = observer(
(props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const Designer = useContext(MenuItemDesignerContext);
return (
<AntdMenu.Item
{...others}
className={css`
:active {
background: inherit;
}
`}
key={schema.name}
eventKey={schema.name}
schema={schema}
onClick={() => {
window.open(props.href, '_blank');
}}
>
<SortableItem className={designerCss} removeParentsIfNoChildren={false}>
<Icon type={icon} />
<span
className={css`
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 100%;
vertical-align: middle;
`}
>
{field.title}
</span>
<Designer />
</SortableItem>
</AntdMenu.Item>
);
},
{ displayName: 'MenuURL' },
);
Menu.SubMenu = observer(
(props) => {
const { icon, ...others } = props;
const schema = useFieldSchema();
const field = useField();
const mode = useContext(MenuModeContext);
const Designer = useContext(MenuItemDesignerContext);
if (mode === 'mix') {
return <Menu.Item {...props} />;
}
return (
<AntdMenu.SubMenu
{...others}
className={css`
:active {
background: inherit;
}
`}
key={schema.name}
eventKey={schema.name}
title={
<SortableItem className={subMenuDesignerCss} removeParentsIfNoChildren={false}>
<Icon type={icon} />
{field.title}
<Designer />
</SortableItem>
}
>
<RecursionField schema={schema} onlyRenderProperties />
</AntdMenu.SubMenu>
);
},
{ displayName: 'MenuSubMenu' },
);
Menu.Designer = MenuDesigner; Menu.Designer = MenuDesigner;

View File

@ -3,20 +3,23 @@ import { Pagination as AntdPagination } from 'antd';
import React, { KeyboardEventHandler } from 'react'; import React, { KeyboardEventHandler } from 'react';
import { useProps } from '../../hooks/useProps'; import { useProps } from '../../hooks/useProps';
export const Pagination = observer((props: any) => { export const Pagination = observer(
const { hidden, ...others } = useProps(props); (props: any) => {
if (hidden) { const { hidden, ...others } = useProps(props);
return null; if (hidden) {
} return null;
const onKeypress: KeyboardEventHandler<HTMLDivElement> = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
} }
}; const onKeypress: KeyboardEventHandler<HTMLDivElement> = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
}
};
return ( return (
<div onKeyPress={onKeypress}> <div onKeyPress={onKeypress}>
<AntdPagination {...others} /> <AntdPagination {...others} />
</div> </div>
); );
}); },
{ displayName: 'Pagination' },
);

View File

@ -25,98 +25,101 @@ const toValue = (value, placeholder) => {
return value; return value;
}; };
export const ReadPrettyRecordPicker: React.FC = observer((props: any) => { export const ReadPrettyRecordPicker: React.FC = observer(
const { ellipsis } = props; (props: any) => {
const fieldSchema = useFieldSchema(); const { ellipsis } = props;
const recordCtx = useRecord(); const fieldSchema = useFieldSchema();
const { getCollectionJoinField } = useCollectionManager(); const recordCtx = useRecord();
// value 做了转换,但 props.value 和原来 useField().value 的值不一致 const { getCollectionJoinField } = useCollectionManager();
// const field = useField<Field>(); // value 做了转换,但 props.value 和原来 useField().value 的值不一致
const fieldNames = useFieldNames(props); // const field = useField<Field>();
const [visible, setVisible] = useState(false); const fieldNames = useFieldNames(props);
const { getField } = useCollection(); const [visible, setVisible] = useState(false);
const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']); const { getField } = useCollection();
const [record, setRecord] = useState({}); const collectionField = getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']);
const compile = useCompile(); const [record, setRecord] = useState({});
const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label'); const compile = useCompile();
const showFilePicker = isShowFilePicker(labelUiSchema); const labelUiSchema = useLabelUiSchema(collectionField, fieldNames?.label || 'label');
const { snapshot } = useActionContext(); const showFilePicker = isShowFilePicker(labelUiSchema);
const isTagsMode = fieldSchema['x-component-props']?.mode === 'tags'; const { snapshot } = useActionContext();
const isTagsMode = fieldSchema['x-component-props']?.mode === 'tags';
const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>(); const ellipsisWithTooltipRef = useRef<IEllipsisWithTooltipRef>();
if (showFilePicker) { if (showFilePicker) {
return collectionField ? <Preview {...props} /> : null; return collectionField ? <Preview {...props} /> : null;
} }
const renderRecords = () => const renderRecords = () =>
toArr(props.value).map((record, index, arr) => { toArr(props.value).map((record, index, arr) => {
const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A'); const val = toValue(compile(record?.[fieldNames?.label || 'label']), 'N/A');
const text = getLabelFormatValue(labelUiSchema, val, true); const text = getLabelFormatValue(labelUiSchema, val, true);
return ( return (
<Fragment key={`${record.id}_${index}`}> <Fragment key={`${record.id}_${index}`}>
<span> <span>
{snapshot || isTagsMode ? ( {snapshot || isTagsMode ? (
text text
) : ( ) : (
<a <a
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
setVisible(true); setVisible(true);
setRecord(record); setRecord(record);
ellipsisWithTooltipRef?.current?.setPopoverVisible(false); ellipsisWithTooltipRef?.current?.setPopoverVisible(false);
}} }}
> >
{text} {text}
</a> </a>
)} )}
</span> </span>
{index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null} {index < arr.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : null}
</Fragment> </Fragment>
); );
}); });
const renderWithoutTableFieldResourceProvider = () => ( const renderWithoutTableFieldResourceProvider = () => (
<WithoutTableFieldResource.Provider value={true}> <WithoutTableFieldResource.Provider value={true}>
<FormProvider> <FormProvider>
<RecursionField <RecursionField
schema={fieldSchema} schema={fieldSchema}
onlyRenderProperties onlyRenderProperties
filterProperties={(s) => { filterProperties={(s) => {
return s['x-component'] === 'RecordPicker.Viewer'; return s['x-component'] === 'RecordPicker.Viewer';
}} }}
/> />
</FormProvider> </FormProvider>
</WithoutTableFieldResource.Provider> </WithoutTableFieldResource.Provider>
);
const renderRecordProvider = () => {
const collectionFieldNames = fieldSchema?.['x-collection-field']?.split('.');
return collectionFieldNames && collectionFieldNames.length > 2 ? (
<RecordProvider record={recordCtx[collectionFieldNames[1]]}>
<RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
</RecordProvider>
) : (
<RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
); );
};
return collectionField ? ( const renderRecordProvider = () => {
<div> const collectionFieldNames = fieldSchema?.['x-collection-field']?.split('.');
<BlockAssociationContext.Provider value={`${collectionField.collectionName}.${collectionField.name}`}>
<CollectionProvider name={collectionField.target ?? collectionField.targetCollection}> return collectionFieldNames && collectionFieldNames.length > 2 ? (
<EllipsisWithTooltip ellipsis={ellipsis} ref={ellipsisWithTooltipRef}> <RecordProvider record={recordCtx[collectionFieldNames[1]]}>
{renderRecords()} <RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
</EllipsisWithTooltip> </RecordProvider>
<ActionContext.Provider ) : (
value={{ visible, setVisible, openMode: 'drawer', snapshot: collectionField.interface === 'snapshot' }} <RecordProvider record={record}>{renderWithoutTableFieldResourceProvider()}</RecordProvider>
> );
{renderRecordProvider()} };
</ActionContext.Provider>
</CollectionProvider> return collectionField ? (
</BlockAssociationContext.Provider> <div>
</div> <BlockAssociationContext.Provider value={`${collectionField.collectionName}.${collectionField.name}`}>
) : null; <CollectionProvider name={collectionField.target ?? collectionField.targetCollection}>
}); <EllipsisWithTooltip ellipsis={ellipsis} ref={ellipsisWithTooltipRef}>
{renderRecords()}
</EllipsisWithTooltip>
<ActionContext.Provider
value={{ visible, setVisible, openMode: 'drawer', snapshot: collectionField.interface === 'snapshot' }}
>
{renderRecordProvider()}
</ActionContext.Provider>
</CollectionProvider>
</BlockAssociationContext.Provider>
</div>
) : null;
},
{ displayName: 'ReadPrettyRecordPicker' },
);

View File

@ -6,19 +6,20 @@ import { useRequest } from '../../../api-client';
import { useRecord } from '../../../record-provider'; import { useRecord } from '../../../record-provider';
import { useActionContext } from '../action'; import { useActionContext } from '../action';
export const ReadPretty = observer((props: any) => { export const ReadPretty = observer(
const fieldNames = { ...defaultFieldNames, ...props.fieldNames }; (props: any) => {
const field = useField<any>(); const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
const fieldSchema = useFieldSchema(); const field = useField<any>();
const record = useRecord(); const fieldSchema = useFieldSchema();
const { snapshot } = useActionContext(); const record = useRecord();
const { snapshot } = useActionContext();
const { data } = useRequest( const { data } = useRequest(
snapshot snapshot
? async () => ({ ? async () => ({
data: record[fieldSchema.name], data: record[fieldSchema.name],
}) })
: { : {
action: 'list', action: 'list',
...props.service, ...props.service,
params: { params: {
@ -30,10 +31,12 @@ export const ReadPretty = observer((props: any) => {
}, },
}, },
}, },
{ {
refreshDeps: [props.service, field.value], refreshDeps: [props.service, field.value],
}, },
); );
return <Select.ReadPretty {...props} options={data?.data}></Select.ReadPretty>; return <Select.ReadPretty {...props} options={data?.data}></Select.ReadPretty>;
}); },
{ displayName: 'ReadPretty' },
);

View File

@ -12,29 +12,32 @@ type Composed = {
Object?: React.FC<any>; Object?: React.FC<any>;
}; };
export const ReadPretty = observer((props: any) => { export const ReadPretty = observer(
const fieldNames = { ...defaultFieldNames, ...props.fieldNames }; (props: any) => {
const field = useField<any>(); const fieldNames = { ...defaultFieldNames, ...props.fieldNames };
const compile = useCompile(); const field = useField<any>();
const compile = useCompile();
if (!isValid(props.value)) { if (!isValid(props.value)) {
return <div />; return <div />;
} }
if (isArrayField(field) && field?.value?.length === 0) { if (isArrayField(field) && field?.value?.length === 0) {
return <div />; return <div />;
} }
const dataSource = field.dataSource || props.options || []; const dataSource = field.dataSource || props.options || [];
const options = getCurrentOptions(field.value, dataSource, fieldNames); const options = getCurrentOptions(field.value, dataSource, fieldNames);
return ( return (
<div> <div>
<EllipsisWithTooltip ellipsis={props.ellipsis}> <EllipsisWithTooltip ellipsis={props.ellipsis}>
{options.map((option, key) => ( {options.map((option, key) => (
<Tag key={key} color={option[fieldNames.color]} icon={option.icon}> <Tag key={key} color={option[fieldNames.color]} icon={option.icon}>
{compile(option[fieldNames.label])} {compile(option[fieldNames.label])}
</Tag> </Tag>
))} ))}
</EllipsisWithTooltip> </EllipsisWithTooltip>
</div> </div>
); );
}); },
{ displayName: 'ReadPretty' },
);

View File

@ -42,12 +42,15 @@ export const designerCss = css`
} }
`; `;
export const TableColumnActionBar = observer((props) => { export const TableColumnActionBar = observer(
const Designer = useDesigner(); (props) => {
return ( const Designer = useDesigner();
<SortableItem className={designerCss}> return (
<Designer /> <SortableItem className={designerCss}>
{props.children} <Designer />
</SortableItem> {props.children}
); </SortableItem>
}); );
},
{ displayName: 'TableColumnActionBar' },
);

View File

@ -163,164 +163,165 @@ const useValidator = (validator: (value: any) => string) => {
}, []); }, []);
}; };
export const Table: any = observer((props: any) => { export const Table: any = observer(
const { pagination: pagination1, useProps, onChange, ...others1 } = props; (props: any) => {
const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {}; const { pagination: pagination1, useProps, onChange, ...others1 } = props;
const { const { pagination: pagination2, onClickRow, ...others2 } = useProps?.() || {};
dragSort = false, const {
showIndex = true, dragSort = false,
onRowSelectionChange, showIndex = true,
onChange: onTableChange, onRowSelectionChange,
rowSelection, onChange: onTableChange,
rowKey, rowSelection,
required, rowKey,
onExpand, required,
...others onExpand,
} = { ...others1, ...others2 } as any; ...others
const field = useArrayField(others); } = { ...others1, ...others2 } as any;
const columns = useTableColumns(others); const field = useArrayField(others);
const schema = useFieldSchema(); const columns = useTableColumns(others);
const isTableSelector = schema?.parent?.['x-decorator'] === 'TableSelectorProvider'; const schema = useFieldSchema();
const ctx = isTableSelector ? useTableSelectorContext() : useTableBlockContext(); const isTableSelector = schema?.parent?.['x-decorator'] === 'TableSelectorProvider';
const { expandFlag } = ctx; const ctx = isTableSelector ? useTableSelectorContext() : useTableBlockContext();
const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => {})); const { expandFlag } = ctx;
const paginationProps = usePaginationProps(pagination1, pagination2); const onRowDragEnd = useMemoizedFn(others.onRowDragEnd || (() => { }));
const requiredValidator = field.required || required; const paginationProps = usePaginationProps(pagination1, pagination2);
const { treeTable } = schema?.parent?.['x-decorator-props'] || {}; const requiredValidator = field.required || required;
const [expandedKeys, setExpandesKeys] = useState([]); const { treeTable } = schema?.parent?.['x-decorator-props'] || {};
const [allIncludesChildren, setAllIncludesChildren] = useState([]); const [expandedKeys, setExpandesKeys] = useState([]);
const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>(field?.data?.selectedRowKeys || []); const [allIncludesChildren, setAllIncludesChildren] = useState([]);
const [selectedRow, setSelectedRow] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState<any[]>(field?.data?.selectedRowKeys || []);
const [selectedRow, setSelectedRow] = useState([]);
let onRow = null, let onRow = null,
highlightRow = ''; highlightRow = '';
if (onClickRow) { if (onClickRow) {
onRow = (record) => { onRow = (record) => {
return { return {
onClick: () => onClickRow(record, setSelectedRow, selectedRow), onClick: () => onClickRow(record, setSelectedRow, selectedRow),
};
}; };
}; highlightRow = css`
highlightRow = css` & > td {
& > td { background-color: #caedff !important;
background-color: #caedff !important; }
} &:hover > td {
&:hover > td { background-color: #caedff !important;
background-color: #caedff !important; }
} `;
`;
}
useEffect(() => {
field.setValidator((value) => {
if (requiredValidator) {
return Array.isArray(value) && value.length > 0 ? null : 'The field value is required';
}
return;
});
}, [requiredValidator]);
useEffect(() => {
if (treeTable !== false) {
const keys = getIdsWithChildren(field.value?.slice?.());
setAllIncludesChildren(keys);
} }
}, [field.value]);
useEffect(() => {
if (expandFlag) {
setExpandesKeys(allIncludesChildren);
} else {
setExpandesKeys([]);
}
}, [expandFlag, allIncludesChildren]);
const components = useMemo(() => { useEffect(() => {
return { field.setValidator((value) => {
header: { if (requiredValidator) {
wrapper: (props) => { return Array.isArray(value) && value.length > 0 ? null : 'The field value is required';
return ( }
<DndContext> return;
<thead {...props} /> });
</DndContext> }, [requiredValidator]);
);
useEffect(() => {
if (treeTable !== false) {
const keys = getIdsWithChildren(field.value?.slice?.());
setAllIncludesChildren(keys);
}
}, [field.value]);
useEffect(() => {
if (expandFlag) {
setExpandesKeys(allIncludesChildren);
} else {
setExpandesKeys([]);
}
}, [expandFlag, allIncludesChildren]);
const components = useMemo(() => {
return {
header: {
wrapper: (props) => {
return (
<DndContext>
<thead {...props} />
</DndContext>
);
},
cell: (props) => {
return (
<th
{...props}
className={cls(
props.className,
css`
max-width: 300px;
white-space: nowrap;
&:hover .general-schema-designer {
display: block;
}
`,
)}
/>
);
},
}, },
cell: (props) => { body: {
return ( wrapper: (props) => {
<th return (
<DndContext
onDragEnd={(e) => {
if (!e.active || !e.over) {
console.warn('move cancel');
return;
}
const fromIndex = e.active?.data.current?.sortable?.index;
const toIndex = e.over?.data.current?.sortable?.index;
const from = field.value[fromIndex];
const to = field.value[toIndex];
field.move(fromIndex, toIndex);
onRowDragEnd({ fromIndex, toIndex, from, to });
}}
>
<tbody {...props} />
</DndContext>
);
},
row: (props) => {
return <SortableRow {...props}></SortableRow>;
},
cell: (props) => (
<td
{...props} {...props}
className={cls( className={classNames(
props.className, props.className,
css` css`
max-width: 300px; max-width: 300px;
white-space: nowrap; white-space: nowrap;
&:hover .general-schema-designer { .nb-read-pretty-input-number {
display: block; text-align: right;
} }
`, `,
)} )}
/> />
); ),
}, },
}, };
body: { }, [field, onRowDragEnd, dragSort]);
wrapper: (props) => {
return (
<DndContext
onDragEnd={(e) => {
if (!e.active || !e.over) {
console.warn('move cancel');
return;
}
const fromIndex = e.active?.data.current?.sortable?.index; const defaultRowKey = (record: any) => {
const toIndex = e.over?.data.current?.sortable?.index; return field.value?.indexOf?.(record);
const from = field.value[fromIndex];
const to = field.value[toIndex];
field.move(fromIndex, toIndex);
onRowDragEnd({ fromIndex, toIndex, from, to });
}}
>
<tbody {...props} />
</DndContext>
);
},
row: (props) => {
return <SortableRow {...props}></SortableRow>;
},
cell: (props) => (
<td
{...props}
className={classNames(
props.className,
css`
max-width: 300px;
white-space: nowrap;
.nb-read-pretty-input-number {
text-align: right;
}
`,
)}
/>
),
},
}; };
}, [field, onRowDragEnd, dragSort]);
const defaultRowKey = (record: any) => { const getRowKey = (record: any) => {
return field.value?.indexOf?.(record); if (typeof rowKey === 'string') {
}; return record[rowKey]?.toString();
} else {
return (rowKey ?? defaultRowKey)(record)?.toString();
}
};
const getRowKey = (record: any) => { const restProps = {
if (typeof rowKey === 'string') { rowSelection: rowSelection
return record[rowKey]?.toString(); ? {
} else {
return (rowKey ?? defaultRowKey)(record)?.toString();
}
};
const restProps = {
rowSelection: rowSelection
? {
type: 'checkbox', type: 'checkbox',
selectedRowKeys: selectedRowKeys, selectedRowKeys: selectedRowKeys,
onChange(selectedRowKeys: any[], selectedRows: any[]) { onChange(selectedRowKeys: any[], selectedRows: any[]) {
@ -348,40 +349,40 @@ export const Table: any = observer((props: any) => {
className={classNames( className={classNames(
checked ? 'checked' : null, checked ? 'checked' : null,
css` css`
position: relative; position: relative;
display: flex; display: flex;
float: left; float: left;
align-items: center; align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
padding-right: 8px; padding-right: 8px;
.nb-table-index {
opacity: 0;
}
&:not(.checked) {
.nb-table-index {
opacity: 1;
}
}
&:hover {
.nb-table-index { .nb-table-index {
opacity: 0; opacity: 0;
} }
.nb-origin-node { &:not(.checked) {
display: block; .nb-table-index {
opacity: 1;
}
} }
} &:hover {
`, .nb-table-index {
opacity: 0;
}
.nb-origin-node {
display: block;
}
}
`,
)} )}
> >
<div <div
className={classNames( className={classNames(
checked ? 'checked' : null, checked ? 'checked' : null,
css` css`
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
`, `,
)} )}
> >
{dragSort && <SortHandle id={getRowKey(record)} />} {dragSort && <SortHandle id={getRowKey(record)} />}
@ -392,13 +393,13 @@ export const Table: any = observer((props: any) => {
'nb-origin-node', 'nb-origin-node',
checked ? 'checked' : null, checked ? 'checked' : null,
css` css`
position: absolute; position: absolute;
right: 50%; right: 50%;
transform: translateX(50%); transform: translateX(50%);
&:not(.checked) { &:not(.checked) {
display: none; display: none;
} }
`, `,
)} )}
> >
{originNode} {originNode}
@ -408,94 +409,96 @@ export const Table: any = observer((props: any) => {
}, },
...rowSelection, ...rowSelection,
} }
: undefined, : undefined,
}; };
const SortableWrapper = useCallback<React.FC>( const SortableWrapper = useCallback<React.FC>(
({ children }) => { ({ children }) => {
return dragSort return dragSort
? React.createElement(SortableContext, { ? React.createElement(SortableContext, {
items: field.value.map(getRowKey), items: field.value.map(getRowKey),
children: children, children: children,
}) })
: React.createElement(React.Fragment, { : React.createElement(React.Fragment, {
children, children,
}); });
}, },
[field, dragSort], [field, dragSort],
); );
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const fixedBlock = fieldSchema?.parent?.['x-decorator-props']?.fixedBlock; const fixedBlock = fieldSchema?.parent?.['x-decorator-props']?.fixedBlock;
const { height: tableHeight, tableSizeRefCallback } = useTableSize(); const { height: tableHeight, tableSizeRefCallback } = useTableSize();
const scroll = useMemo(() => { const scroll = useMemo(() => {
return fixedBlock return fixedBlock
? { ? {
x: 'max-content', x: 'max-content',
y: tableHeight, y: tableHeight,
} }
: { : {
x: 'max-content', x: 'max-content',
}; };
}, [fixedBlock, tableHeight]); }, [fixedBlock, tableHeight]);
return ( return (
<div <div
className={css` className={css`
height: 100%;
overflow: hidden;
.ant-table-wrapper {
height: 100%; height: 100%;
.ant-spin-nested-loading { overflow: hidden;
.ant-table-wrapper {
height: 100%; height: 100%;
.ant-spin-container { .ant-spin-nested-loading {
height: 100%; height: 100%;
display: flex; .ant-spin-container {
flex-direction: column; height: 100%;
display: flex;
flex-direction: column;
}
} }
} }
} .ant-table {
.ant-table { overflow-x: auto;
overflow-x: auto; overflow-y: hidden;
overflow-y: hidden; }
} `}
`} >
> <SortableWrapper>
<SortableWrapper> <AntdTable
<AntdTable ref={tableSizeRefCallback}
ref={tableSizeRefCallback} rowKey={rowKey ?? defaultRowKey}
rowKey={rowKey ?? defaultRowKey} dataSource={field?.value?.slice?.()}
dataSource={field?.value?.slice?.()} {...others}
{...others} {...restProps}
{...restProps} pagination={paginationProps}
pagination={paginationProps} components={components}
components={components} onChange={(pagination, filters, sorter, extra) => {
onChange={(pagination, filters, sorter, extra) => { onTableChange?.(pagination, filters, sorter, extra);
onTableChange?.(pagination, filters, sorter, extra); }}
}} onRow={onRow}
onRow={onRow} rowClassName={(record) => (selectedRow.includes(record[rowKey]) ? highlightRow : '')}
rowClassName={(record) => (selectedRow.includes(record[rowKey]) ? highlightRow : '')} tableLayout={'auto'}
tableLayout={'auto'} scroll={scroll}
scroll={scroll} columns={columns}
columns={columns} expandable={{
expandable={{ onExpand: (flag, record) => {
onExpand: (flag, record) => { const newKeys = flag ? [...expandedKeys, record.id] : expandedKeys.filter((i) => record.id !== i);
const newKeys = flag ? [...expandedKeys, record.id] : expandedKeys.filter((i) => record.id !== i); setExpandesKeys(newKeys);
setExpandesKeys(newKeys); onExpand?.(flag, record);
onExpand?.(flag, record); },
}, expandedRowKeys: expandedKeys,
expandedRowKeys: expandedKeys, }}
}} />
/> </SortableWrapper>
</SortableWrapper> {field.errors.length > 0 && (
{field.errors.length > 0 && ( <div className="ant-formily-item-error-help ant-formily-item-help ant-formily-item-help-enter ant-formily-item-help-enter-active">
<div className="ant-formily-item-error-help ant-formily-item-help ant-formily-item-help-enter ant-formily-item-help-enter-active"> {field.errors.map((error) => {
{field.errors.map((error) => { return error.messages.map((message) => <div>{message}</div>);
return error.messages.map((message) => <div>{message}</div>); })}
})} </div>
</div> )}
)} </div>
</div> );
); },
}); { displayName: 'Table' },
);

View File

@ -6,36 +6,42 @@ import { useCollection } from '../../../collection-manager';
import { useCompile } from '../../hooks'; import { useCompile } from '../../hooks';
import { ActionBar } from '../action'; import { ActionBar } from '../action';
export const TableField: any = observer((props) => { export const TableField: any = observer(
const fieldSchema = useFieldSchema(); (props) => {
const { getField } = useCollection(); const fieldSchema = useFieldSchema();
const field = useField<Field>(); const { getField } = useCollection();
const collectionField = getField(fieldSchema.name); const field = useField<Field>();
const compile = useCompile(); const collectionField = getField(fieldSchema.name);
const ctx = useFormBlockContext(); const compile = useCompile();
useEffect(() => { const ctx = useFormBlockContext();
if (!field.title) { useEffect(() => {
field.title = compile(collectionField?.uiSchema?.title); if (!field.title) {
} field.title = compile(collectionField?.uiSchema?.title);
if (ctx?.field) { }
ctx.field.added = ctx.field.added || new Set(); if (ctx?.field) {
ctx.field.added.add(fieldSchema.name); ctx.field.added = ctx.field.added || new Set();
} ctx.field.added.add(fieldSchema.name);
}, []); }
useEffect(() => { }, []);
field.decoratorProps.asterisk = fieldSchema.required; useEffect(() => {
}, [fieldSchema.required]); field.decoratorProps.asterisk = fieldSchema.required;
return <div>{props.children}</div>; }, [fieldSchema.required]);
}); return <div>{props.children}</div>;
},
{ displayName: 'TableField' },
);
TableField.ActionBar = observer((props) => { TableField.ActionBar = observer(
const form = useForm(); (props) => {
if (form.readPretty) { const form = useForm();
return null; if (form.readPretty) {
} return null;
return ( }
<div style={{ marginBottom: 8, marginTop: -32 }}> return (
<ActionBar {...props} /> <div style={{ marginBottom: 8, marginTop: -32 }}>
</div> <ActionBar {...props} />
); </div>
}); );
},
{ displayName: 'TableField.ActionBar' },
);

View File

@ -4,27 +4,30 @@ import { SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
import { Table, TableColumnType } from 'antd'; import { Table, TableColumnType } from 'antd';
import React from 'react'; import React from 'react';
const ArrayTable = observer((props: any) => { const ArrayTable = observer(
const { rowKey } = props; (props: any) => {
const field = useField<ArrayField>(); const { rowKey } = props;
const schema = useFieldSchema(); const field = useField<ArrayField>();
const columnSchemas = schema.reduceProperties((buf, s) => { const schema = useFieldSchema();
if (s['x-component'] === 'ArrayTable.Column') { const columnSchemas = schema.reduceProperties((buf, s) => {
buf.push(s); if (s['x-component'] === 'ArrayTable.Column') {
} buf.push(s);
return buf; }
}, []); return buf;
}, []);
const columns = columnSchemas.map((s) => { const columns = columnSchemas.map((s) => {
return { return {
render: (value, record) => { render: (value, record) => {
return <RecursionField name={record.__path} schema={s} onlyRenderProperties />; return <RecursionField name={record.__path} schema={s} onlyRenderProperties />;
}, },
} as TableColumnType<any>; } as TableColumnType<any>;
}); });
return <Table {...props} rowKey={rowKey} columns={columns} dataSource={field.value} />; return <Table {...props} rowKey={rowKey} columns={columns} dataSource={field.value} />;
}); },
{ displayName: 'ArrayTable' },
);
const Value = connect((props) => { const Value = connect((props) => {
return <li>value: {props.value}</li>; return <li>value: {props.value}</li>;

View File

@ -151,28 +151,29 @@ const useDefAction = () => {
}; };
}; };
export const TableArray: React.FC<any> = observer((props) => { export const TableArray: React.FC<any> = observer(
const field = useField<ArrayField>(); (props) => {
const columns = useTableColumns(); const field = useField<ArrayField>();
const { const columns = useTableColumns();
dragSort = false, const {
showIndex = true, dragSort = false,
useSelectedRowKeys = useDef, showIndex = true,
useDataSource = useDefDataSource, useSelectedRowKeys = useDef,
useAction = useDefAction, useDataSource = useDefDataSource,
onChange, useAction = useDefAction,
...others onChange,
} = props; ...others
const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys(); } = props;
useDataSource({ const [selectedRowKeys, setSelectedRowKeys] = useSelectedRowKeys();
onSuccess(data) { useDataSource({
field.value = data?.data || []; onSuccess(data) {
}, field.value = data?.data || [];
}); },
const { move } = useAction(); });
const restProps = { const { move } = useAction();
rowSelection: props.rowSelection const restProps = {
? { rowSelection: props.rowSelection
? {
type: 'checkbox', type: 'checkbox',
selectedRowKeys, selectedRowKeys,
onChange(selectedRowKeys: any[]) { onChange(selectedRowKeys: any[]) {
@ -189,39 +190,39 @@ export const TableArray: React.FC<any> = observer((props) => {
className={classNames( className={classNames(
checked ? 'checked' : null, checked ? 'checked' : null,
css` css`
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
padding-right: 8px; padding-right: 8px;
.nb-table-index {
opacity: 0;
}
&:not(.checked) {
.nb-table-index {
opacity: 1;
}
}
&:hover {
.nb-table-index { .nb-table-index {
opacity: 0; opacity: 0;
} }
.nb-origin-node { &:not(.checked) {
display: block; .nb-table-index {
opacity: 1;
}
} }
} &:hover {
`, .nb-table-index {
opacity: 0;
}
.nb-origin-node {
display: block;
}
}
`,
)} )}
> >
<div <div
className={classNames( className={classNames(
checked ? 'checked' : null, checked ? 'checked' : null,
css` css`
position: relative; position: relative;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
`, `,
)} )}
> >
{dragSort && <SortHandle />} {dragSort && <SortHandle />}
@ -232,13 +233,13 @@ export const TableArray: React.FC<any> = observer((props) => {
'nb-origin-node', 'nb-origin-node',
checked ? 'checked' : null, checked ? 'checked' : null,
css` css`
position: absolute; position: absolute;
right: 50%; right: 50%;
transform: translateX(50%); transform: translateX(50%);
&:not(.checked) { &:not(.checked) {
display: none; display: none;
} }
`, `,
)} )}
> >
{originNode} {originNode}
@ -248,43 +249,45 @@ export const TableArray: React.FC<any> = observer((props) => {
}, },
...props.rowSelection, ...props.rowSelection,
} }
: undefined, : undefined,
}; };
const defaultRowKey = (record: any) => { const defaultRowKey = (record: any) => {
return field.value?.indexOf?.(record); return field.value?.indexOf?.(record);
}; };
return ( return (
<div <div
className={css` className={css`
.ant-table { .ant-table {
overflow-x: auto; overflow-x: auto;
overflow-y: hidden; overflow-y: hidden;
} }
`}
>
<ReactDragListView
handleSelector={'.drag-handle'}
onDragEnd={async (fromIndex, toIndex) => {
const from = field.value[fromIndex];
const to = field.value[toIndex];
field.move(fromIndex, toIndex);
await move(from, to);
}}
lineClassName={css`
border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important;
`} `}
> >
<Table <ReactDragListView
rowKey={defaultRowKey} handleSelector={'.drag-handle'}
{...others} onDragEnd={async (fromIndex, toIndex) => {
{...restProps} const from = field.value[fromIndex];
components={components} const to = field.value[toIndex];
columns={columns} field.move(fromIndex, toIndex);
dataSource={field?.value?.slice?.()} await move(from, to);
/> }}
</ReactDragListView> lineClassName={css`
</div> border-bottom: 2px solid rgba(241, 139, 98, 0.6) !important;
); `}
}); >
<Table
rowKey={defaultRowKey}
{...others}
{...restProps}
components={components}
columns={columns}
dataSource={field?.value?.slice?.()}
/>
</ReactDragListView>
</div>
);
},
{ displayName: 'TableArray' },
);

View File

@ -42,12 +42,15 @@ export const designerCss = css`
} }
`; `;
export const TableColumnActionBar = observer((props) => { export const TableColumnActionBar = observer(
const Designer = useDesigner(); (props) => {
return ( const Designer = useDesigner();
<SortableItem className={designerCss}> return (
<Designer /> <SortableItem className={designerCss}>
{props.children} <Designer />
</SortableItem> {props.children}
); </SortableItem>
}); );
},
{ displayName: 'TableColumnActionBar' },
);

View File

@ -9,20 +9,23 @@ type Props = TableProps<any> & { value?: any; onChange?: any; objectValue?: bool
const toArr = (value: any) => (isArr(value) ? value : isValid(value) ? [value] : []); const toArr = (value: any) => (isArr(value) ? value : isValid(value) ? [value] : []);
export const TableRowSelection = observer((props: Props) => { export const TableRowSelection = observer(
const { rowKey = 'id', objectValue } = props; (props: Props) => {
const field = useField<Field>(); const { rowKey = 'id', objectValue } = props;
const rowSelection: any = { const field = useField<Field>();
type: 'checkbox', const rowSelection: any = {
...props.rowSelection, type: 'checkbox',
selectedRowKeys: toArr(field.value).map((val) => (typeof val === 'object' ? val[rowKey as any] : val)), ...props.rowSelection,
onChange(selectedRowKeys: any[], selectedRows?: any) { selectedRowKeys: toArr(field.value).map((val) => (typeof val === 'object' ? val[rowKey as any] : val)),
if (rowSelection.type === 'checkbox') { onChange(selectedRowKeys: any[], selectedRows?: any) {
props.onChange(objectValue ? selectedRows : selectedRowKeys); if (rowSelection.type === 'checkbox') {
} else { props.onChange(objectValue ? selectedRows : selectedRowKeys);
props.onChange([...(objectValue ? selectedRows : selectedRowKeys)].shift()); } else {
} props.onChange([...(objectValue ? selectedRows : selectedRowKeys)].shift());
}, }
}; },
return <TableVoid {...props} rowSelection={rowSelection} />; };
}); return <TableVoid {...props} rowSelection={rowSelection} />;
},
{ displayName: 'TableRowSelection' },
);

View File

@ -80,45 +80,48 @@ const useDefSelectedRowKeys = () => {
return [result?.state?.selectedRowKeys, (selectedRowKeys) => result?.setState?.({ selectedRowKeys })]; return [result?.state?.selectedRowKeys, (selectedRowKeys) => result?.setState?.({ selectedRowKeys })];
}; };
export const TableVoid: React.FC<TableVoidProps> = observer((props) => { export const TableVoid: React.FC<TableVoidProps> = observer(
const { rowKey = 'id', useDataSource = useDef, useSelectedRowKeys = useDefSelectedRowKeys } = props; (props) => {
const field = useField<Field>(); const { rowKey = 'id', useDataSource = useDef, useSelectedRowKeys = useDefSelectedRowKeys } = props;
const fieldSchema = useFieldSchema(); const field = useField<Field>();
const form = useMemo(() => createForm(), []); const fieldSchema = useFieldSchema();
const f = useAttach(form.createArrayField({ ...field.props, basePath: '' })); const form = useMemo(() => createForm(), []);
const result = useDataSource( const f = useAttach(form.createArrayField({ ...field.props, basePath: '' }));
{ const result = useDataSource(
uid: fieldSchema['x-uid'], {
onSuccess(data) { uid: fieldSchema['x-uid'],
form.setValues({ onSuccess(data) {
[fieldSchema.name]: data?.data, form.setValues({
}); [fieldSchema.name]: data?.data,
if (field?.componentProps?.pagination === false) { });
return; if (field?.componentProps?.pagination === false) {
} return;
field.componentProps.pagination = field.componentProps.pagination || {}; }
if (data?.meta?.count) { field.componentProps.pagination = field.componentProps.pagination || {};
field.componentProps.pagination.total = data?.meta?.count; if (data?.meta?.count) {
} field.componentProps.pagination.total = data?.meta?.count;
field.componentProps.pagination.current = data?.meta?.page || 1; }
field.componentProps.pagination.pageSize = data?.meta?.pageSize || 10; field.componentProps.pagination.current = data?.meta?.page || 1;
field.componentProps.pagination.pageSize = data?.meta?.pageSize || 10;
},
}, },
}, props,
props, );
); return (
return ( <AsyncDataProvider value={result}>
<AsyncDataProvider value={result}> <FormContext.Provider value={form}>
<FormContext.Provider value={form}> <FieldContext.Provider value={f}>
<FieldContext.Provider value={f}> <TableArray
<TableArray {...props}
{...props} rowKey={rowKey}
rowKey={rowKey} useSelectedRowKeys={useSelectedRowKeys}
useSelectedRowKeys={useSelectedRowKeys} loading={result?.loading}
loading={result?.loading} pagination={usePaginationProps(props, result)}
pagination={usePaginationProps(props, result)} />
/> </FieldContext.Provider>
</FieldContext.Provider> </FormContext.Provider>
</FormContext.Provider> </AsyncDataProvider>
</AsyncDataProvider> );
); },
}); { displayName: 'TableVoid' },
);

View File

@ -184,40 +184,43 @@ const schema: ISchema = {
}, },
}; };
const DataSourceProvider = observer((props) => { const DataSourceProvider = observer(
const [selectedRowKeys, setSelectedRowKeys] = useState([]); (props) => {
const [dataSource, setDataSource] = useState([]); const [selectedRowKeys, setSelectedRowKeys] = useState([]);
const service = useRequest( const [dataSource, setDataSource] = useState([]);
() => { const service = useRequest(
return Promise.resolve({ () => {
data: [ return Promise.resolve({
{ id: 1, name: 'Name1' }, data: [
{ id: 2, name: 'Name2' }, { id: 1, name: 'Name1' },
{ id: 3, name: 'Name3' }, { id: 2, name: 'Name2' },
], { id: 3, name: 'Name3' },
}); ],
}, });
{
onSuccess(data) {
setDataSource(data.data);
}, },
}, {
); onSuccess(data) {
console.log('dataSource1', dataSource); setDataSource(data.data);
return ( },
<DataSourceContext.Provider },
value={{ );
service, console.log('dataSource1', dataSource);
dataSource, return (
setDataSource, <DataSourceContext.Provider
selectedRowKeys, value={{
setSelectedRowKeys, service,
}} dataSource,
> setDataSource,
{props.children} selectedRowKeys,
</DataSourceContext.Provider> setSelectedRowKeys,
); }}
}); >
{props.children}
</DataSourceContext.Provider>
);
},
{ displayName: 'DataSourceProvider' },
);
export default () => { export default () => {
return ( return (

View File

@ -9,28 +9,31 @@ import { DndContext, SortableItem } from '../../common';
import { useDesigner } from '../../hooks/useDesigner'; import { useDesigner } from '../../hooks/useDesigner';
import { TabsDesigner } from './Tabs.Designer'; import { TabsDesigner } from './Tabs.Designer';
export const Tabs: any = observer((props: TabsProps) => { export const Tabs: any = observer(
const fieldSchema = useFieldSchema(); (props: TabsProps) => {
const { render } = useSchemaInitializer(fieldSchema['x-initializer']); const fieldSchema = useFieldSchema();
return ( const { render } = useSchemaInitializer(fieldSchema['x-initializer']);
<DndContext> return (
<AntdTabs <DndContext>
style={props.style} <AntdTabs
tabBarExtraContent={{ style={props.style}
right: render(), tabBarExtraContent={{
}} right: render(),
> }}
{fieldSchema.mapProperties((schema, key) => { >
return ( {fieldSchema.mapProperties((schema, key) => {
<AntdTabs.TabPane tab={<RecursionField name={key} schema={schema} onlyRenderSelf />} key={key}> return (
<RecursionField name={key} schema={schema} onlyRenderProperties /> <AntdTabs.TabPane tab={<RecursionField name={key} schema={schema} onlyRenderSelf />} key={key}>
</AntdTabs.TabPane> <RecursionField name={key} schema={schema} onlyRenderProperties />
); </AntdTabs.TabPane>
})} );
</AntdTabs> })}
</DndContext> </AntdTabs>
); </DndContext>
}); );
},
{ displayName: 'Tabs' },
);
const designerCss = css` const designerCss = css`
position: relative; position: relative;
@ -79,15 +82,18 @@ const designerCss = css`
} }
`; `;
Tabs.TabPane = observer((props: TabPaneProps & { icon?: any }) => { Tabs.TabPane = observer(
const Designer = useDesigner(); (props: TabPaneProps & { icon?: any }) => {
const field = useField(); const Designer = useDesigner();
return ( const field = useField();
<SortableItem className={classNames('nb-action-link', designerCss, props.className)}> return (
{props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />} {props.tab || field.title} <SortableItem className={classNames('nb-action-link', designerCss, props.className)}>
<Designer /> {props.icon && <Icon style={{ marginRight: 2 }} type={props.icon} />} {props.tab || field.title}
</SortableItem> <Designer />
); </SortableItem>
}); );
},
{ displayName: 'TabPane' },
);
Tabs.Designer = TabsDesigner; Tabs.Designer = TabsDesigner;

Some files were not shown because too many files have changed in this diff Show More