feat(client): add settings form

This commit is contained in:
chenos 2022-02-09 23:37:12 +08:00
parent 6608901596
commit 0c83c16391
5 changed files with 407 additions and 1 deletions

View File

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

View File

@ -0,0 +1,269 @@
import { FormButtonGroup, FormDialog, FormDrawer, FormItem, FormLayout, Reset, Submit } from '@formily/antd';
import { createForm, Field, ObjectField, onFormValuesChange } from '@formily/core';
import {
FieldContext,
FormContext,
observer,
RecursionField,
Schema,
SchemaOptionsContext,
useField,
useFieldSchema,
useForm
} from '@formily/react';
import { Dropdown, Menu, Modal, Select, Switch } from 'antd';
import React, { createContext, useContext, useMemo, useState } from 'react';
import { SchemaComponentOptions, useAttach, useDesignable } from '..';
export interface SettingsFormContextProps {
field?: Field;
fieldSchema?: Schema;
dropdownVisible?: boolean;
setDropdownVisible?: (v: boolean) => void;
dn?: any;
}
export const SettingsFormContext = createContext<SettingsFormContextProps>(null);
export const useSettingsFormContext = () => {
return useContext(SettingsFormContext);
};
export const SettingsForm: any = observer((props: any) => {
const dn = useDesignable();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const [dropdownVisible, setDropdownVisible] = useState(false);
const settingsFormSchema = useMemo(() => new Schema(props.schema), []);
const form = useMemo(
() =>
createForm({
initialValues: fieldSchema.toJSON(),
effects(form) {
onFormValuesChange((form) => {
dn.patch(form.values);
console.log('form.values', form.values);
});
},
}),
[],
);
const f = useAttach(form.createVoidField({ ...field.props, basePath: '' }));
return (
<SettingsFormContext.Provider value={{ dn, field, fieldSchema, dropdownVisible, setDropdownVisible }}>
<SchemaComponentOptions components={{ SettingsForm }}>
<FieldContext.Provider value={null}>
<FormContext.Provider value={form}>
<FieldContext.Provider value={f}>
<Dropdown
visible={dropdownVisible}
onVisibleChange={(visible) => setDropdownVisible(visible)}
overlayStyle={{ width: 200 }}
overlay={
<Menu>
{settingsFormSchema.mapProperties((s, key) => {
return <RecursionField name={key} schema={s} />;
})}
</Menu>
}
>
<a></a>
</Dropdown>
</FieldContext.Provider>
</FormContext.Provider>
</FieldContext.Provider>
</SchemaComponentOptions>
</SettingsFormContext.Provider>
);
});
SettingsForm.Divider = () => {
return <Menu.Divider />;
};
SettingsForm.Remove = (props) => {
const field = useField();
const { dn, setDropdownVisible } = useSettingsFormContext();
return (
<Menu.Item
onClick={() => {
setDropdownVisible(false);
Modal.confirm({
title: 'Are you sure delete this task?',
content: 'Some descriptions',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
...props.confirm,
onOk() {
dn.remove();
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
}}
>
{field.title}
</Menu.Item>
);
};
SettingsForm.Switch = observer(() => {
const field = useField<Field>();
return (
<Menu.Item
onClick={() => {
field.value = !field.value;
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{field.title} <Switch checked={!!field.value} />
</div>
</Menu.Item>
);
});
SettingsForm.Select = observer((props) => {
const field = useField<Field>();
const [open, setOpen] = useState(false);
return (
<Menu.Item onClick={() => !open && setOpen(true)}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{field.title}
<Select
open={open}
onDropdownVisibleChange={(open) => setOpen(open)}
onSelect={() => {
setOpen(false);
}}
onChange={(value) => {
field.value = value;
}}
value={field.value}
options={field.dataSource}
style={{ width: '60%' }}
size={'small'}
bordered={false}
/>
</div>
</Menu.Item>
);
});
SettingsForm.Modal = () => {
const form = useForm();
const field = useField<Field>();
const fieldSchema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const { setDropdownVisible } = useSettingsFormContext();
return (
<Menu.Item
style={{ width: 200 }}
onClick={async () => {
setDropdownVisible(false);
const values = await FormDialog('Title', () => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components, FormItem }}>
<FormLayout layout={'vertical'}>
<RecursionField schema={fieldSchema} onlyRenderProperties />
</FormLayout>
</SchemaComponentOptions>
);
}).open({
initialValues: fieldSchema.type !== 'void' ? field.value : form.values,
});
if (fieldSchema.type !== 'void') {
form.setValues(
{
[fieldSchema.name]: values,
},
'deepMerge',
);
} else {
form.setValues(values);
}
}}
>
{field.title}
</Menu.Item>
);
};
SettingsForm.Drawer = () => {
const form = useForm();
const field = useField<ObjectField>();
const fieldSchema = useFieldSchema();
const options = useContext(SchemaOptionsContext);
const { setDropdownVisible } = useSettingsFormContext();
return (
<Menu.Item
style={{ width: 200 }}
onClick={async () => {
setDropdownVisible(false);
const values = await FormDrawer('Pop-up form', () => {
return (
<SchemaComponentOptions scope={options.scope} components={{ ...options.components, FormItem }}>
<FormLayout layout={'vertical'}>
<RecursionField schema={fieldSchema} onlyRenderProperties />
<FormDrawer.Footer>
<FormButtonGroup align="right">
<Reset>Reset</Reset>
<Submit
onSubmit={() => {
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}}
>
Submit
</Submit>
</FormButtonGroup>
</FormDrawer.Footer>
</FormLayout>
</SchemaComponentOptions>
);
}).open({
initialValues: fieldSchema.type !== 'void' ? field.value : form.values,
});
if (fieldSchema.type !== 'void') {
form.setValues(
{
[fieldSchema.name]: values,
},
'deepMerge',
);
} else {
form.setValues(values);
}
}}
>
{field.title}
</Menu.Item>
);
};
SettingsForm.SubMenu = () => {
const field = useField();
const fieldSchema = useFieldSchema();
return (
<Menu.SubMenu title={field.title}>
{fieldSchema.mapProperties((schema, key) => {
return <RecursionField name={key} schema={schema} />;
})}
</Menu.SubMenu>
);
};
SettingsForm.ItemGroup = () => {
const field = useField();
const fieldSchema = useFieldSchema();
return (
<Menu.ItemGroup title={field.title}>
{fieldSchema.mapProperties((schema, key) => {
return <RecursionField name={key} schema={schema} />;
})}
</Menu.ItemGroup>
);
};

View File

@ -0,0 +1,133 @@
import { ISchema, observer, useFieldSchema } from '@formily/react';
import { AntdSchemaComponentProvider, SchemaComponent, SchemaComponentProvider, SettingsForm } from '@nocobase/client';
import React from 'react';
const schema: ISchema = {
type: 'object',
properties: {
'x-component-props.switch': {
title: 'Switch',
'x-component': 'SettingsForm.Switch',
},
'x-component-props.select': {
title: 'Select',
'x-component': 'SettingsForm.Select',
enum: [
{ label: 'Option1', value: 'option1' },
{ label: 'Option2', value: 'option2' },
{ label: 'Option3', value: 'option3' },
],
},
modal: {
type: 'void',
title: 'Open Modal',
'x-component': 'SettingsForm.Modal',
'x-component-props': {},
properties: {
'x-component-props.title': {
title: '标题',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
},
},
drawer: {
type: 'void',
title: 'Open Drawer',
'x-component': 'SettingsForm.Drawer',
properties: {
'x-component-props.title': {
title: '标题',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
},
},
group: {
type: 'void',
title: 'ItemGroup',
'x-component': 'SettingsForm.ItemGroup',
properties: {
'x-component-props': {
type: 'object',
title: 'Open Modal',
'x-component': 'SettingsForm.Modal',
properties: {
title: {
title: '标题',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
},
},
},
},
submenu: {
type: 'void',
title: 'SubMenu',
'x-component': 'SettingsForm.SubMenu',
properties: {
'x-component-props': {
type: 'object',
title: 'Open Modal',
'x-component': 'SettingsForm.Modal',
properties: {
title: {
title: '标题',
'x-component': 'Input',
'x-decorator': 'FormItem',
},
},
},
},
},
divider: {
'x-component': 'SettingsForm.Divider',
},
remove: {
title: 'Delete',
'x-component': 'SettingsForm.Remove',
'x-component-props': {
confirm: {
title: 'Are you sure delete this task?',
content: 'Some descriptions',
},
},
},
},
};
const Hello = observer((props: any) => {
const fieldSchema = useFieldSchema();
return (
<div>
<pre>{JSON.stringify(props, null, 2)}</pre>
<pre>{JSON.stringify(fieldSchema.toJSON(), null, 2)}</pre>
<SettingsForm schema={schema} />
</div>
);
});
export default () => {
return (
<SchemaComponentProvider components={{ Hello }}>
<AntdSchemaComponentProvider>
<SchemaComponent
schema={{
type: 'object',
properties: {
hello: {
'x-component': 'Hello',
'x-component-props': {
title: 'abc',
switch: true,
select: 'option1',
},
},
},
}}
/>
</AntdSchemaComponentProvider>
</SchemaComponentProvider>
);
};

View File

@ -5,4 +5,6 @@ group:
path: /client
---
# SettingsForm <Badge>待定</Badge>
# SettingsForm
<code src="./demos/demo1.tsx" />

View File

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