tachybase_todo/packages/client/src/schemas/add-new/index.tsx
2021-07-30 16:10:40 +08:00

975 lines
31 KiB
TypeScript

import React, {
Children,
createContext,
useContext,
useEffect,
useState,
} from 'react';
import { FormDialog, FormLayout } from '@formily/antd';
import {
connect,
observer,
mapProps,
mapReadPretty,
useField,
useFieldSchema,
RecursionField,
Schema,
ISchema,
useForm,
} from '@formily/react';
import {
Menu,
MenuProps,
MenuItemProps,
SubMenuProps,
DividerProps,
Dropdown,
Modal,
Button,
Spin,
Switch,
Checkbox,
} from 'antd';
import {
MenuOutlined,
GroupOutlined,
PlusOutlined,
LinkOutlined,
AppstoreAddOutlined,
EditOutlined,
DeleteOutlined,
ArrowRightOutlined,
SettingOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
LoadingOutlined,
DownOutlined,
DatabaseOutlined,
} from '@ant-design/icons';
import {
createCollectionField,
createOrUpdateCollection,
createSchema,
removeSchema,
useDesignable,
useSchemaPath,
} from '../';
import { uid } from '@formily/shared';
import { getSchemaPath, SchemaField } from '../../components/schema-renderer';
import { cloneDeep } from 'lodash';
import { options } from '../database-field/interfaces';
import './style.less';
import IconPicker from '../../components/icon-picker';
import { barChartConfig, columnChartConfig } from './chart';
import { isGridRowOrCol } from '../grid';
import {
useCollectionContext,
useCollectionsContext,
useDisplayedMapContext,
} from '../../constate';
import SwitchMenuItem from '../../components/SwitchMenuItem';
const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid();
return {
type: 'void',
name: uid(),
'x-component': 'Grid.Row',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid.Col',
properties: {
[name]: schema,
},
},
},
};
};
const isGrid = (schema: Schema) => {
return schema['x-component'] === 'Grid';
};
const isGridBlock = (schema: Schema) => {
if (schema.parent['x-component'] !== 'Grid.Col') {
return false;
}
// Grid.Col 里有多少 Block
if (Object.keys(schema.parent.properties).length > 1) {
return false;
}
// 有多少 Grid.Row
if (Object.keys(schema.parent.parent.properties).length > 1) {
return false;
}
return true;
};
function generateCardItemSchema(component) {
const defaults = {
'Markdown.Void': {
type: 'void',
default: '这是一段演示文字,**支持使用 Markdown 语法**',
'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-decorator': 'CardItem',
'x-read-pretty': true,
'x-component': 'Markdown.Void',
},
Table: {
type: 'array',
'x-designable-bar': 'Table.DesignableBar',
'x-decorator': 'CardItem',
'x-component': 'Table',
default: [
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
// { key: uid(), field1: uid(), field2: uid() },
],
'x-component-props': {
rowKey: 'id',
dragSort: true,
showIndex: true,
refreshRequestOnChange: true,
pagination: {
pageSize: 10,
},
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Table.ActionBar',
'x-designable-bar': 'Table.ActionBar.DesignableBar',
properties: {
[uid()]: {
type: 'void',
title: '筛选',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'filter',
},
'x-align': 'left',
'x-component': 'Table.Filter',
'x-designable-bar': 'Table.Filter.DesignableBar',
'x-component-props': {
fieldNames: [],
},
},
[uid()]: {
type: 'void',
name: 'action1',
title: '新增',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'create',
},
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
modal: {
type: 'void',
title: '新增数据',
'x-decorator': 'Form',
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableCreateAction }}',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
},
},
[uid()]: {
type: 'void',
name: 'action1',
title: '删除',
'x-align': 'right',
'x-decorator': 'AddNew.Displayed',
'x-decorator-props': {
displayName: 'destroy',
},
'x-component': 'Action',
'x-designable-bar': 'Table.Action.DesignableBar',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
},
},
[uid()]: {
type: 'void',
title: '操作',
'x-component': 'Table.Column',
'x-component-props': {
className: 'nb-table-operation',
},
'x-designable-bar': 'Table.OperationDesignableBar',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Action.Dropdown',
'x-component-props': {
buttonProps: {
icon: 'EllipsisOutlined',
},
},
properties: {
[uid()]: {
type: 'void',
title: '操作 1',
'x-component': 'Menu.Action',
'x-component-props': {
style: {
minWidth: 150,
},
disabled: true,
},
},
[uid()]: {
type: 'void',
name: 'action1',
title: '查看',
'x-component': 'Menu.Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
[uid()]: {
type: 'void',
title: '查看',
'x-component': 'Action.Modal',
'x-component-props': {
bodyStyle: {
background: '#f0f2f5',
paddingTop: 0,
},
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Tabs',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Tabs.TabPane',
'x-component-props': {
tab: 'Tab1',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.PaneItem',
},
},
},
},
},
},
},
},
},
},
[uid()]: {
type: 'void',
title: '编辑',
'x-component': 'Menu.Action',
'x-designable-bar': 'Table.Action.DesignableBar',
properties: {
[uid()]: {
type: 'void',
title: '编辑数据',
'x-decorator': 'Form',
'x-decorator-props': {
useResource: '{{ Table.useResource }}',
useValues: '{{ Table.useTableRowRecord }}',
},
'x-component': 'Action.Modal',
'x-component-props': {
useOkAction: '{{ Table.useTableUpdateAction }}',
},
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
},
},
[uid()]: {
type: 'void',
title: '删除',
'x-component': 'Menu.Action',
'x-component-props': {
useAction: '{{ Table.useTableDestroyAction }}',
},
},
},
},
},
},
},
},
Form: {
type: 'void',
name: uid(),
'x-decorator': 'CardItem',
'x-component': 'Form',
'x-component-props': {
showDefaultButtons: true,
},
'x-designable-bar': 'Form.DesignableBar',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
},
'Chart.Column': {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'Chart.Column',
'x-designable-bar': 'Chart.DesignableBar',
'x-component-props': {
config: cloneDeep(columnChartConfig),
},
},
'Chart.Bar': {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'Chart.Bar',
'x-designable-bar': 'Chart.DesignableBar',
'x-component-props': {
config: cloneDeep(barChartConfig),
},
},
};
return defaults[component];
}
function generateFormItemSchema(component) {
const defaults = {
Markdown: {
type: 'string',
title: uid(),
'x-designable-bar': 'Markdown.DesignableBar',
'x-decorator': 'FormItem',
'x-component': 'Markdown',
'x-component-props': {},
},
};
return defaults[component];
}
const dbSchema = {
type: 'object',
properties: {
title: {
type: 'string',
title: '数据表名称',
required: true,
'x-decorator': 'FormItem',
'x-component': 'Input',
},
name: {
type: 'string',
title: '数据表标识',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-read-pretty': true,
},
fields: {
type: 'array',
title: '数据表字段',
'x-decorator': 'FormItem',
'x-component': 'DatabaseField',
default: [],
},
},
};
export const AddNew = () => null;
AddNew.CardItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
const path = useSchemaPath();
const { collections = [], loading, refresh } = useCollectionsContext();
return (
<Dropdown
trigger={['click']}
overlayStyle={{
minWidth: 200,
}}
onVisibleChange={(visible) => {
console.log('onVisibleChange', visible);
}}
overlay={
<Menu
onClick={async (info) => {
let data: ISchema;
let collectionName = null;
if (['addNewTable', 'addNewForm'].includes(info.key)) {
const values = await FormDialog(`新建数据表`, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={dbSchema} />
</FormLayout>
);
}).open({
initialValues: {
name: `t_${uid()}`,
fields: [],
},
});
await createOrUpdateCollection(values);
refresh();
data = generateCardItemSchema(
info.key === 'addNewTable' ? 'Table' : 'Form',
);
collectionName = values.name;
} else if (info.key.startsWith('collection.')) {
const keys = info.key.split('.');
const component = keys.pop();
const tableName = keys.pop();
collectionName = tableName;
data = generateCardItemSchema(component);
console.log('info.keyPath', component, tableName);
} else {
data = generateCardItemSchema(info.key);
console.log('generateCardItemSchema', data, info.key);
}
if (schema['key']) {
data['key'] = uid();
}
if (collectionName) {
data['x-component-props'] = data['x-component-props'] || {};
data['x-component-props']['resource'] = collectionName;
data['x-component-props']['collectionName'] = collectionName;
}
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
}}
>
<Menu.ItemGroup title={'数据区块'}>
{[
{ key: 'Table', title: '表格', icon: 'TableOutlined' },
{ key: 'Form', title: '表单', icon: 'FormOutlined' },
{
key: 'Calendar',
title: '日历',
icon: 'CalendarOutlined',
disabled: true,
},
{
key: 'Kanban',
title: '看板',
icon: 'CreditCardOutlined',
disabled: true,
},
].map((view) => (
<Menu.SubMenu
icon={<IconPicker type={view.icon} />}
disabled={view.disabled}
key={view.key}
title={view.title}
>
<Menu.ItemGroup key={`${view.key}-select`} title={'所属数据表'}>
{collections?.map((item) => (
<Menu.Item
style={{ minWidth: 150 }}
key={`collection.${item.name}.${view.key}`}
>
{item.title}
</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item icon={<PlusOutlined />} key={`addNew${view.key}`}>
</Menu.Item>
</Menu.SubMenu>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.ItemGroup title={'多媒体区块'}>
<Menu.Item
key={'Markdown.Void'}
icon={<IconPicker type={'FileMarkdownOutlined'} />}
>
Markdown
</Menu.Item>
<Menu.Item
disabled
key={'Wysiwyg.Void'}
icon={<IconPicker type={'FileTextOutlined'} />}
>
</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider />
<Menu.ItemGroup title={'图表区块'}>
<Menu.Item
key={'Chart.Column'}
icon={<IconPicker type={'BarChartOutlined'} />}
>
</Menu.Item>
<Menu.Item
key={'Chart.Bar'}
icon={<IconPicker type={'BarChartOutlined'} />}
>
</Menu.Item>
<Menu.Item
disabled
key={'Chart.Line'}
icon={<IconPicker type={'LineChartOutlined'} />}
>
线
</Menu.Item>
<Menu.Item
disabled
key={'Chart.Pie'}
icon={<IconPicker type={'PieChartOutlined'} />}
>
</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu disabled key={'Ref'} title={'引用模板'}>
<Menu.ItemGroup key={'form-select'} title={'选择模板'}>
<Menu.Item key={'Ref.name1'}>1</Menu.Item>
</Menu.ItemGroup>
<Menu.Divider></Menu.Divider>
<Menu.Item key={'addNewRef'}></Menu.Item>
</Menu.SubMenu>
</Menu>
}
>
{ghost ? (
<PlusOutlined />
) : (
<Button type={'dashed'} icon={<PlusOutlined />}>
</Button>
)}
</Dropdown>
);
});
AddNew.FormItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild, deepRemove } =
useDesignable();
const path = useSchemaPath();
const { collection, fields, refresh } = useCollectionContext();
const [visible, setVisible] = useState(false);
const displayed = useDisplayedMapContext();
return (
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
overlayStyle={{
minWidth: 200,
}}
overlay={
<Menu>
<Menu.ItemGroup className={'display-fields'} title={`字段展示`}>
{fields?.map((field) => (
<SwitchMenuItem
key={field.key}
title={field?.uiSchema?.title}
checked={displayed.has(field.name)}
onChange={async (checked) => {
if (!checked) {
const s: any = displayed.get(field.name);
const p = getSchemaPath(s);
const removed = deepRemove(p);
if (!removed) {
console.log('getSchemaPath', p, removed);
return;
}
const last = removed.pop();
displayed.remove(field.name);
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
return;
}
let data: ISchema = {
key: uid(),
type: 'void',
'x-decorator': 'Form.Field.Item',
'x-designable-bar': 'Form.Field.DesignableBar',
'x-component': 'Form.Field',
'x-component-props': {
fieldName: field.name,
},
};
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
}}
/>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu
popupClassName={'add-new-fields-popup'}
className={'sub-menu-add-new-fields'}
title={'新建字段'}
>
{options.map(
(option) =>
option.children.length > 0 && (
<Menu.ItemGroup title={option.label}>
{option.children.map((item) => (
<Menu.Item
style={{ minWidth: 150 }}
key={item.name}
onClick={async () => {
setVisible(false);
const values = await FormDialog(`新增字段`, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={item} />
</FormLayout>
);
}).open({
initialValues: {
interface: item.name,
...item.default,
key: uid(),
name: `f_${uid()}`,
},
});
await createCollectionField(collection?.name, values);
await refresh();
let data: ISchema = cloneDeep(values.uiSchema);
data['name'] = values.name;
data['referenceKey'] = data['key'];
data['key'] = uid();
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
}}
>
{item.title}
</Menu.Item>
))}
</Menu.ItemGroup>
),
)}
</Menu.SubMenu>
{/* <Menu.Divider /> */}
<Menu.Item
onClick={async () => {
let data: ISchema = {
type: 'void',
default: '这是一段演示文字,**支持使用 Markdown 语法**',
'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-decorator': 'FormItem',
'x-read-pretty': true,
'x-component': 'Markdown.Void',
};
if (schema['key']) {
data['key'] = uid();
}
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
console.log('ISchema', schema, data, path);
await createSchema(s);
}
setVisible(false);
}}
>
</Menu.Item>
</Menu>
}
>
{ghost ? (
<PlusOutlined />
) : (
<Button type={'dashed'} icon={<PlusOutlined />}>
</Button>
)}
</Dropdown>
);
});
AddNew.PaneItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
const path = useSchemaPath();
const [visible, setVisible] = useState(false);
return (
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
overlayStyle={{
minWidth: 200,
}}
// placement={'bottomCenter'}
overlay={
<Menu>
<Menu.ItemGroup title={'数据区块'}>
<Menu.Item
icon={<IconPicker type={'FileOutlined'} />}
onClick={async () => {
let data: ISchema = {
type: 'void',
name: uid(),
'x-decorator': 'CardItem',
'x-component': 'Form',
'x-read-pretty': true,
'x-component-props': {
useResource: '{{ Table.useResource }}',
useValues: '{{ Table.useTableRowRecord }}',
},
'x-designable-bar': 'Form.DesignableBar',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
};
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
setVisible(false);
}}
style={{ minWidth: 150 }}
>
</Menu.Item>
<Menu.Item
icon={<IconPicker type={'FormOutlined'} />}
onClick={async () => {
let data: ISchema = {
type: 'void',
name: uid(),
'x-decorator': 'CardItem',
'x-component': 'Form',
'x-component-props': {
useResource: '{{ Table.useResource }}',
useValues: '{{ Table.useTableRowRecord }}',
showDefaultButtons: true,
},
'x-designable-bar': 'Form.DesignableBar',
properties: {
[uid()]: {
type: 'void',
'x-component': 'Grid',
'x-component-props': {
addNewComponent: 'AddNew.FormItem',
},
},
},
};
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
setVisible(false);
}}
>
</Menu.Item>
</Menu.ItemGroup>
<Menu.ItemGroup title={'相关数据区块'}>
<Menu.Item
style={{ minWidth: 150 }}
icon={<IconPicker type={'HistoryOutlined'} />}
>
</Menu.Item>
<Menu.Item icon={<IconPicker type={'CommentOutlined'} />}>
</Menu.Item>
</Menu.ItemGroup>
<Menu.ItemGroup title={'多媒体区块'}>
<Menu.Item
icon={<IconPicker type={'FileMarkdownOutlined'} />}
onClick={async () => {
let data: ISchema = {
key: uid(),
type: 'void',
default: '这是一段演示文字,**支持使用 Markdown 语法**',
'x-designable-bar': 'Markdown.Void.DesignableBar',
'x-decorator': 'CardItem',
'x-read-pretty': true,
'x-component': 'Markdown.Void',
};
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
} else if (isGrid(schema)) {
data = generateGridBlock(data);
}
if (data) {
let s;
if (isGrid(schema)) {
s = appendChild(data, [...path]);
} else if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
}
await createSchema(s);
}
setVisible(false);
}}
>
Markdown
</Menu.Item>
</Menu.ItemGroup>
</Menu>
}
>
{ghost ? (
<PlusOutlined />
) : (
<Button block type={'dashed'} icon={<PlusOutlined />}>
</Button>
)}
</Dropdown>
);
});
AddNew.Displayed = observer((props: any) => {
const { displayName, children } = props;
const displayed = useDisplayedMapContext();
const { schema } = useDesignable();
useEffect(() => {
if (displayName) {
displayed.set(displayName, schema);
}
}, [displayName, schema]);
return children;
});
export default AddNew;