diff --git a/packages/client/src/schema-component/antd/block-item/BlockItem.tsx b/packages/client/src/schema-component/antd/block-item/BlockItem.tsx index bf4f65a05..9675b143d 100644 --- a/packages/client/src/schema-component/antd/block-item/BlockItem.tsx +++ b/packages/client/src/schema-component/antd/block-item/BlockItem.tsx @@ -1,12 +1,13 @@ import React from 'react'; import { useDesignable } from '../..'; +import { SortableItem } from '../sortable-item'; export const BlockItem: React.FC = (props) => { const { DesignableBar } = useDesignable(); return ( -
+ {props.children} -
+ ); }; diff --git a/packages/client/src/schema-component/antd/block-item/demos/demo1.tsx b/packages/client/src/schema-component/antd/block-item/demos/demo1.tsx index f462d1525..b3e462243 100644 --- a/packages/client/src/schema-component/antd/block-item/demos/demo1.tsx +++ b/packages/client/src/schema-component/antd/block-item/demos/demo1.tsx @@ -1,55 +1,15 @@ -import React from 'react'; +import { observer, useFieldSchema } from '@formily/react'; import { uid } from '@formily/shared'; -import { observer, useField, useFieldSchema } from '@formily/react'; -import { useDraggable, useDroppable } from '@dnd-kit/core'; -import { SchemaComponent, SchemaComponentProvider, BlockItem, DndContext } from '@nocobase/client'; - -function Draggable(props) { - const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: props.id, - data: props.data, - }); - const style = transform - ? { - transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, - } - : undefined; - - return ( - - ); -} - -function Droppable(props) { - const { isOver, setNodeRef } = useDroppable({ - id: props.id, - data: props.data, - }); - const style = { - color: isOver ? 'green' : undefined, - }; - - return ( -
- {props.children} -
- ); -} +import { BlockItem, DndContext, DragHandler, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import React from 'react'; const Block = observer((props) => { const fieldSchema = useFieldSchema(); - const field = useField(); return ( - -
- Block {fieldSchema.name} - - Drag - -
-
+
+ Block {fieldSchema.name} + +
); }); diff --git a/packages/client/src/schema-component/antd/block-item/index.tsx b/packages/client/src/schema-component/antd/block-item/index.tsx index b33e1d49a..fc03b9311 100644 --- a/packages/client/src/schema-component/antd/block-item/index.tsx +++ b/packages/client/src/schema-component/antd/block-item/index.tsx @@ -1 +1,2 @@ export * from './BlockItem'; + diff --git a/packages/client/src/schema-component/antd/dnd-context/index.tsx b/packages/client/src/schema-component/antd/dnd-context/index.tsx index 972b5d551..85c0472be 100644 --- a/packages/client/src/schema-component/antd/dnd-context/index.tsx +++ b/packages/client/src/schema-component/antd/dnd-context/index.tsx @@ -10,6 +10,9 @@ const useDragEnd = () => { console.log({ active, over }); const activeSchema = active?.data?.current?.schema; const overSchema = over?.data?.current?.schema; + const insertAdjacent = over?.data?.current?.insertAdjacent; + const wrapSchema = over?.data?.current?.wrapSchema; + const onInsertAdjacent = over?.data?.current?.onInsertAdjacent; if (!activeSchema || !overSchema) { return; @@ -20,7 +23,25 @@ const useDragEnd = () => { }); dn.on('afterInsertAdjacent', refresh); - dn.insertBeforeBeginOrAfterEnd(activeSchema); + dn.on('afterRemove', refresh); + + if (activeSchema.parent === overSchema.parent) { + return dn.insertBeforeBeginOrAfterEnd(activeSchema); + } + + if (insertAdjacent) { + console.log('removeIfChildrenEmpty', activeSchema) + dn.insertAdjacent(insertAdjacent, activeSchema, { + wrap: wrapSchema, + removeEmptyParents: true, + }); + // onInsertAdjacent && onInsertAdjacent({ + // dn, + // orginDraggedParentSchema, + // draggedSchema: activeSchema, + // }); + return; + } }; }; diff --git a/packages/client/src/schema-component/antd/form/Form.tsx b/packages/client/src/schema-component/antd/form/Form.tsx index 8b7ba1987..09959f0d0 100644 --- a/packages/client/src/schema-component/antd/form/Form.tsx +++ b/packages/client/src/schema-component/antd/form/Form.tsx @@ -1,3 +1,4 @@ +import { FormLayout } from '@formily/antd'; import { createForm, FormPath } from '@formily/core'; import { FieldContext, @@ -72,7 +73,7 @@ const useDefaultValues = (props: any = {}, opts: any = {}) => { }; export const Form: ComposedForm = observer((props) => { - const { useValues = useDefaultValues } = props; + const { request, initialValue, useValues = useDefaultValues, ...others } = props; const decorator = useFormDecorator(); const fieldSchema = useFieldSchema(); const form = useMemo(() => createForm(), []); @@ -83,7 +84,13 @@ export const Form: ComposedForm = observer((props) => { form.setValues(data?.data); }, }); - return {decorator ? : }; + return ( + + + {decorator ? : } + + + ); }); Form.__NOCOBASE_FORM = true; diff --git a/packages/client/src/schema-component/antd/grid/demos/demo1.tsx b/packages/client/src/schema-component/antd/grid/demos/demo1.tsx index 802399ca0..774310d55 100644 --- a/packages/client/src/schema-component/antd/grid/demos/demo1.tsx +++ b/packages/client/src/schema-component/antd/grid/demos/demo1.tsx @@ -1,115 +1,90 @@ -import React from 'react'; -import { uid } from '@formily/shared'; import { observer, useFieldSchema } from '@formily/react'; -import { useDraggable, useDroppable } from '@dnd-kit/core'; -import { SchemaComponent, SchemaComponentProvider, Grid } from '@nocobase/client'; - -function Draggable(props) { - const { attributes, listeners, setNodeRef, transform } = useDraggable({ - id: props.id, - data: props.data, - }); - const style = transform - ? { - transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, - } - : undefined; - - return ( - - ); -} - -function Droppable(props) { - const { isOver, setNodeRef } = useDroppable({ - id: props.id, - data: props.data, - }); - const style = { - color: isOver ? 'green' : undefined, - }; - - return ( -
- {props.children} -
- ); -} +import { uid } from '@formily/shared'; +import { BlockItem, DragHandler, Grid, SchemaComponent, SchemaComponentProvider } from '@nocobase/client'; +import React from 'react'; const Block = observer((props) => { const fieldSchema = useFieldSchema(); return ( - -
- Block {fieldSchema.name} - - Drag - -
-
+
+ Block {fieldSchema.title} + +
); }); export default function App() { return ( - + { @@ -10,10 +14,72 @@ export const Grid: any = observer((props) => { ); }); +const ColDivider = (props) => { + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + + const droppableStyle = { + backgroundColor: isOver ? 'green' : undefined, + }; + + return
; +}; + Grid.Row = observer((props) => { - return
{props.children}
; + 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}
; + return
{props.children}
; }); diff --git a/packages/client/src/schema-component/antd/index.ts b/packages/client/src/schema-component/antd/index.ts index 3726f9706..0c2b8e2a7 100644 --- a/packages/client/src/schema-component/antd/index.ts +++ b/packages/client/src/schema-component/antd/index.ts @@ -25,8 +25,10 @@ export * from './radio'; export * from './record-picker'; export * from './row-selection'; export * from './select'; +export * from './sortable-item'; export * from './tabs'; export * from './time-picker'; export * from './tree-select'; export * from './upload'; export * from './void-table'; + diff --git a/packages/client/src/schema-component/antd/sortable-item/index.md b/packages/client/src/schema-component/antd/sortable-item/index.md new file mode 100644 index 000000000..fd351eb65 --- /dev/null +++ b/packages/client/src/schema-component/antd/sortable-item/index.md @@ -0,0 +1,9 @@ +--- +nav: + path: /client +group: + path: /schema-components +--- + +# SortableItem + diff --git a/packages/client/src/schema-component/antd/sortable-item/index.tsx b/packages/client/src/schema-component/antd/sortable-item/index.tsx new file mode 100644 index 000000000..8041424c6 --- /dev/null +++ b/packages/client/src/schema-component/antd/sortable-item/index.tsx @@ -0,0 +1,61 @@ +import { useDraggable, useDroppable } from '@dnd-kit/core'; +import { observer, useField, useFieldSchema } from '@formily/react'; +import React, { createContext, useContext } from 'react'; + +export const DraggableContext = createContext(null); + +export const Sortable = (props: any) => { + const draggable = useDraggable({ + id: props.id, + data: props.data, + }); + + const { isOver, setNodeRef } = useDroppable({ + id: props.id, + data: props.data, + }); + + const droppableStyle = { + color: isOver ? 'green' : undefined, + }; + + return ( + +
+ {props.children} +
+
+ ); +}; + +export const SortableItem = observer((props) => { + const field = useField(); + const fieldSchema = useFieldSchema(); + const onInsertAdjacent = ({ dn, orginDraggedParentSchema }) => { + dn.removeIfChildrenEmpty(orginDraggedParentSchema, { + removeEmptyParents: true, + }); + }; + return ( + + {props.children} + + ); +}); + +export const DragHandler = () => { + const { attributes, listeners, setNodeRef, transform } = useContext(DraggableContext); + const style = transform + ? { + transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`, + } + : undefined; + return ( +
+ Drag +
+ ); +}; diff --git a/packages/client/src/schema-component/hooks/useDesignable.tsx b/packages/client/src/schema-component/hooks/useDesignable.tsx index 0e78df8ad..ef0d38dbe 100644 --- a/packages/client/src/schema-component/hooks/useDesignable.tsx +++ b/packages/client/src/schema-component/hooks/useDesignable.tsx @@ -1,8 +1,8 @@ -import React, { useContext } from 'react'; import { ISchema, Schema, useField, useFieldSchema } from '@formily/react'; -import { SchemaComponentContext } from '../context'; -import set from 'lodash/set'; import { uid } from '@formily/shared'; +import set from 'lodash/set'; +import React, { useContext } from 'react'; +import { SchemaComponentContext } from '../context'; interface CreateDesignableProps { current: Schema; @@ -17,6 +17,11 @@ export function createDesignable(options: CreateDesignableProps) { */ type Position = 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd'; +interface InsertAdjacentOptions { + wrap?: (s: ISchema) => ISchema; + removeEmptyParents?: boolean; +} + const generateUid = (s: ISchema) => { if (!s['x-uid']) { s['x-uid'] = uid(); @@ -26,6 +31,8 @@ const generateUid = (s: ISchema) => { }); }; +const defaultWrap = (s: ISchema) => s; + export class Designable { current: Schema; @@ -66,25 +73,63 @@ export class Designable { this.events[name].forEach((fn) => fn.bind(this)(...args)); } - insertAdjacent(position: Position, schema: ISchema) { + insertAdjacent(position: Position, schema: ISchema, options: InsertAdjacentOptions = {}) { switch (position) { case 'beforeBegin': - return this.insertBeforeBegin(schema); + return this.insertBeforeBegin(schema, options); case 'afterBegin': - return this.insertAfterBegin(schema); + return this.insertAfterBegin(schema, options); case 'beforeEnd': - return this.insertBeforeEnd(schema); + return this.insertBeforeEnd(schema, options); case 'afterEnd': - return this.insertAfterEnd(schema); + return this.insertAfterEnd(schema, options); } } - remove() { - const s = this.current.parent.removeProperty(this.current.name); - this.emit('afterRemove', s); + removeIfChildrenEmpty(schema?: Schema, options: { recursive?: boolean } = {}) { + if (!schema) { + return; + } + const { recursive } = options; + let s = schema; + const count = Object.keys(s.properties || {}).length; + console.log('removeIfChildrenEmpty', count, s.properties); + if (count > 0) { + return; + } + let removed; + while (s.parent) { + removed = s.parent.removeProperty(s.name); + if (!recursive) { + break; + } + const count = Object.keys(s.parent.properties || {}).length; + if (count > 0) { + break; + } + s = s.parent; + } } - insertBeforeBeginOrAfterEnd(schema: ISchema) { + remove(schema?: Schema, options: { removeEmptyParents?: boolean } = {}) { + const { removeEmptyParents } = options; + let s = schema || this.current; + let removed; + while (s.parent) { + removed = s.parent.removeProperty(s.name); + if (!removeEmptyParents) { + break; + } + const count = Object.keys(s.parent.properties || {}).length; + if (count > 0) { + break; + } + s = s.parent; + } + this.emit('afterRemove', removed); + } + + insertBeforeBeginOrAfterEnd(schema: ISchema, options: InsertAdjacentOptions = {}) { if (!Schema.isSchemaInstance(this.current)) { return; } @@ -104,22 +149,25 @@ export class Designable { fromIndex = index; } }); - return fromIndex > toIndex ? this.insertBeforeBegin(schema) : this.insertAfterEnd(schema); + return fromIndex > toIndex ? this.insertBeforeBegin(schema, options) : this.insertAfterEnd(schema, options); } /** * Before the current schema itself. */ - insertBeforeBegin(schema: ISchema) { + insertBeforeBegin(schema: ISchema, options: InsertAdjacentOptions = {}) { if (!Schema.isSchemaInstance(this.current)) { return; } + const { wrap = defaultWrap, removeEmptyParents } = options; const properties = {}; let start = false; if (Schema.isSchemaInstance(schema)) { schema.parent.removeProperty(schema.name); - schema.parent = this.current.parent; + if (removeEmptyParents) { + this.removeIfChildrenEmpty(schema.parent); + } } this.current.parent.mapProperties((property, key) => { @@ -133,7 +181,9 @@ export class Designable { }); this.prepareProperty(schema); - const s = this.current.parent.addProperty(schema.name, schema); + const wrapped = wrap(schema); + const s = this.current.parent.addProperty(wrapped.name, wrapped); + s.parent = this.current.parent; this.current.parent.setProperties(properties); this.emit('afterInsertAdjacent', 'beforeBegin', s); } @@ -144,13 +194,16 @@ export class Designable { * @param schema * @returns */ - insertAfterBegin(schema: ISchema) { + insertAfterBegin(schema: ISchema, options: InsertAdjacentOptions = {}) { if (!Schema.isSchemaInstance(this.current)) { return; } + const { wrap = defaultWrap, removeEmptyParents } = options; if (Schema.isSchemaInstance(schema)) { schema.parent.removeProperty(schema.name); - schema.parent = this.current; + if (removeEmptyParents) { + this.removeIfChildrenEmpty(schema.parent); + } } const properties = {}; this.current.mapProperties((schema, key) => { @@ -158,7 +211,9 @@ export class Designable { }); this.current.properties = {}; this.prepareProperty(schema); - const s = this.current.addProperty(schema.name, schema); + const wrapped = wrap(schema); + const s = this.current.addProperty(wrapped.name, wrapped); + s.parent = this.current; this.current.setProperties(properties); this.emit('afterInsertAdjacent', 'afterBegin', s); } @@ -169,32 +224,40 @@ export class Designable { * @param schema * @returns */ - insertBeforeEnd(schema: ISchema) { + insertBeforeEnd(schema: ISchema, options: InsertAdjacentOptions = {}) { if (!Schema.isSchemaInstance(this.current)) { return; } + const { wrap = defaultWrap, removeEmptyParents } = options; if (Schema.isSchemaInstance(schema)) { schema.parent.removeProperty(schema.name); - schema.parent = this.current; + if (removeEmptyParents) { + this.removeIfChildrenEmpty(schema.parent); + } } this.prepareProperty(schema); - const s = this.current.addProperty(schema.name || uid(), schema); + const wrapped = wrap(schema); + const s = this.current.addProperty(wrapped.name || uid(), wrapped); + s.parent = this.current; this.emit('afterInsertAdjacent', 'beforeEnd', s); } /** * After the current schema itself. */ - insertAfterEnd(schema: ISchema) { + insertAfterEnd(schema: ISchema, options: InsertAdjacentOptions = {}) { if (!Schema.isSchemaInstance(this.current)) { return; } const properties = {}; let start = false; - + const { wrap = defaultWrap, removeEmptyParents } = options; if (Schema.isSchemaInstance(schema)) { schema.parent.removeProperty(schema.name); - schema.parent = this.current.parent; + if (removeEmptyParents) { + this.removeIfChildrenEmpty(schema.parent); + } + schema.parent = null; } this.current.parent.mapProperties((property, key) => { @@ -208,7 +271,11 @@ export class Designable { }); this.prepareProperty(schema); - const s = this.current.parent.addProperty(schema.name || uid(), schema); + + const wrapped = wrap(schema); + + const s = this.current.parent.addProperty(wrapped.name || uid(), wrapped); + s.parent = this.current.parent; this.current.parent.setProperties(properties); this.emit('afterInsertAdjacent', 'afterEnd', s); }