import { createForm } from '@formily/core';
import {
createSchemaField,
FormProvider,
ISchema,
Schema,
SchemaOptionsContext,
useFieldSchema,
useForm,
} from '@formily/react';
import { uid } from '@formily/shared';
import constate from 'constate';
import { get } from 'lodash';
import React, { createContext } from 'react';
import { useState } from 'react';
import { useContext } from 'react';
import { useMemo } from 'react';
import { CodeOutlined } from '@ant-design/icons';
import Editor from '@monaco-editor/react';
import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd';
import { Space, Card, Modal, Spin } from 'antd';
import { Action } from '../../schemas/action';
import { AddNew } from '../../schemas/add-new';
import { Cascader } from '../../schemas/cascader';
import { Checkbox } from '../../schemas/checkbox';
import { ColorSelect } from '../../schemas/color-select';
import { DatabaseField, DatabaseCollection } from '../../schemas/database-field';
import { DatePicker } from '../../schemas/date-picker';
import { Filter } from '../../schemas/filter';
import { Form } from '../../schemas/form';
import { Grid } from '../../schemas/grid';
import { IconPicker } from '../../schemas/icon-picker';
import { Input } from '../../schemas/input';
import { InputNumber } from '../../schemas/input-number';
import { Markdown } from '../../schemas/markdown';
import { Menu } from '../../schemas/menu';
import { Password } from '../../schemas/password';
import { Radio } from '../../schemas/radio';
import { Select } from '../../schemas/select';
import { Table } from '../../schemas/table';
import { Tabs } from '../../schemas/tabs';
import { TimePicker } from '../../schemas/time-picker';
import { Upload } from '../../schemas/upload';
import { FormItem } from '../../schemas/form-item';
import { BlockItem } from '../../schemas/block-item';
import { DragAndDrop } from '../../schemas/drag-and-drop';
import { TreeSelect } from '../../schemas/tree-select';
export const BlockContext = createContext({ dragRef: null });
const Div = (props) =>
;
interface DesignableContextProps {
schema?: Schema;
refresh?: any;
}
export const SchemaField = createSchemaField({
scope: {},
components: {
Card,
Div,
Space,
ArrayCollapse,
ArrayTable,
FormLayout,
TreeSelect,
DragAndDrop,
BlockItem,
FormItem,
Action,
AddNew,
Cascader,
Checkbox,
ColorSelect,
DatabaseField,
DatabaseCollection,
DatePicker,
Filter,
Form,
Grid,
IconPicker,
Input,
InputNumber,
Markdown,
Menu,
Password,
Radio,
Select,
Table,
Tabs,
TimePicker,
Upload,
},
});
export const DesignableContext = createContext({});
export function useSchema(path?: any) {
const { schema, refresh } = useContext(DesignableContext);
return { schema, refresh };
}
export function pathToArray(path): string[] {
if (Array.isArray(path)) {
return [...path];
}
if (typeof path === 'string') {
return path.split('.');
}
}
export function findPropertyByPath(schema: Schema, path?: any): Schema {
if (!path) {
return schema;
}
const arr = pathToArray(path);
let property = schema;
while (arr.length) {
const name = arr.shift();
if (!property.properties) {
console.warn('property does not exist.');
return null;
}
property = property.properties[name];
if (!property) {
console.warn('property does not exist.');
return null;
}
}
return property;
}
export function addPropertyBefore(target: Schema, data: ISchema) {
Object.keys(target.parent.properties).forEach((name) => {
if (name === target.name) {
target.parent.addProperty(data.name, data);
}
const property = target.parent.properties[name];
property.parent.removeProperty(property.name);
target.parent.addProperty(property.name, property.toJSON());
});
}
export function addPropertyAfter(target: Schema, data: ISchema) {
Object.keys(target.parent.properties).forEach((name) => {
const property = target.parent.properties[name];
property.parent.removeProperty(property.name);
target.parent.addProperty(property.name, property.toJSON());
if (name === target.name) {
target.parent.addProperty(data.name, data);
}
});
}
function setKeys(schema: ISchema, parentKey = null) {
if (!schema['key']) {
schema['key'] = uid();
}
if (parentKey && !schema['parentKey']) {
schema['parentKey'] = parentKey;
}
Object.keys(schema.properties || {}).forEach((name) => {
setKeys(schema.properties[name], schema['key']);
});
}
export function useDesignable(path?: any) {
const { schema = new Schema({}), refresh } = useContext(DesignableContext);
const schemaPath = path || useSchemaPath();
const fieldSchema = useFieldSchema();
let currentSchema =
findPropertyByPath(schema, schemaPath) || ({} as Schema);
if (!currentSchema) {
currentSchema = fieldSchema;
}
if (Object.keys(currentSchema).length === 0) {
currentSchema = fieldSchema;
}
// console.log('useDesignable', { schema, schemaPath, currentSchema });
const options = useContext(SchemaOptionsContext);
const DesignableBar =
get(options.components, currentSchema['x-designable-bar']) || (() => null);
return {
DesignableBar,
schema: currentSchema,
refresh,
prepend: (property: ISchema, targetPath?: any): Schema => {
let target = currentSchema;
if (targetPath) {
target = findPropertyByPath(schema, targetPath);
}
if (!target) {
console.error('target schema does not exist.');
return;
}
if (!property.name) {
property.name = uid();
}
if (target['key']) {
property['parentKey'] = target['key'];
setKeys(property);
property['__prepend__'] = true;
// if (!property['key']) {
// property['key'] = uid();
// }
}
const properties = {};
properties[property.name] = property;
Object.keys(target.properties).forEach((name, index) => {
const current = target.properties[name];
current.parent.removeProperty(current.name);
properties[current.name] = current.toJSON();
});
// console.log({ properties }, target.properties);
target.setProperties(properties);
refresh();
return target.properties[property.name];
},
appendChild: (property: ISchema, targetPath?: any): Schema => {
let target = currentSchema;
if (targetPath) {
target = findPropertyByPath(schema, targetPath);
}
if (!target) {
console.error('target schema does not exist.');
return;
}
if (!property.name) {
property.name = uid();
}
if (target['key']) {
property['parentKey'] = target['key'];
// if (!property['key']) {
// property['key'] = uid();
// }
setKeys(property);
}
target.addProperty(property.name, property);
// BUG: 空 properties 时,addProperty 无反应。
const tmp = { name: uid() };
addPropertyAfter(target, tmp);
target.parent.removeProperty(tmp.name);
refresh();
return target.properties[property.name];
},
insertAfter: (property: ISchema, targetPath?: any): Schema => {
let target = currentSchema;
if (targetPath) {
target = findPropertyByPath(schema, targetPath);
}
if (!target) {
console.error('target schema does not exist.');
return;
}
if (!property.name) {
property.name = uid();
}
console.log('target.parentKey', target);
if (target['parentKey']) {
property['parentKey'] = target['parentKey'];
setKeys(property);
property['__insertAfter__'] = target['key'];
}
addPropertyAfter(target, property);
refresh();
return target.parent.properties[property.name];
},
insertBefore(property: ISchema, targetPath?: any): Schema {
let target = currentSchema;
if (targetPath) {
target = findPropertyByPath(schema, targetPath);
}
if (!target) {
console.error('target schema does not exist.');
return;
}
if (!property.name) {
property.name = uid();
}
if (target['parentKey']) {
property['parentKey'] = target['parentKey'];
setKeys(property);
property['__insertBefore__'] = target['key'];
}
addPropertyBefore(target, property);
refresh();
return target.parent.properties[property.name];
},
deepRemove(targetPath?: any) {
let target = currentSchema;
if (targetPath) {
target = findPropertyByPath(schema, targetPath);
}
if (!target) {
console.error('target schema does not exist.');
return;
}
const removed = [];
const remove = (s: Schema) => {
if (!s.parent) {
return;
}
s.parent.removeProperty(s.name);
removed.push(s);
if (Object.keys(s.parent.properties || {}).length === 0) {
remove(s.parent);
}
};
remove(target);
refresh();
return removed;
},
remove(targetPath?: any) {
let target = currentSchema;
if (targetPath) {
target = findPropertyByPath(schema, targetPath);
}
if (!target) {
console.error('target schema does not exist.');
return;
}
target.parent.removeProperty(target.name);
refresh();
return target;
},
};
}
export function getSchemaPath(schema: Schema) {
const path = schema['x-path'] || [schema.name];
let parent = schema.parent;
while (parent) {
if (!parent.name) {
break;
}
if (parent['x-path']) {
path.unshift(...parent['x-path']);
} else {
path.unshift(parent.name);
}
parent = parent.parent;
}
// console.log('getSchemaPath', path, schema);
return [...path];
}
export function useSchemaPath() {
const schema = useFieldSchema();
const path = getSchemaPath(schema);
return [...path];
}
const FormValues = () => {
const form = useForm();
const [visible, setVisible] = useState(false);
return (
<>
setVisible(true)} />
setVisible(false)}
onCancel={() => setVisible(false)}
visible={visible}
>
{/* {JSON.stringify(schema.toJSON(), null, 2)}
*/}
>
);
};
const CodePreview = ({ schema }) => {
const [visible, setVisible] = useState(false);
return (
<>
setVisible(true)}
/>
setVisible(false)}
onCancel={() => setVisible(false)}
visible={visible}
>
{/* {JSON.stringify(schema.toJSON(), null, 2)}
*/}
>
);
};
export interface SchemaRendererProps {
schema: Schema | ISchema;
form?: any;
render?: any;
components?: any;
scope?: any;
debug?: boolean;
onlyRenderProperties?: boolean;
onRefresh?: any;
[key: string]: any;
}
export const SchemaRenderer = (props: SchemaRendererProps) => {
const [, refresh] = useState(uid());
const form = useMemo(() => props.form || createForm(), []);
const schema = useMemo(() => {
if (Schema.isSchemaInstance(props.schema)) {
return schema;
}
let s = props.schema;
if (props.onlyRenderProperties) {
s = {
type: 'object',
properties: s.properties,
};
} else if (s.name) {
s = {
type: 'object',
properties: {
[s.name]: s,
},
};
}
return new Schema(s);
}, []);
const defaultRender = ({ schema }) => (
{props.debug && }
{props.debug && }
);
return (
{
props.onRefresh && props.onRefresh(schema);
refresh(uid());
},
}}
>
{props.render || defaultRender}
);
};