feat: improve action schema component

This commit is contained in:
chenos 2022-02-10 12:06:48 +08:00
parent 0c83c16391
commit 43393b4d44
20 changed files with 384 additions and 86 deletions

View File

@ -3,11 +3,11 @@ import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import React, { useState } from 'react';
import { PluginManager } from '../plugin-manager';
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
import { ActionContext, SchemaComponent, useActionContext } from '../schema-component';
import { RoleTable } from './RolePermissionManager';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -39,7 +39,7 @@ const schema: ISchema = {
export const ACLShortcut = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<PluginManager.Toolbar.Item
icon={<LockOutlined />}
title={'角色和权限'}
@ -48,6 +48,6 @@ export const ACLShortcut = () => {
}}
/>
<SchemaComponent components={{ RoleTable }} scope={{ useCloseAction }} schema={schema} />
</VisibleContext.Provider>
</ActionContext.Provider>
);
};

View File

@ -3,11 +3,11 @@ import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import React, { useState } from 'react';
import { PluginManager } from '../plugin-manager';
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
import { ActionContext, SchemaComponent, useActionContext } from '../schema-component';
import { ConfigurationTable } from './Configuration';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -38,7 +38,7 @@ const schema: ISchema = {
export const CollectionManagerShortcut = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<PluginManager.Toolbar.Item
icon={<DatabaseOutlined />}
title={'数据表配置'}
@ -47,6 +47,6 @@ export const CollectionManagerShortcut = () => {
}}
/>
<SchemaComponent scope={{ useCloseAction }} schema={schema} components={{ ConfigurationTable }} />
</VisibleContext.Provider>
</ActionContext.Provider>
);
};

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { VerifiedOutlined } from '@ant-design/icons';
import { ISchema, useForm } from '@formily/react';
import { PluginManager, SchemaComponent, useActionVisible, VisibleContext } from '@nocobase/client';
import { ActionContext, PluginManager, SchemaComponent, useActionContext } from '@nocobase/client';
import React, { useState } from 'react';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -51,7 +51,7 @@ export const Plugin2 = () => null;
Plugin2.ToolbarItem = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<PluginManager.Toolbar.Item
icon={<VerifiedOutlined />}
title={'Plugin2'}
@ -60,6 +60,6 @@ Plugin2.ToolbarItem = () => {
}}
/>
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
</VisibleContext.Provider>
</ActionContext.Provider>
);
};

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { VerifiedOutlined } from '@ant-design/icons';
import { ISchema, useForm } from '@formily/react';
import { PluginManager, SchemaComponent, useActionVisible, VisibleContext } from '@nocobase/client';
import { ActionContext, PluginManager, SchemaComponent, useActionContext } from '@nocobase/client';
import React, { useState } from 'react';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -51,7 +51,7 @@ export const Plugin4 = () => null;
Plugin4.ToolbarItem = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<PluginManager.Toolbar.Item
icon={<VerifiedOutlined />}
title={'Plugin4'}
@ -60,6 +60,6 @@ Plugin4.ToolbarItem = () => {
}}
/>
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
</VisibleContext.Provider>
</ActionContext.Provider>
);
};

View File

@ -44,7 +44,7 @@ Plugin1.ToolbarItem = () => {
Plugin2.ToolbarItem = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<PluginManager.Toolbar.Item
icon={<VerifiedOutlined />}
title={'Plugin2'}
@ -53,7 +53,7 @@ Plugin2.ToolbarItem = () => {
}}
/>
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
</VisibleContext.Provider>
</ActionContext.Provider>
);
};
```

View File

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

View File

@ -1,17 +1,18 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { Drawer } from 'antd';
import React, { useContext } from 'react';
import React from 'react';
import { createPortal } from 'react-dom';
import { VisibleContext } from './context';
import { useActionContext } from './hooks';
import { ComposedActionDrawer } from './types';
export const ActionDrawer: ComposedActionDrawer = observer((props) => {
const [visible, setVisible] = useContext(VisibleContext);
const { footerNodeName = 'Action.Drawer.Footer', ...others } = props;
const { visible, setVisible } = useActionContext();
const schema = useFieldSchema();
const field = useField();
const footerSchema = schema.reduceProperties((buf, s) => {
if (s['x-component'] === 'Action.Drawer.Footer') {
if (s['x-component'] === footerNodeName) {
return s;
}
return buf;
@ -22,7 +23,7 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => {
<Drawer
width={'50%'}
title={schema.title}
{...props}
{...others}
destroyOnClose
visible={visible}
onClose={() => setVisible(false)}
@ -43,7 +44,7 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => {
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === 'Action.Drawer.Footer';
return s['x-component'] === footerNodeName;
}}
/>
</div>
@ -55,7 +56,7 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => {
schema={schema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] !== 'Action.Drawer.Footer';
return s['x-component'] !== footerNodeName;
}}
/>
</Drawer>,

View File

@ -0,0 +1,75 @@
import { css } from '@emotion/css';
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { Modal } from 'antd';
import React from 'react';
import { createPortal } from 'react-dom';
import { useActionContext } from '.';
import { ComposedActionDrawer } from './types';
export const ActionModal: ComposedActionDrawer = observer((props) => {
const { footerNodeName = 'Action.Modal.Footer', ...others } = props;
const { visible, setVisible } = useActionContext();
const schema = useFieldSchema();
const field = useField();
const footerSchema = schema.reduceProperties((buf, s) => {
if (s['x-component'] === footerNodeName) {
return s;
}
return buf;
});
return (
<>
{createPortal(
<Modal
// width={'50%'}
title={schema.title}
{...others}
destroyOnClose
visible={visible}
onCancel={() => setVisible(false)}
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;
}}
/>
</Modal>,
document.body,
)}
</>
);
});
ActionModal.Footer = observer(() => {
const field = useField();
const schema = useFieldSchema();
return <RecursionField basePath={field.address} schema={schema} onlyRenderProperties />;
});
export default ActionModal;

View File

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

View File

@ -1,19 +1,22 @@
import { observer, RecursionField, useField, useFieldSchema } from '@formily/react';
import { Button } from 'antd';
import React, { useState } from 'react';
import ActionContainer from './Action.Container';
import { ActionDrawer } from './Action.Drawer';
import { VisibleContext } from './context';
import { ActionModal } from './Action.Modal';
import { ActionPage } from './Action.Page';
import { ActionContext } from './context';
import { useA } from './hooks';
import { ComposedAction } from './types';
export const Action: ComposedAction = observer((props) => {
const { useAction = useA, onClick, ...others } = props;
export const Action: ComposedAction = observer((props: any) => {
const { openMode, containerRefKey, useAction = useA, onClick, ...others } = props;
const [visible, setVisible] = useState(false);
const schema = useFieldSchema();
const field = useField();
const { run } = useAction();
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible, openMode, containerRefKey }}>
<Button
{...others}
onClick={(e) => {
@ -25,10 +28,13 @@ export const Action: ComposedAction = observer((props) => {
{field.title}
</Button>
<RecursionField basePath={field.address} schema={schema} onlyRenderProperties />
</VisibleContext.Provider>
</ActionContext.Provider>
);
});
Action.Drawer = ActionDrawer;
Action.Modal = ActionModal;
Action.Container = ActionContainer;
Action.Page = ActionPage;
export default Action;

View File

@ -1,3 +1,10 @@
import { createContext } from 'react';
export const VisibleContext = createContext<[boolean, any]>([false, () => {}]);
export const ActionContext = createContext<ActionContextProps>({});
export interface ActionContextProps {
visible?: boolean;
setVisible?: (v: boolean) => void;
openMode?: 'drawer' | 'modal' | 'page';
containerRefKey?: string;
}

View File

@ -1,10 +1,10 @@
import React from 'react';
import { observer, ISchema, useForm } from '@formily/react';
import { SchemaComponent, SchemaComponentProvider, Form, Action, useActionVisible } from '@nocobase/client';
import { FormItem, Input } from '@formily/antd';
import { ISchema, observer, useForm } from '@formily/react';
import { Action, Form, SchemaComponent, SchemaComponentProvider, useActionContext } from '@nocobase/client';
import React from 'react';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {

View File

@ -1,18 +1,17 @@
import React, { useState } from 'react';
import { observer, ISchema, useForm } from '@formily/react';
import { FormItem, Input } from '@formily/antd';
import { ISchema, observer, useForm } from '@formily/react';
import {
Action,
ActionContext,
Form,
SchemaComponent,
SchemaComponentProvider,
Form,
Action,
useActionVisible,
VisibleContext,
useActionContext
} from '@nocobase/client';
import { FormItem, Input } from '@formily/antd';
import { Button } from 'antd';
import React, { useState } from 'react';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -58,10 +57,10 @@ export default observer(() => {
const [visible, setVisible] = useState(false);
return (
<SchemaComponentProvider components={{ Form, Action, Input, FormItem }}>
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<a onClick={() => setVisible(true)}>Open</a>
<SchemaComponent scope={{ useCloseAction }} schema={schema} />
</VisibleContext.Provider>
</ActionContext.Provider>
</SchemaComponentProvider>
);
});

View File

@ -0,0 +1,107 @@
import { FormItem, Input } from '@formily/antd';
import { Field } from '@formily/core';
import { ISchema, observer, useField, useForm } from '@formily/react';
import { Action, Form, SchemaComponent, SchemaComponentProvider, useActionContext } from '@nocobase/client';
import { Select } from 'antd';
import React, { useRef } from 'react';
const useCloseAction = () => {
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
setVisible(false);
form.submit((values) => {
console.log(values);
});
},
};
};
const Editable = observer((props) => {
const field = useField<Field>();
return (
<div>
<div style={{ marginBottom: 24 }}>
<Select
value={field.componentProps.openMode}
style={{ width: 100 }}
onChange={(value) => {
field.componentProps.openMode = value;
}}
>
<Select.Option value={'drawer'}>Drawer</Select.Option>
<Select.Option value={'modal'}>Modal</Select.Option>
<Select.Option value={'page'}>Page</Select.Option>
</Select>
</div>
{props.children}
</div>
);
});
const schema: ISchema = {
type: 'object',
properties: {
action1: {
'x-decorator': 'Editable',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
openMode: 'modal',
containerRefKey: 'containerRef',
},
type: 'void',
title: 'Open',
properties: {
drawer1: {
'x-component': 'Action.Container',
'x-component-props': {},
type: 'void',
title: 'Modal Title',
properties: {
hello1: {
title: 'T1',
'x-content': 'Hello',
},
footer1: {
'x-component': 'Action.Container.Footer',
type: 'void',
properties: {
close1: {
title: 'Close',
'x-component': 'Action',
'x-component-props': {
useAction: '{{ useCloseAction }}',
},
},
submit: {
title: 'Submit',
'x-component': 'Action',
'x-component-props': {
type: 'primary',
useAction: '{{ useCloseAction }}',
},
},
},
},
},
},
},
},
},
};
export default observer(() => {
const containerRef = useRef();
return (
<SchemaComponentProvider
scope={{ containerRef, useCloseAction }}
components={{ Editable, Form, Action, Input, FormItem }}
>
<SchemaComponent schema={schema} />
<div ref={containerRef}></div>
</SchemaComponentProvider>
);
});

View File

@ -1,6 +1,6 @@
import { useForm } from '@formily/react';
import { useContext } from 'react';
import { VisibleContext } from './context';
import { ActionContext } from './context';
export const useA = () => {
return {
@ -8,13 +8,21 @@ export const useA = () => {
};
};
export const useActionVisible = () => {
const [visible, setVisible] = useContext(VisibleContext);
return { visible, setVisible };
export const useActionContext = () => {
const ctx = useContext(ActionContext);
return {
...ctx,
setVisible(visible: boolean) {
if (ctx.openMode !== 'page') {
ctx.setVisible(visible);
}
},
};
};
export const useCloseAction = () => {
const [, setVisible] = useContext(VisibleContext);
const { setVisible } = useContext(ActionContext);
const form = useForm();
return {
async run() {

View File

@ -10,22 +10,15 @@ group:
## Nodes
已确定的节点:
- Action
- Action.URL
- Action.Link
- Action.Drawer
- Action.Drawer.Footer
- Action.Modal
- Action.Modal.Footer
- Action.Popover
不确定的节点:
- Action.Group
- Action.Dropdown
- Action.Window
- Action.Drawer
- Action.Drawer.Footer
- Action.Container
- Action.Container.Footer
- ActionBar
## Examples
@ -34,8 +27,12 @@ group:
<code src="./demos/demo1.tsx"/>
### VisibleContext + Action.Drawer
### ActionContext + Action.Drawer
只配置 Action.Drawer而不需要 Action通过 VisibleContext 自定义按钮。
只配置 Action.Drawer而不需要 Action结合 ActionContext 自定义按钮。
<code src="./demos/demo2.tsx"/>
### 不同的打开方式
<code src="./demos/demo3.tsx"/>

View File

@ -11,6 +11,6 @@ export type ComposedAction = React.FC<ActionProps> & {
[key: string]: any;
};
export type ComposedActionDrawer = React.FC<DrawerProps> & {
export type ComposedActionDrawer = React.FC<DrawerProps & { footerNodeName?: string }> & {
Footer?: React.FC;
};

View File

@ -15,7 +15,7 @@ import { toArr } from '@formily/shared';
import { Button, Drawer, Select, Space, Tag } from 'antd';
import React, { createContext, useContext, useMemo, useState } from 'react';
import { useAttach } from '../../hooks/useAttach';
import { VisibleContext } from '../action';
import { ActionContext } from '../action';
const InputRecordPicker: React.FC = (props) => {
const [visible, setVisible] = useState(false);
@ -130,13 +130,11 @@ RecordPicker.SelectedItem = () => {
const fieldSchema = useFieldSchema();
const [visible, setVisible] = useState(false);
return (
<>
<VisibleContext.Provider value={[visible, setVisible]}>
<Tag style={{ cursor: 'pointer' }} onClick={() => setVisible(true)}>
{ctx.record?.name}
</Tag>
<RecursionField onlyRenderProperties schema={fieldSchema}></RecursionField>
</VisibleContext.Provider>
</>
<ActionContext.Provider value={{ visible, setVisible }}>
<Tag style={{ cursor: 'pointer' }} onClick={() => setVisible(true)}>
{ctx.record?.name}
</Tag>
<RecursionField onlyRenderProperties schema={fieldSchema}></RecursionField>
</ActionContext.Provider>
);
};

View File

@ -4,10 +4,10 @@ import { uid } from '@formily/shared';
import React, { useState } from 'react';
import { useSystemSettings } from '.';
import { PluginManager, useRequest } from '..';
import { SchemaComponent, useActionVisible, VisibleContext } from '../schema-component';
import { ActionContext, SchemaComponent, useActionContext } from '../schema-component';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
// const form = useForm();
return {
async run() {
@ -24,7 +24,7 @@ const useSystemSettingsValues = (props, options) => {
};
const useSaveSystemSettingsValues = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
const { mutate } = useSystemSettings();
return {
@ -112,7 +112,7 @@ const schema: ISchema = {
export const SystemSettingsShortcut = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<PluginManager.Toolbar.Item
eventKey={'ACLAction'}
onClick={() => {
@ -125,6 +125,6 @@ export const SystemSettingsShortcut = () => {
scope={{ useSaveSystemSettingsValues, useSystemSettingsValues, useCloseAction }}
schema={schema}
/>
</VisibleContext.Provider>
</ActionContext.Provider>
);
};

View File

@ -2,10 +2,10 @@ import { ISchema, useForm } from '@formily/react';
import { uid } from '@formily/shared';
import { Menu } from 'antd';
import React, { useState } from 'react';
import { SchemaComponent, useActionVisible, useRequest, VisibleContext } from '../';
import { ActionContext, SchemaComponent, useActionContext, useRequest } from '../';
const useCloseAction = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -30,7 +30,7 @@ const useCurrentUserValues = (props, options) => {
};
const useSaveCurrentUserValues = () => {
const { setVisible } = useActionVisible();
const { setVisible } = useActionContext();
const form = useForm();
return {
async run() {
@ -90,7 +90,7 @@ const schema: ISchema = {
export const ProfileAction = () => {
const [visible, setVisible] = useState(false);
return (
<VisibleContext.Provider value={[visible, setVisible]}>
<ActionContext.Provider value={{ visible, setVisible }}>
<Menu.Item
eventKey={'ProfileAction'}
onClick={() => {
@ -100,6 +100,6 @@ export const ProfileAction = () => {
</Menu.Item>
<SchemaComponent scope={{ useCurrentUserValues, useCloseAction, useSaveCurrentUserValues }} schema={schema} />
</VisibleContext.Provider>
</ActionContext.Provider>
);
};