This commit is contained in:
chenos 2021-07-21 21:51:16 +08:00
parent e8399e167e
commit 8d298de6fe
22 changed files with 767 additions and 185 deletions

View File

@ -17,20 +17,46 @@ import {
useHistory,
Redirect,
} from 'react-router-dom';
import { CollectionContextProvider, PageTitleContextProvider, SchemaRenderer, usePageTitleContext } from '../../schemas';
import {
CollectionContextProvider,
PageTitleContextProvider,
SchemaRenderer,
SwithDesignableContextProvider,
usePageTitleContext,
useSwithDesignableContext,
} from '../../schemas';
import { useRequest } from 'ahooks';
import './style.less';
import { uid } from '@formily/shared';
import { ISchema } from '@formily/react';
import Database from './datatable';
import { HighlightOutlined } from '@ant-design/icons';
import { useCookieState } from 'ahooks';
import cls from 'classnames';
function DesignableToggle() {
const { designable, setDesignable } = useSwithDesignableContext();
return (
<Button
className={cls('nb-designable-toggle', { active: designable })}
type={'primary'}
style={{ height: 46, border: 0 }}
onClick={() => {
setDesignable(!designable);
}}
>
<HighlightOutlined />
</Button>
);
}
function LayoutWithMenu({ schema }) {
const match = useRouteMatch<any>();
const location = useLocation();
const sideMenuRef = useRef();
const [activeKey, setActiveKey] = useState(match.params.name);
const [,setPageTitle] = usePageTitleContext();
const [, setPageTitle] = usePageTitleContext();
const onSelect = (info) => {
console.log('LayoutWithMenu', info);
if (!info.schema) {
@ -58,6 +84,7 @@ function LayoutWithMenu({ schema }) {
}}
/>
<Database />
<DesignableToggle />
</Layout.Header>
<Layout>
<Layout.Sider
@ -103,11 +130,13 @@ export function AdminLayout({ route }: any) {
}
return (
<CollectionContextProvider>
<PageTitleContextProvider>
<LayoutWithMenu schema={data} />
</PageTitleContextProvider>
</CollectionContextProvider>
<SwithDesignableContextProvider>
<CollectionContextProvider>
<PageTitleContextProvider>
<LayoutWithMenu schema={data} />
</PageTitleContextProvider>
</CollectionContextProvider>
</SwithDesignableContextProvider>
);
}

View File

@ -47,3 +47,10 @@
}
}
}
.nb-designable-toggle {
background: none !important;
&.active {
background: #1890ff !important;
}
}

View File

@ -19,7 +19,7 @@ import { useMemo } from 'react';
import { CodeOutlined } from '@ant-design/icons';
import Editor from '@monaco-editor/react';
import { ArrayCollapse, ArrayTable, FormLayout } from '@formily/antd';
import { ArrayCollapse, ArrayTable, FormLayout, FormItem as FormilyFormItem } from '@formily/antd';
import { Space, Card, Modal, Spin } from 'antd';
import { Action } from '../../schemas/action';
@ -50,6 +50,7 @@ import { CardItem } from '../../schemas/card-item';
import { DragAndDrop } from '../../schemas/drag-and-drop';
import { TreeSelect } from '../../schemas/tree-select';
import { Page } from '../../schemas/page';
import { useCollectionContext, useSwithDesignableContext } from '../../schemas';
export const BlockContext = createContext({ dragRef: null });
@ -74,6 +75,7 @@ export const SchemaField = createSchemaField({
ArrayTable,
FormLayout,
TreeSelect,
FormilyFormItem,
DragAndDrop,
@ -188,10 +190,12 @@ export function useSchemaComponent(component: string) {
}
export function useDesignable(path?: any) {
const { designable } = useSwithDesignableContext();
const { schema = new Schema({}), refresh } = useContext(DesignableContext);
const schemaPath = path || useSchemaPath();
const fieldSchema = useFieldSchema();
let currentSchema =
let current;
let currentSchema = current =
findPropertyByPath(schema, schemaPath) || ({} as Schema);
if (!currentSchema) {
currentSchema = fieldSchema;
@ -204,6 +208,9 @@ export function useDesignable(path?: any) {
const DesignableBar =
get(options.components, currentSchema['x-designable-bar']) || (() => null);
return {
designable,
root: schema,
currentSchema: current,
DesignableBar,
schema: currentSchema,
refresh,
@ -325,10 +332,14 @@ export function useDesignable(path?: any) {
}
s.parent.removeProperty(s.name);
removed.push(s);
if (s['x-component'] === 'Grid.Row') {
return;
}
if (Object.keys(s.parent.properties || {}).length === 0) {
remove(s.parent);
}
};
console.log({ removed })
remove(target);
refresh();
return removed;
@ -353,12 +364,12 @@ export function getSchemaPath(schema: Schema) {
const path = schema['x-path'] || [schema.name];
let parent = schema.parent;
while (parent) {
if (!parent.name) {
break;
}
// if (!parent.name) {
// break;
// }
if (parent['x-path']) {
path.unshift(...parent['x-path']);
} else {
} else if (parent.name) {
path.unshift(parent.name);
}
parent = parent.parent;
@ -434,6 +445,7 @@ export interface SchemaRendererProps {
}
export const SchemaRenderer = (props: SchemaRendererProps) => {
const { designable } = useSwithDesignableContext();
const [, refresh] = useState(uid());
const form = useMemo(() => props.form || createForm(), []);
const schema = useMemo(() => {
@ -456,6 +468,10 @@ export const SchemaRenderer = (props: SchemaRendererProps) => {
}
return new Schema(s);
}, []);
// useEffect(() => {
// refresh(uid());
// }, [designable]);
console.log({ designable }, 'designable')
const defaultRender = ({ schema }) => (
<FormProvider form={form}>
<SchemaField

View File

@ -19,6 +19,8 @@ const request = extend({
timeout: 1000,
});
// console.log = () => {}
const RouteSwitch = createRouteSwitch({
components: {
AdminLayout,

View File

@ -30,7 +30,7 @@ export const Action: any = observer((props: any) => {
const isDropdownOrPopover =
child &&
['Action.Dropdown', 'Action.Popover'].includes(child['x-component']);
console.log({ DesignableBar, schema });
// console.log({ DesignableBar, schema });
return (
<VisibleContext.Provider value={[visible, setVisible]}>
{!isDropdownOrPopover && (

View File

@ -47,6 +47,7 @@ import {
DatabaseOutlined,
} from '@ant-design/icons';
import {
createCollectionField,
createOrUpdateCollection,
createSchema,
request,
@ -57,6 +58,9 @@ import {
import { uid } from '@formily/shared';
import { useRequest } from 'ahooks';
import { SchemaField } from '../../components/schema-renderer';
import { useFormContext } from '../form';
import { cloneDeep } from 'lodash';
import { options } from '../database-field/interfaces';
const generateGridBlock = (schema: ISchema) => {
const name = schema.name || uid();
@ -429,6 +433,9 @@ function generateCardItemSchema(component) {
name: uid(),
'x-decorator': 'CardItem',
'x-component': 'Form',
'x-component-props': {
showDefaultButtons: true,
},
'x-designable-bar': 'Form.DesignableBar',
properties: {
[uid()]: {
@ -491,9 +498,14 @@ AddNew.CardItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
const path = useSchemaPath();
const { data: collections = [], loading, run } = useRequest('collections:findAll', {
formatResult: (result) => result?.data,
});
// const {
// data: collections = [],
// loading,
// run,
// } = useRequest('collections:findAll', {
// formatResult: (result) => result?.data,
// });
const { data: collections = [], loading, refresh } = useCollectionContext();
console.log({ collections });
return (
<Dropdown
@ -501,9 +513,6 @@ AddNew.CardItem = observer((props: any) => {
minWidth: 200,
}}
onVisibleChange={(visible) => {
if (visible) {
run();
}
console.log('onVisibleChange', visible);
}}
placement={'bottomCenter'}
@ -511,6 +520,7 @@ AddNew.CardItem = observer((props: any) => {
<Menu
onClick={async (info) => {
let data: ISchema;
let collectionName = null;
if (['addNewTable', 'addNewForm'].includes(info.key)) {
const values = await FormDialog(`新建数据表`, () => {
return (
@ -525,13 +535,16 @@ AddNew.CardItem = observer((props: any) => {
},
});
await createOrUpdateCollection(values);
refresh();
data = generateCardItemSchema(
info.key === 'addNewTable' ? 'Table' : 'Form',
);
collectionName = values.name;
} else if (info.key !== 'Markdown') {
const keys = info.key.split('.');
const component = keys.shift();
const tableName = keys.join('.');
collectionName = tableName;
data = generateCardItemSchema(component);
console.log('info.keyPath', component, tableName);
} else {
@ -540,6 +553,10 @@ AddNew.CardItem = observer((props: any) => {
if (schema['key']) {
data['key'] = uid();
}
if (collectionName) {
data['x-component-props'] = data['x-component-props'] || {};
data['x-component-props']['collectionName'] = collectionName;
}
if (isGridBlock(schema)) {
path.pop();
path.pop();
@ -669,8 +686,9 @@ AddNew.BlockItem = observer((props: any) => {
AddNew.FormItem = observer((props: any) => {
const { ghost, defaultAction } = props;
const { schema, insertBefore, insertAfter } = useDesignable();
const { schema, insertBefore, insertAfter, appendChild } = useDesignable();
const path = useSchemaPath();
const { collection = {}, refresh } = useFormContext();
return (
<Dropdown
overlayStyle={{
@ -679,67 +697,136 @@ AddNew.FormItem = observer((props: any) => {
placement={'bottomCenter'}
overlay={
<Menu>
<Menu.ItemGroup title={'选择已有字段'}>
<Menu.Item
style={{
minWidth: 150,
}}
onClick={async () => {
let data: ISchema = generateFormItemSchema('Markdown');
if (schema['key']) {
data['key'] = uid();
}
if (isGridBlock(schema)) {
path.pop();
path.pop();
data = generateGridBlock(data);
}
if (data) {
let s;
if (defaultAction === 'insertAfter') {
s = insertAfter(data, [...path]);
} else {
s = insertBefore(data, [...path]);
<Menu.ItemGroup title={`选择「${collection.title}」里已有字段`}>
{collection?.fields?.map((field) => (
<Menu.Item
key={field.key}
onClick={async () => {
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);
}
await createSchema(s);
}
}}
>
<Checkbox /> 1
</Menu.Item>
<Menu.Item>
<Checkbox /> 2
</Menu.Item>
<Menu.Item>
<Checkbox /> 3
</Menu.Item>
<Menu.Item>
<Checkbox /> 4
</Menu.Item>
<Menu.Item>
<Checkbox /> 5
</Menu.Item>
<Menu.Item>
<Checkbox /> 6
</Menu.Item>
<Menu.Item>
<Checkbox /> 7
</Menu.Item>
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);
}
}}
>
{field?.uiSchema?.title || '未命名'}
</Menu.Item>
))}
</Menu.ItemGroup>
<Menu.Divider />
<Menu.SubMenu title={'新增字段'}>
<Menu.Item
style={{
minWidth: 150,
}}
>
</Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.Item></Menu.Item>
<Menu.SubMenu title={'新建字段'}>
{options.map((option) => (
<Menu.ItemGroup title={option.label}>
{option.children.map((item) => (
<Menu.Item
style={{ minWidth: 150 }}
key={item.name}
onClick={async () => {
const values = await FormDialog(`新增字段`, () => {
return (
<FormLayout layout={'vertical'}>
<SchemaField schema={item} />
</FormLayout>
);
}).open({
initialValues: {
...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 = {
key: uid(),
type: 'string',
default: '这是一段演示文字',
'x-designable-bar': 'Markdown.FormItemDesignableBar',
'x-decorator': 'FormItem',
'x-read-pretty': true,
'x-component': 'Markdown',
'x-component-props': {
savedInSchema: true,
},
};
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.Item>
</Menu>
}
>

View File

@ -39,7 +39,7 @@ const DraggableBlock = (props) => {
<div
onMouseEnter={(e) => {
setActive(true);
console.log('e.onMouseEnter', new Date().toString());
// console.log('e.onMouseEnter', new Date().toString());
}}
onMouseMove={(event) => {
let dropElement = document.elementFromPoint(
@ -65,11 +65,11 @@ const DraggableBlock = (props) => {
if (dropIds.length > 0) {
setActive(dropIds[0] === schema.name);
}
console.log('e.onMouseMove', dropIds, schema.name);
// console.log('e.onMouseMove', dropIds, schema.name);
}}
onMouseLeave={(e) => {
setActive(false);
console.log('e.onMouseLeave', new Date().toString());
// console.log('e.onMouseLeave', new Date().toString());
}}
ref={mergeRefs([previewRef, dropRef])}
className={cls('nb-grid-block', 'designable-form-item', {

View File

@ -6,12 +6,15 @@ export const select: ISchema = {
title: '下拉选择',
group: 'choices',
default: {
interface: 'select',
dataType: 'string',
// name,
uiSchema: {
type: 'string',
// title,
'x-component': 'Select',
'x-decorator': 'FormItem',
'x-designable-bar': 'Markdown.DesignableBar',
enum: [],
} as ISchema,
},

View File

@ -6,12 +6,15 @@ export const string: ISchema = {
title: '单行文本',
group: 'basic',
default: {
interface: 'string',
dataType: 'string',
// name,
uiSchema: {
type: 'string',
// title,
'x-component': 'Input',
'x-decorator': 'FormItem',
'x-designable-bar': 'Input.DesignableBar',
} as ISchema,
},
properties: {

View File

@ -6,11 +6,14 @@ export const subTable: ISchema = {
title: '子表格',
group: 'relation',
default: {
interface: 'subTable',
// name,
uiSchema: {
type: 'string',
// title,
'x-component': 'Select',
'x-decorator': 'FormItem',
'x-component': 'Table',
'x-designable-bar': 'Markdown.DesignableBar',
enum: [],
} as ISchema,
},

View File

@ -11,7 +11,9 @@ export const textarea: ISchema = {
uiSchema: {
type: 'string',
// title,
'x-decorator': 'FormItem',
'x-component': 'Input.TextArea',
'x-designable-bar': 'Markdown.DesignableBar',
} as ISchema,
},
properties: {

View File

@ -0,0 +1,94 @@
import React, { useContext, useMemo, useRef, useState } from 'react';
import { createForm } from '@formily/core';
import {
SchemaOptionsContext,
Schema,
useFieldSchema,
observer,
SchemaExpressionScopeContext,
FormProvider,
ISchema,
useField,
useForm,
RecursionField,
} from '@formily/react';
import {
useSchemaPath,
SchemaField,
useDesignable,
removeSchema,
useCollectionContext,
} from '../';
import get from 'lodash/get';
import { Button, Dropdown, Menu, Space } from 'antd';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import cls from 'classnames';
import { FormLayout } from '@formily/antd';
import './style.less';
import AddNew from '../add-new';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import { isGridRowOrCol } from '../grid';
import constate from 'constate';
import { useEffect } from 'react';
import { uid } from '@formily/shared';
import { getSchemaPath } from '../../components/schema-renderer';
export const DesignableBar = observer((props) => {
const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
if (!designable) {
return null;
}
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
field.readPretty = true;
}}
>
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
});

View File

@ -0,0 +1,114 @@
import React, { useContext, useMemo, useRef, useState } from 'react';
import { createForm } from '@formily/core';
import {
SchemaOptionsContext,
Schema,
useFieldSchema,
observer,
SchemaExpressionScopeContext,
FormProvider,
ISchema,
useField,
useForm,
RecursionField,
} from '@formily/react';
import {
useSchemaPath,
SchemaField,
useDesignable,
removeSchema,
useCollectionContext,
updateSchema,
} from '../';
import get from 'lodash/get';
import { Button, Dropdown, Menu, Space } from 'antd';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import cls from 'classnames';
import { FormLayout } from '@formily/antd';
import './style.less';
import AddNew from '../add-new';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import { isGridRowOrCol } from '../grid';
import constate from 'constate';
import { useEffect } from 'react';
import { uid } from '@formily/shared';
import { getSchemaPath } from '../../components/schema-renderer';
import { FormFieldUIDContext } from '.';
export const FieldDesignableBar = observer((props) => {
const field = useField();
const fieldSchema = useFieldSchema();
const { designable, schema, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
const fieldUid = useContext(FormFieldUIDContext);
const fieldName = schema['x-component-props']?.['fieldName'];
if (!designable) {
return null;
}
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={async () => {
const title = uid();
field
.query(field.address.concat(fieldUid, fieldName))
.take((f) => {
f.title = title;
});
console.log(
'fieldfieldfieldfieldfield',
field.address.concat(fieldUid, fieldName).entire,
schema,
);
schema['title'] = title;
await updateSchema({
key: schema['key'],
title,
});
}}
>
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
console.log({ schema, removed });
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
});

View File

@ -20,6 +20,13 @@ group:
// 添加其他节点
</Form>
<Form>
// Form.Field 可用于覆盖字段原配置,但不修改原字段配置,新配置只作用于当前 Form
<Form.Field title={'自定义标题'}>
<Input title={'原标题'}>
</Form.Field>
</Form>
// Form 用作 decorator 时只用于创建独立的作用域
<Table x-decorator={'Form'}/>
</pre>

View File

@ -9,10 +9,18 @@ import {
FormProvider,
ISchema,
useField,
useForm,
RecursionField,
} from '@formily/react';
import { useSchemaPath, SchemaField, useDesignable, removeSchema } from '../';
import {
useSchemaPath,
SchemaField,
useDesignable,
removeSchema,
useCollectionContext,
} from '../';
import get from 'lodash/get';
import { Dropdown, Menu, Space } from 'antd';
import { Button, Dropdown, Menu, Space } from 'antd';
import { MenuOutlined, DragOutlined } from '@ant-design/icons';
import cls from 'classnames';
import { FormLayout } from '@formily/antd';
@ -20,9 +28,34 @@ import './style.less';
import AddNew from '../add-new';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import { isGridRowOrCol } from '../grid';
import constate from 'constate';
import { useEffect } from 'react';
import { uid } from '@formily/shared';
import { getSchemaPath } from '../../components/schema-renderer';
import { DesignableBar } from './DesignableBar';
import { FieldDesignableBar } from './Field.DesignableBar';
import { createContext } from 'react';
import { BlockItem } from '../block-item';
const [FormContextProvider, useFormContext] = constate(({ schema, form }) => {
// const schema = useFieldSchema();
const collectionName = schema['x-component-props']?.['collectionName'];
const { data: collections = [], loading, refresh } = useCollectionContext();
const collection = collectionName
? collections.find((item) => item.name === collectionName)
: {};
console.log({ collection });
return { form, schema, collection, refresh };
});
export { FormContextProvider, useFormContext };
export const Form: any = observer((props: any) => {
const { useValues = () => ({}), ...others } = props;
const {
useValues = () => ({}),
showDefaultButtons = false,
...others
} = props;
const initialValues = useValues();
const form = useMemo(() => {
console.log('Form.useMemo', initialValues);
@ -33,91 +66,105 @@ export const Form: any = observer((props: any) => {
const scope = useContext(SchemaExpressionScopeContext);
return (
<FormProvider form={form}>
{schema['x-decorator'] === 'Form' ? (
<SchemaField
scope={scope}
schema={{
type: 'object',
properties: {
[schema.name]: {
...schema.toJSON(),
'x-path': path,
// 避免死循环
'x-decorator': 'Form.__Decorator',
},
},
}}
/>
) : (
<FormLayout layout={'vertical'} {...others}>
{/* @ts-ignore */}
<FormContextProvider schema={schema} form={form}>
{schema['x-decorator'] === 'Form' ? (
<SchemaField
scope={scope}
schema={{
type: 'object',
properties: schema.properties,
properties: {
[schema.name]: {
...schema.toJSON(),
'x-path': path,
// 避免死循环
'x-decorator': 'Form.__Decorator',
},
},
}}
/>
</FormLayout>
)}
) : (
<FormLayout layout={'vertical'} {...others}>
<SchemaField
scope={scope}
schema={{
type: 'object',
properties: schema.properties,
}}
/>
</FormLayout>
)}
{showDefaultButtons && (
<Space style={{ marginTop: 24 }}>
<Button
onClick={async () => {
const values = await form.submit();
console.log({ values });
}}
type={'primary'}
>
</Button>
<Button
onClick={async () => {
await form.reset();
}}
>
</Button>
</Space>
)}
</FormContextProvider>
</FormProvider>
);
});
Form.__Decorator = ({ children }) => children;
export const FormFieldUIDContext = createContext(null);
Form.DesignableBar = observer((props) => {
const field = useField();
const { schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
Form.Field = observer((props: any) => {
const { fieldName } = props;
const { schema } = useDesignable();
const path = getSchemaPath(schema);
console.log('getSchemaPath', path);
const { collection = {} } = useFormContext();
if (!fieldName) {
return null;
}
const field = collection?.fields?.find((item) => item.name === fieldName);
if (!field) {
return null;
}
const name = useContext(FormFieldUIDContext);
const title = schema['title'] || field.uiSchema['title'];
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
field.readPretty = false;
}}
>
Markdown
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
<RecursionField
name={name}
schema={
new Schema({
'x-path': path,
type: 'void',
properties: {
[field.name]: {
...field.uiSchema,
title,
'x-decorator': 'FormilyFormItem',
},
},
})
}
/>
);
});
Form.Field.Item = observer((props) => {
const name = uid();
return (
<FormFieldUIDContext.Provider value={name}>
<BlockItem>{props.children}</BlockItem>
</FormFieldUIDContext.Provider>
);
});
Form.__Decorator = ({ children }) => children;
Form.DesignableBar = DesignableBar;
Form.Field.DesignableBar = FieldDesignableBar;

View File

@ -89,6 +89,7 @@ export const Grid: any = observer((props: any) => {
// const schema = useFieldSchema();
const gridPath = useSchemaPath();
const {
designable,
schema,
schema: designableSchema,
insertAfter,
@ -166,7 +167,7 @@ export const Grid: any = observer((props: any) => {
</>
);
})}
{AddNewComponent && <AddNewComponent />}
{designable && AddNewComponent && <AddNewComponent />}
</div>
</DragDropManagerProvider>
);
@ -187,7 +188,7 @@ Grid.Row = observer((props) => {
deepRemove,
} = useDesignable();
const len = Object.keys(designableSchema.properties || {}).length;
console.log({ len, schema, designableSchema });
// console.log({ len, schema, designableSchema });
return (
<ColumnSizeContext.Provider value={len}>
<ColDivider

View File

@ -6,7 +6,7 @@ import { createContext } from 'react';
import { extend } from 'umi-request';
import { isVoidField } from '@formily/core';
import { useRequest } from 'ahooks';
import { useCookieState, useRequest } from 'ahooks';
export * from '../components/schema-renderer';
@ -14,19 +14,19 @@ export function mapReadPretty(component, readPrettyProps) {
return function (target) {
return observer(
(props) => {
const field = useField()
const field = useField();
if (!isVoidField(field) && field?.pattern === 'readPretty') {
return React.createElement(component, {
...readPrettyProps,
...props,
})
});
}
return React.createElement(target, props)
return React.createElement(target, props);
},
{
forwardRef: true,
}
)
},
);
};
}
@ -57,6 +57,13 @@ export async function createOrUpdateCollection(data: any) {
});
}
export async function createCollectionField(collectionName: string, data: any) {
return await request(`collections/${collectionName}/fields:create`, {
method: 'post',
data,
});
}
export async function deleteCollection(name) {
await request('collections:destroy', {
method: 'post',
@ -109,3 +116,16 @@ const [CollectionContextProvider, useCollectionContext] = constate(() => {
});
export { CollectionContextProvider, useCollectionContext };
const [SwithDesignableContextProvider, useSwithDesignableContext] = constate(() => {
const [active, setActive] = useCookieState('useCookieDesignable');
return {
designable: active === 'true',
setDesignable(value) {
setActive(value ? 'true' : 'false');
}
}
});
export { SwithDesignableContextProvider, useSwithDesignableContext };

View File

@ -1,10 +1,19 @@
import React from 'react'
import { connect, mapProps, mapReadPretty } from '@formily/react'
import { connect, mapProps, mapReadPretty, observer, useField, useFieldSchema } from '@formily/react'
import { Input as AntdInput } from 'antd'
import { InputProps, TextAreaProps } from 'antd/lib/input'
import { LoadingOutlined } from '@ant-design/icons'
import { Display } from '../display';
import { DesignableBar } from './DesignableBar';
import { Dropdown, Menu, Space } from 'antd';
import { LoadingOutlined, MenuOutlined, DragOutlined } from '@ant-design/icons';
import { getSchemaPath, useDesignable } from '../../components/schema-renderer';
import { useContext, useState } from 'react';
import AddNew from '../add-new';
import cls from 'classnames';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import { uid } from '@formily/shared';
import { removeSchema, updateSchema } from '..';
import { isGridRowOrCol } from '../grid';
type ComposedInput = React.FC<InputProps> & {
TextArea?: React.FC<TextAreaProps>
@ -15,6 +24,7 @@ type ComposedInput = React.FC<InputProps> & {
export const Input: ComposedInput = connect(
AntdInput,
mapProps((props, field) => {
console.log('fieldfieldfieldfieldfield', field.address.entire);
return {
...props,
suffix: (
@ -31,8 +41,65 @@ export const Input: ComposedInput = connect(
mapReadPretty(Display.Input)
)
Input.DesignableBar = DesignableBar;
Input.TextArea = connect(AntdInput.TextArea, mapReadPretty(Display.TextArea))
Input.URL = connect(AntdInput, mapReadPretty(Display.URL))
Input.DesignableBar = observer((props) => {
const field = useField();
const fieldSchema = useFieldSchema();
const { root, currentSchema, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.CardItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
console.log({ root, currentSchema, schema, fieldSchema }, getSchemaPath(fieldSchema));
}}
>
Input
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
});
export default Input

View File

@ -20,6 +20,7 @@ import { DraggableBlockContext } from '../../components/drag-and-drop';
import { uid } from '@formily/shared';
import { removeSchema, updateSchema } from '..';
import { isGridRowOrCol } from '../grid';
import './style.less';
export const Markdown: any = connect(
(props) => {
@ -67,7 +68,7 @@ export const Markdown: any = connect(
console.log('Markdown', props);
let text = props.value;
let value = (
<div dangerouslySetInnerHTML={{ __html: micromark(text || '') }} />
<div className={'nb-markdown'} dangerouslySetInnerHTML={{ __html: micromark(text || '') }} />
);
return <Display.TextArea {...props} text={text} value={value} />;
}),
@ -75,9 +76,12 @@ export const Markdown: any = connect(
Markdown.DesignableBar = observer((props) => {
const field = useField();
const { schema, refresh, deepRemove } = useDesignable();
const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
if (!designable) {
return null;
}
return (
<div className={cls('designable-bar', { active: visible })}>
<span
@ -117,7 +121,67 @@ Markdown.DesignableBar = observer((props) => {
}
}}
>
</Menu.Item>
</Menu>
}
>
<MenuOutlined />
</Dropdown>
</Space>
</span>
</div>
);
});
Markdown.FormItemDesignableBar = observer((props) => {
const field = useField();
const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
if (!designable) {
return null;
}
return (
<div className={cls('designable-bar', { active: visible })}>
<span
onClick={(e) => {
e.stopPropagation();
}}
className={cls('designable-bar-actions', { active: visible })}
>
<Space size={'small'}>
<AddNew.FormItem defaultAction={'insertAfter'} ghost />
{dragRef && <DragOutlined ref={dragRef} />}
<Dropdown
trigger={['click']}
visible={visible}
onVisibleChange={(visible) => {
setVisible(visible);
}}
overlay={
<Menu>
<Menu.Item
key={'update'}
onClick={() => {
field.readPretty = false;
}}
>
</Menu.Item>
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>
</Menu.Item>
</Menu>
}

View File

@ -0,0 +1,3 @@
.nb-markdown > *:last-child {
margin-bottom: 0;
}

View File

@ -97,7 +97,7 @@ const SideMenu = (props: any) => {
export const Menu: any = observer((props: any) => {
const { mode, onSelect, sideMenuRef, ...others } = props;
const { schema } = useDesignable();
const { designable, schema } = useDesignable();
const fieldSchema = useFieldSchema();
console.log('Menu.schema', schema, fieldSchema);
const [selectedKey, setSelectedKey] = useState(null);
@ -275,8 +275,10 @@ Menu.SubMenu = observer((props: any) => {
});
Menu.AddNew = observer((props: any) => {
const { appendChild } = useDesignable(props.path);
const { designable, appendChild } = useDesignable(props.path);
if (!designable) {
return null;
}
const schemas = {
'Menu.Link': {
icon: <MenuOutlined />,
@ -516,11 +518,15 @@ Menu.DesignableBar = (props) => {
const field = useField();
const [visible, setVisible] = useState(false);
const { schema, remove, refresh, insertAfter, insertBefore, appendChild } =
const { designable, schema, remove, refresh, insertAfter, insertBefore, appendChild } =
useDesignable();
const formConfig = schemas[schema['x-component']];
const isSubMenu = schema['x-component'] === 'Menu.SubMenu';
if (!designable) {
return null;
}
return (
<div className={cls('designable-bar', { active: visible })}>
<div

View File

@ -32,10 +32,11 @@ import {
SortableElement,
} from 'react-sortable-hoc';
import cls from 'classnames';
import { getSchemaPath, useDesignable, useSchemaPath, VisibleContext } from '../';
import { getSchemaPath, removeSchema, useDesignable, useSchemaPath, VisibleContext } from '../';
import './style.less';
import { DraggableBlockContext } from '../../components/drag-and-drop';
import AddNew from '../add-new';
import { isGridRowOrCol } from '../grid';
interface TableRowProps {
index: number;
@ -446,7 +447,6 @@ function useDesignableBar() {
Table.Column = observer((props) => {
const schema = useFieldSchema();
const field = useField();
console.log('Table.Column', schema, field.title);
const { DesignableBar } = useDesignableBar();
return (
<div className={'nb-table-column'}>
@ -461,7 +461,6 @@ Table.Column.DesignableBar = () => {
// const fieldSchema = useFieldSchema();
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
console.log('Table.Column.DesignableBar', { schema });
return (
<div className={cls('designable-bar', { active: visible })}>
<span
@ -571,7 +570,7 @@ Table.Index = observer((props) => {
const schema = useFieldSchema();
const field = useField<Formily.Core.Models.Field>();
const path = useSchemaPath();
return <div>#{index + 1}</div>;
return <div>{index + 1}</div>;
});
Table.Addition = observer((props: any) => {
@ -605,7 +604,7 @@ Table.Action.DesignableBar = () => {
const path = useSchemaPath();
const { schema, remove, refresh, insertAfter } = useDesignable();
const [visible, setVisible] = useState(false);
console.log('Table.Action.DesignableBar', path, field.address.entire, { schema, field });
// console.log('Table.Action.DesignableBar', path, field.address.entire, { schema, field });
return (
<div className={cls('designable-bar', { active: visible })}>
<span
@ -638,9 +637,12 @@ Table.Action.DesignableBar = () => {
Table.DesignableBar = observer((props) => {
const field = useField();
const { schema, refresh, deepRemove } = useDesignable();
const { designable, schema, refresh, deepRemove } = useDesignable();
const [visible, setVisible] = useState(false);
const { dragRef } = useContext(DraggableBlockContext);
if (!designable) {
return null;
}
return (
<div className={cls('designable-bar', { active: visible })}>
<span
@ -671,8 +673,13 @@ Table.DesignableBar = observer((props) => {
<Menu.Divider />
<Menu.Item
key={'delete'}
onClick={() => {
deepRemove();
onClick={async () => {
const removed = deepRemove();
// console.log({ removed })
const last = removed.pop();
if (isGridRowOrCol(last)) {
await removeSchema(last);
}
}}
>