From ab457ade220fc1d84f975e5ea539bfbcf736db29 Mon Sep 17 00:00:00 2001 From: chenos Date: Thu, 10 Feb 2022 17:07:53 +0800 Subject: [PATCH] feat: improve code --- .../src/application/demos/demo2/mock.ts | 11 + .../antd/card-item/CardItem.tsx | 2 +- .../antd/grid/AddBlockItem.tsx | 133 +++++++++++ .../antd/grid/AddFormItem.tsx | 147 ++++++++++++ .../src/schema-component/antd/grid/Grid.tsx | 214 ++++++++++++++++++ .../antd/grid/demos/demo2.tsx | 160 +------------ .../antd/grid/demos/demo3.tsx | 145 +----------- .../src/schema-component/antd/grid/index.tsx | 177 +-------------- .../core/RemoteSchemaComponent.tsx | 2 +- .../schema-component/hooks/useDesignable.tsx | 16 +- 10 files changed, 529 insertions(+), 478 deletions(-) create mode 100644 packages/client/src/schema-component/antd/grid/AddBlockItem.tsx create mode 100644 packages/client/src/schema-component/antd/grid/AddFormItem.tsx create mode 100644 packages/client/src/schema-component/antd/grid/Grid.tsx diff --git a/packages/client/src/application/demos/demo2/mock.ts b/packages/client/src/application/demos/demo2/mock.ts index 145ba8482..1cc54e16d 100644 --- a/packages/client/src/application/demos/demo2/mock.ts +++ b/packages/client/src/application/demos/demo2/mock.ts @@ -1,3 +1,4 @@ +import { uid } from '@formily/shared'; import { APIClient } from '@nocobase/client'; import MockAdapter from 'axios-mock-adapter'; @@ -119,6 +120,16 @@ export default (apiClient: APIClient) => { name: name, 'x-uid': name, 'x-component': 'Page', + properties: { + [uid()]: { + type: 'void', + name: 'grid1', + 'x-component': 'Grid', + 'x-item-initializer': 'Grid.AddBlockItem', + 'x-uid': uid(), + properties: {}, + }, + }, }, }; return [200, response]; diff --git a/packages/client/src/schema-component/antd/card-item/CardItem.tsx b/packages/client/src/schema-component/antd/card-item/CardItem.tsx index 1b347bcf7..14bc9c72e 100644 --- a/packages/client/src/schema-component/antd/card-item/CardItem.tsx +++ b/packages/client/src/schema-component/antd/card-item/CardItem.tsx @@ -5,7 +5,7 @@ import { BlockItem } from '../block-item'; export const CardItem: React.FC = (props) => { return ( - + {props.children} diff --git a/packages/client/src/schema-component/antd/grid/AddBlockItem.tsx b/packages/client/src/schema-component/antd/grid/AddBlockItem.tsx new file mode 100644 index 000000000..04a645e47 --- /dev/null +++ b/packages/client/src/schema-component/antd/grid/AddBlockItem.tsx @@ -0,0 +1,133 @@ +import { FormOutlined, TableOutlined } from '@ant-design/icons'; +import { ISchema, observer } from '@formily/react'; +import { uid } from '@formily/shared'; +import React from 'react'; +import { SchemaInitializer } from '../../../schema-initializer'; + +const gridRowColWrap = (schema: ISchema) => { + return { + type: 'void', + 'x-component': 'Grid.Row', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid.Col', + properties: { + [schema.name || uid()]: schema, + }, + }, + }, + }; +}; + +const itemWrap = SchemaInitializer.itemWrap; + +const TableBlockInitializerItem = itemWrap((props) => { + const { insert } = props; + return ( + } + onClick={() => { + insert({ + type: 'void', + 'x-component': 'VoidTable', + 'x-decorator': 'CardItem', + }); + }} + items={[ + { + type: 'itemGroup', + title: 'select a data source', + children: [ + { + type: 'item', + name: 'users', + title: 'Users', + }, + { + type: 'item', + name: 'posts', + title: 'Posts', + }, + ], + }, + ]} + > + Table + + ); +}); + +const FormBlockInitializerItem = itemWrap((props) => { + const { insert } = props; + return ( + } + onClick={() => { + insert({ + type: 'void', + 'x-uid': uid(), + 'x-component': 'Form', + 'x-decorator': 'CardItem', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid', + 'x-item-initializer': 'Grid.AddFormItem', + 'x-uid': uid(), + }, + }, + }); + }} + items={[ + { + type: 'itemGroup', + title: 'select a data source', + children: [ + { + type: 'item', + name: 'users', + title: 'Users', + }, + { + type: 'item', + name: 'posts', + title: 'Posts', + }, + ], + }, + ]} + > + Form + + ); +}); + +export const AddGridBlockItem = observer((props: any) => { + return ( + + Add block + + ); +}); diff --git a/packages/client/src/schema-component/antd/grid/AddFormItem.tsx b/packages/client/src/schema-component/antd/grid/AddFormItem.tsx new file mode 100644 index 000000000..92c9e54b3 --- /dev/null +++ b/packages/client/src/schema-component/antd/grid/AddFormItem.tsx @@ -0,0 +1,147 @@ +import { ISchema, observer, Schema, useFieldSchema } from '@formily/react'; +import { uid } from '@formily/shared'; +import { Switch } from 'antd'; +import React from 'react'; +import { SchemaInitializer, SchemaInitializerItemOptions } from '../../../schema-initializer'; +import { useDesignable } from '../../hooks'; + +const useFormItemInitializerFields = () => { + return [ + { + type: 'item', + title: 'Name', + component: InitializeFormItem, + schema: { + name: 'name', + type: 'string', + title: 'Name', + 'x-component': 'Input', + 'x-decorator': 'FormItem', + 'x-collection-field': 'posts.name', + }, + }, + { + type: 'item', + title: 'Title', + component: InitializeFormItem, + schema: { + name: 'title', + type: 'string', + title: 'Title', + 'x-component': 'Input', + 'x-decorator': 'FormItem', + 'x-collection-field': 'posts.title', + }, + }, + ] as SchemaInitializerItemOptions[]; +}; + +const gridRowColWrap = (schema: ISchema) => { + return { + type: 'void', + 'x-component': 'Grid.Row', + properties: { + [uid()]: { + type: 'void', + 'x-component': 'Grid.Col', + properties: { + [schema.name || uid()]: schema, + }, + }, + }, + }; +}; + +const useCurrentFieldSchema = (path: string) => { + const fieldSchema = useFieldSchema(); + const { remove } = useDesignable(); + const findFieldSchema = (schema: Schema) => { + return schema.reduceProperties((buf, s) => { + if (s['x-collection-field'] === path) { + return s; + } + const child = findFieldSchema(s); + if (child) { + return child; + } + return buf; + }); + }; + const schema = findFieldSchema(fieldSchema); + return { + schema, + exists: !!schema, + remove() { + schema && + remove(schema, { + breakComponent: 'Grid', + removeEmptyParents: true, + }); + }, + }; +}; + +const itemWrap = SchemaInitializer.itemWrap; + +const InitializeFormItem = itemWrap((props) => { + const { item, insert } = props; + const { schema, exists, remove } = useCurrentFieldSchema(item.schema['x-collection-field']); + return ( + { + if (exists) { + return remove(); + } + insert({ + ...item.schema, + }); + }} + > +
+ {item.title} +
+
+ ); +}); + +const InitializeTextFormItem = itemWrap((props) => { + const { insert } = props; + return ( + { + insert({ + type: 'void', + 'x-component': 'Markdown.Void', + 'x-decorator': 'FormItem', + // 'x-editable': false, + }); + }} + /> + ); +}); + +export const AddGridFormItem = observer((props: any) => { + return ( + + Configure fields + + ); +}); diff --git a/packages/client/src/schema-component/antd/grid/Grid.tsx b/packages/client/src/schema-component/antd/grid/Grid.tsx new file mode 100644 index 000000000..9c7119838 --- /dev/null +++ b/packages/client/src/schema-component/antd/grid/Grid.tsx @@ -0,0 +1,214 @@ +import { useDndMonitor, useDroppable } from '@dnd-kit/core'; +import { css } from '@emotion/css'; +import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; +import { uid } from '@formily/shared'; +import cls from 'classnames'; +import React, { createContext, useContext, useState } from 'react'; +import { useComponent } from '../..'; +import { DndContext } from '../../common/dnd-context'; +import { AddGridBlockItem } from './AddBlockItem'; +import { AddGridFormItem } from './AddFormItem'; + +const GridRowContext = createContext(null); +const GridColContext = createContext(null); + +const ColDivider = (props) => { + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + + const droppableStyle = { + backgroundColor: isOver ? 'green' : undefined, + }; + + return
; +}; + +const RowDivider = (props) => { + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + + const droppableStyle = {}; + + if (isOver) { + droppableStyle['backgroundColor'] = 'green'; + } + + const [active, setActive] = useState(false); + + useDndMonitor({ + onDragStart(event) { + setActive(true); + }, + onDragMove(event) {}, + onDragOver(event) {}, + onDragEnd(event) { + setActive(false); + }, + onDragCancel(event) { + setActive(false); + }, + }); + + return ( +
+ ); +}; + +const wrapRowSchema = (schema: Schema) => { + const row = new Schema({ + type: 'void', + name: `row_${uid()}`, + 'x-uid': uid(), + 'x-component': 'Grid.Row', + }); + const col = row.addProperty(uid(), { + type: 'void', + 'x-uid': uid(), + 'x-component': 'Grid.Col', + }); + if (Schema.isSchemaInstance(schema)) { + schema.parent = col; + } + col.addProperty(schema.name, schema); + return row; +}; + +const wrapColSchema = (schema: Schema) => { + const s = new Schema({ + type: 'void', + name: `col_${uid()}`, + 'x-uid': uid(), + 'x-component': 'Grid.Col', + }); + // parent 更新了,需要重新指定 + if (Schema.isSchemaInstance(schema)) { + schema.parent = s; + } + s.addProperty(schema.name, schema); + return s; +}; + +const useRowProperties = () => { + const fieldSchema = useFieldSchema(); + return fieldSchema.reduceProperties((buf, s) => { + if (s['x-component'] === 'Grid.Row' && !s['x-hidden']) { + buf.push(s); + } + return buf; + }, []); +}; + +const useColProperties = () => { + const fieldSchema = useFieldSchema(); + return fieldSchema.reduceProperties((buf, s) => { + if (s['x-component'] === 'Grid.Col' && !s['x-hidden']) { + buf.push(s); + } + return buf; + }, []); +}; + +export const Grid: any = observer((props) => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const ItemInitializer = useComponent(fieldSchema['x-item-initializer']); + const addr = field.address.toString(); + const rows = useRowProperties(); + return ( +
+ + + {rows.map((schema, index) => { + return ( + + + + + ); + })} + +
{ItemInitializer && }
+
+ ); +}); + +Grid.Row = observer((props) => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const addr = field.address.toString(); + const cols = useColProperties(); + return ( + +
+ + {cols.map((schema, index) => { + return ( + + + + + ); + })} +
+
+ ); +}); + +Grid.Col = observer((props: any) => { + const { cols } = useContext(GridRowContext); + const w = props.width || 100 / cols.length; + const width = `calc(${w}% - 24px - 24px / ${cols.length})`; + return ( +
+ {props.children} +
+ ); +}); + +Grid.AddFormItem = AddGridFormItem; +Grid.AddBlockItem = AddGridBlockItem; diff --git a/packages/client/src/schema-component/antd/grid/demos/demo2.tsx b/packages/client/src/schema-component/antd/grid/demos/demo2.tsx index 0bbb08fe9..5099cae54 100644 --- a/packages/client/src/schema-component/antd/grid/demos/demo2.tsx +++ b/packages/client/src/schema-component/antd/grid/demos/demo2.tsx @@ -1,18 +1,6 @@ -import { ISchema, observer, Schema, useFieldSchema } from '@formily/react'; +import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { - Form, - FormItem, - Grid, - Input, - Markdown, - SchemaComponent, - SchemaComponentProvider, - SchemaInitializer, - SchemaInitializerItemOptions, - useDesignable -} from '@nocobase/client'; -import { Switch } from 'antd'; +import { Form, FormItem, Grid, Input, Markdown, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; import React from 'react'; const schema: ISchema = { @@ -20,7 +8,7 @@ const schema: ISchema = { name: 'grid1', 'x-decorator': 'Form', 'x-component': 'Grid', - 'x-item-initializer': 'AddGridFormItem', + 'x-item-initializer': 'Grid.AddFormItem', 'x-uid': uid(), properties: { row1: { @@ -65,149 +53,9 @@ const schema: ISchema = { }, }; -const useFormItemInitializerFields = () => { - return [ - { - type: 'item', - title: 'Name', - component: InitializeFormItem, - schema: { - name: 'name', - type: 'string', - title: 'Name', - 'x-component': 'Input', - 'x-decorator': 'FormItem', - 'x-collection-field': 'posts.name', - }, - }, - { - type: 'item', - title: 'Title', - component: InitializeFormItem, - schema: { - name: 'title', - type: 'string', - title: 'Title', - 'x-component': 'Input', - 'x-decorator': 'FormItem', - 'x-collection-field': 'posts.title', - }, - }, - ] as SchemaInitializerItemOptions[]; -}; - -const gridRowColWrap = (schema: ISchema) => { - return { - type: 'void', - 'x-component': 'Grid.Row', - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid.Col', - properties: { - [schema.name || uid()]: schema, - }, - }, - }, - }; -}; - -const AddGridFormItem = observer((props: any) => { - return ( - - Configure fields - - ); -}); - -const useCurrentFieldSchema = (path: string) => { - const fieldSchema = useFieldSchema(); - const { remove } = useDesignable(); - const findFieldSchema = (schema: Schema) => { - return schema.reduceProperties((buf, s) => { - if (s['x-collection-field'] === path) { - return s; - } - const child = findFieldSchema(s); - if (child) { - return child; - } - return buf; - }); - }; - const schema = findFieldSchema(fieldSchema); - return { - schema, - exists: !!schema, - remove() { - schema && - remove(schema, { - removeEmptyParents: true, - }); - }, - }; -}; - -const itemWrap = SchemaInitializer.itemWrap; - -const InitializeFormItem = itemWrap((props) => { - const { item, insert } = props; - const { schema, exists, remove } = useCurrentFieldSchema(item.schema['x-collection-field']); - return ( - { - if (exists) { - return remove(); - } - insert({ - ...item.schema, - }); - }} - > -
- {item.title} -
-
- ); -}); - -const InitializeTextFormItem = itemWrap((props) => { - const { insert } = props; - return ( - { - insert({ - type: 'void', - 'x-component': 'Markdown.Void', - 'x-decorator': 'FormItem', - // 'x-editable': false, - }); - }} - /> - ); -}); - export default function App() { return ( - + ); diff --git a/packages/client/src/schema-component/antd/grid/demos/demo3.tsx b/packages/client/src/schema-component/antd/grid/demos/demo3.tsx index 053f5d660..c080fbe1a 100644 --- a/packages/client/src/schema-component/antd/grid/demos/demo3.tsx +++ b/packages/client/src/schema-component/antd/grid/demos/demo3.tsx @@ -1,155 +1,20 @@ -import { FormOutlined, TableOutlined } from '@ant-design/icons'; -import { ISchema, observer } from '@formily/react'; +import { ISchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { - CardItem, - Form, - Grid, - Markdown, - SchemaComponent, - SchemaComponentProvider, - SchemaInitializer, - VoidTable -} from '@nocobase/client'; +import { CardItem, Form, Grid, Markdown, SchemaComponent, SchemaComponentProvider, VoidTable } from '@nocobase/client'; import React from 'react'; const schema: ISchema = { type: 'void', name: 'grid1', 'x-component': 'Grid', - 'x-item-initializer': 'AddGridBlockItem', + 'x-item-initializer': 'Grid.AddBlockItem', 'x-uid': uid(), - properties: { - row1: { - type: 'void', - 'x-component': 'Grid.Row', - 'x-uid': uid(), - }, - }, + properties: {}, }; -const gridRowColWrap = (schema: ISchema) => { - return { - type: 'void', - 'x-component': 'Grid.Row', - properties: { - [uid()]: { - type: 'void', - 'x-component': 'Grid.Col', - properties: { - [schema.name || uid()]: schema, - }, - }, - }, - }; -}; - -const AddGridBlockItem = observer((props: any) => { - return ( - - Add block - - ); -}); - -const itemWrap = SchemaInitializer.itemWrap; - -const TableBlockInitializerItem = itemWrap((props) => { - const { insert } = props; - return ( - } - onClick={() => { - insert({ - type: 'void', - 'x-component': 'VoidTable', - 'x-decorator': 'CardItem', - }); - }} - items={[ - { - type: 'itemGroup', - title: 'select a data source', - children: [ - { - type: 'item', - name: 'users', - title: 'Users', - }, - { - type: 'item', - name: 'posts', - title: 'Posts', - }, - ], - }, - ]} - > - Table - - ); -}); - -const FormBlockInitializerItem = itemWrap((props) => { - const { insert } = props; - return ( - } - onClick={() => { - insert({ - type: 'void', - 'x-component': 'Form', - 'x-decorator': 'CardItem', - }); - }} - items={[ - { - type: 'itemGroup', - title: 'select a data source', - children: [ - { - type: 'item', - name: 'users', - title: 'Users', - }, - { - type: 'item', - name: 'posts', - title: 'Posts', - }, - ], - }, - ]} - > - Form - - ); -}); - export default function App() { return ( - + ); diff --git a/packages/client/src/schema-component/antd/grid/index.tsx b/packages/client/src/schema-component/antd/grid/index.tsx index 107601a52..762542e7c 100644 --- a/packages/client/src/schema-component/antd/grid/index.tsx +++ b/packages/client/src/schema-component/antd/grid/index.tsx @@ -1,176 +1 @@ -import { useDndMonitor, useDroppable } from '@dnd-kit/core'; -import { css } from '@emotion/css'; -import { observer, RecursionField, Schema, useField, useFieldSchema } from '@formily/react'; -import { uid } from '@formily/shared'; -import cls from 'classnames'; -import React, { useState } from 'react'; -import { useComponent } from '../..'; -import { DndContext } from '../../common/dnd-context'; - -const ColDivider = (props) => { - const { isOver, setNodeRef } = useDroppable({ - id: props.id, - data: props.data, - }); - - const droppableStyle = { - backgroundColor: isOver ? 'green' : undefined, - }; - - return
; -}; - -const RowDivider = (props) => { - const { isOver, setNodeRef } = useDroppable({ - id: props.id, - data: props.data, - }); - - const droppableStyle = {}; - - if (isOver) { - droppableStyle['backgroundColor'] = 'green'; - } - - const [active, setActive] = useState(false); - - useDndMonitor({ - onDragStart(event) { - setActive(true); - }, - onDragMove(event) {}, - onDragOver(event) {}, - onDragEnd(event) { - setActive(false); - }, - onDragCancel(event) { - setActive(false); - }, - }); - - return ( -
- ); -}; - -export const Grid: any = observer((props) => { - const field = useField(); - const fieldSchema = useFieldSchema(); - const ItemInitializer = useComponent(fieldSchema['x-item-initializer']); - const addr = field.address.toString(); - const wrapSchema = (schema: Schema) => { - const row = new Schema({ - type: 'void', - name: `row_${uid()}`, - 'x-uid': uid(), - 'x-component': 'Grid.Row', - }); - const col = row.addProperty(uid(), { - type: 'void', - 'x-uid': uid(), - 'x-component': 'Grid.Col', - }); - if (Schema.isSchemaInstance(schema)) { - schema.parent = col; - } - col.addProperty(schema.name, schema); - return row; - }; - return ( -
- - - {fieldSchema.mapProperties((schema, key, index) => { - return ( - - - - - ); - })} - -
{ItemInitializer && }
-
- ); -}); - -Grid.Row = observer((props) => { - const field = useField(); - const fieldSchema = useFieldSchema(); - const addr = field.address.toString(); - const wrapSchema = (schema: Schema) => { - const s = new Schema({ - type: 'void', - name: `col_${uid()}`, - 'x-uid': uid(), - 'x-component': 'Grid.Col', - }); - // parent 更新了,需要重新指定 - if (Schema.isSchemaInstance(schema)) { - schema.parent = s; - } - s.addProperty(schema.name, schema); - return s; - }; - const onInsertAdjacent = ({ dn, draggedSchema }) => { - dn.remove(draggedSchema, { - removeEmptyParents: true, - }); - }; - console.log('fieldSchema', fieldSchema.toJSON()); - return ( -
- - {fieldSchema.mapProperties((schema, key, index) => { - return ( - - - - - ); - })} -
- ); -}); - -Grid.Col = observer((props) => { - return ( -
- {props.children} -
- ); -}); +export * from './Grid'; diff --git a/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx b/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx index 5741073fd..718cc203b 100644 --- a/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx +++ b/packages/client/src/schema-component/core/RemoteSchemaComponent.tsx @@ -38,7 +38,7 @@ const RequestSchemaComponent: React.FC = (props) => if (hidden) { return ; } - return ; + return ; }; export const RemoteSchemaComponent: React.FC = (props) => { diff --git a/packages/client/src/schema-component/hooks/useDesignable.tsx b/packages/client/src/schema-component/hooks/useDesignable.tsx index ff5659070..a26ffcb9d 100644 --- a/packages/client/src/schema-component/hooks/useDesignable.tsx +++ b/packages/client/src/schema-component/hooks/useDesignable.tsx @@ -23,6 +23,11 @@ interface InsertAdjacentOptions { removeEmptyParents?: boolean; } +interface RemoveOptions { + removeEmptyParents?: boolean; + breakComponent?: string; +} + const generateUid = (s: ISchema) => { if (!s['x-uid']) { s['x-uid'] = uid(); @@ -108,8 +113,8 @@ export class Designable { } } - remove(schema?: Schema, options: { removeEmptyParents?: boolean } = {}) { - const { removeEmptyParents } = options; + remove(schema?: Schema, options: RemoveOptions = {}) { + const { removeEmptyParents, breakComponent } = options; let s = schema || this.current; let removed; while (s.parent) { @@ -117,6 +122,9 @@ export class Designable { if (!removeEmptyParents) { break; } + if (s?.parent?.['x-component'] === breakComponent) { + break; + } const count = Object.keys(s.parent.properties || {}).length; if (count > 0) { break; @@ -332,10 +340,10 @@ export function useDesignable() { update(key); refresh(); }, - remove(schema: any, options?: any) { + remove(schema: any, options?: RemoveOptions) { dn.remove(schema, options); }, - insertAdjacent(position: Position, schema: ISchema, options: InsertAdjacentOptions = {}) { + insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions) { dn.insertAdjacent(position, schema, options); }, insertBeforeBegin(schema: ISchema) {