2022-12-21 09:55:59 +08:00
# Schema design capabilities
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
The design capabilities of Schema are mainly
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
- Neighborhood insertion, which can be used to
- Insertion of new schema nodes
- Drag-and-drop movement of existing schema nodes
- schema parameter modification
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
The core designer APIs and parameters are
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
- Designer API: `createDesignable()` & `useDesignable()`
- Schema parameters: `x-designer` , used to adapt the designer component
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
## Designer API
2022-10-31 11:52:17 +08:00
### createDesignable()
```ts
import { Schema } from '@nocobase/client';
const current = new Schema({
type: 'void',
'x-component': 'div',
});
const {
2022-12-21 09:55:59 +08:00
designable, // whether it is configurable
2022-10-31 11:52:17 +08:00
remove,
2022-12-21 09:55:59 +08:00
insertAdjacent, // insert at a position, four positions: beforeBegin, afterBegin, beforeEnd, afterEnd
insertBeforeBegin, // insert in front of the current node
insertAfterBegin, // insert in front of the first child node of the current node
insertBeforeEnd, // after the last child of the current node
insertAfterEnd, // after the current node
2022-10-31 11:52:17 +08:00
} = createDesignable({
current,
});
const newSchema = {
type: 'void',
name: 'hello',
'x-component': 'Hello',
};
insertAfterBegin(newSchema);
console.log(current.toJSON());
{
type: 'void',
'x-component': 'div',
properties: {
hello: {
type: 'void',
'x-component': 'Hello',
},
},
}
```
### useDesignable()
2022-12-21 09:55:59 +08:00
React Hook scenarios can also use `useDesignable()` to get the API of the current schema component designer
2022-10-31 11:52:17 +08:00
```ts
const {
2022-12-21 09:55:59 +08:00
designable, // whether it is configurable
2022-10-31 11:52:17 +08:00
remove,
2022-12-21 09:55:59 +08:00
insertAdjacent, // insert at a position, four positions: beforeBegin, afterBegin, beforeEnd, afterEnd
insertBeforeBegin, // insert in front of the current node
insertAfterBegin, // insert in front of the first child node of the current node
insertBeforeEnd, // after the last child of the current node
insertAfterEnd, // after the current node
2022-10-31 11:52:17 +08:00
} = useDesignable();
const schema = {
name: uid(),
'x-component': 'Hello',
};
2022-12-21 09:55:59 +08:00
// Insert in front of the current node
2022-10-31 11:52:17 +08:00
insertBeforeBegin(schema);
2022-12-21 09:55:59 +08:00
// Equivalent to
2022-10-31 11:52:17 +08:00
insertAdjacent('beforeBegin', schema);
2022-12-21 09:55:59 +08:00
// insert in front of the first child of the current node
2022-10-31 11:52:17 +08:00
insertAfterBegin(schema);
2022-12-21 09:55:59 +08:00
// Equivalent to
2022-10-31 11:52:17 +08:00
insertAdjacent('afterBegin', schema);
2022-12-21 09:55:59 +08:00
// after the last child of the current node
2022-10-31 11:52:17 +08:00
insertBeforeEnd(schema);
2022-12-21 09:55:59 +08:00
// Equivalent to
2022-10-31 11:52:17 +08:00
insertAdjacent('beforeEnd', schema);
2022-12-21 09:55:59 +08:00
// After the current node
2022-10-31 11:52:17 +08:00
insertAfterEnd(schema);
2022-12-21 09:55:59 +08:00
// Equivalent to
2022-10-31 11:52:17 +08:00
insertAdjacent('afterEnd', schema);
```
2022-12-21 09:55:59 +08:00
## Neighborhood insertion
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
Similar to the DOM's [insert adjacent ](https://dom.spec.whatwg.org/#insert-adjacent ) concept, Schema also provides the `insertAdjacent()` method for solving the insertion of adjacent positions.
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
The four adjacent positions
2022-10-31 11:52:17 +08:00
```ts
{
properties: {
2022-12-21 09:55:59 +08:00
// beforeBegin insert before the current node
2022-10-31 11:52:17 +08:00
node1: {
properties: {
2022-12-21 09:55:59 +08:00
// afterBegin inserted before the first child of the current node
2022-10-31 11:52:17 +08:00
// ...
2022-12-21 09:55:59 +08:00
// beforeEnd after the last child of the current node
2022-10-31 11:52:17 +08:00
},
},
2022-12-21 09:55:59 +08:00
// afterEnd after the current node
2022-10-31 11:52:17 +08:00
},
}
```
2022-12-21 09:55:59 +08:00
Like HTML tags, the components of the Schema component library can be combined with each other and inserted in reasonable proximity as needed via the insertAdjacent API.
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
### Inserting a new schema node
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
Within a Schema component, a new node can be inserted directly into the adjacent position of the current Schema with `useDesignable()` .
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
Example
2022-10-31 11:52:17 +08:00
```tsx
import React from 'react';
import { SchemaComponentProvider, SchemaComponent, useDesignable } from '@nocobase/client';
import { observer, Schema, useFieldSchema } from '@formily/react';
import { Button, Space } from 'antd';
import { uid } from '@formily/shared';
const Hello = observer((props) => {
const { insertAdjacent } = useDesignable();
const fieldSchema = useFieldSchema();
return (
< div >
< h1 > {fieldSchema.name}< / h1 >
< Space >
< Button
onClick={() => {
insertAdjacent('beforeBegin', {
'x-component': 'Hello',
});
}}
>
before begin
< / Button >
< Button
onClick={() => {
insertAdjacent('afterBegin', {
'x-component': 'Hello',
});
}}
>
after begin
< / Button >
< Button
onClick={() => {
insertAdjacent('beforeEnd', {
'x-component': 'Hello',
});
}}
>
before end
< / Button >
< Button
onClick={() => {
insertAdjacent('afterEnd', {
'x-component': 'Hello',
});
}}
>
after end
< / Button >
< / Space >
< div style = {{ margin: 50 } } > {props.children}< / div >
< / div >
);
2023-06-06 19:33:04 +08:00
}, { displayName: 'Hello' });
2022-10-31 11:52:17 +08:00
const Page = observer((props) => {
return < div > {props.children}< / div > ;
2023-06-06 19:33:04 +08:00
}, { displayName: 'Page' });
2022-10-31 11:52:17 +08:00
export default () => {
return (
< SchemaComponentProvider components = {{ Page , Hello } } >
< SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
properties: {
hello1: {
type: 'void',
'x-component': 'Hello',
},
},
}}
/>
< / SchemaComponentProvider >
);
}
```
2022-12-21 09:55:59 +08:00
### Drag-and-drop movement of existing schema nodes
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
Methods such as insertAdjacent can also be used to drag and drop nodes
2022-10-31 11:52:17 +08:00
```tsx
import React from 'react';
import { uid } from '@formily/shared';
import { observer, useField, useFieldSchema } from '@formily/react';
import { DndContext, DragEndEvent, useDraggable, useDroppable } from '@dnd-kit/core';
import { SchemaComponent, SchemaComponentProvider, createDesignable, useDesignable } from '@nocobase/client';
const useDragEnd = () => {
const { refresh } = useDesignable();
return ({ active, over }: DragEndEvent) => {
const activeSchema = active?.data?.current?.schema;
const overSchema = over?.data?.current?.schema;
if (!activeSchema || !overSchema) {
return;
}
const dn = createDesignable({
current: overSchema,
});
dn.on('insertAdjacent', refresh);
dn.insertBeforeBeginOrAfterEnd(activeSchema);
};
};
const Page = observer((props) => {
return < DndContext onDragEnd = {useDragEnd()} > {props.children}< / DndContext > ;
2023-06-06 19:33:04 +08:00
}, { displayName: 'Page' });
2022-10-31 11:52:17 +08:00
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 (
< button ref = {setNodeRef} style = {style} { . . . listeners } { . . . attributes } >
{props.children}
< / button >
);
}
function Droppable(props) {
const { isOver, setNodeRef } = useDroppable({
id: props.id,
data: props.data,
});
const style = {
color: isOver ? 'green' : undefined,
};
return (
< div ref = {setNodeRef} style = {style} >
{props.children}
< / div >
);
}
const Block = observer((props) => {
const field = useField();
const fieldSchema = useFieldSchema();
return (
< Droppable id = {field.address.toString()} data = {{ schema: fieldSchema } } >
< div style = {{ marginBottom: 20 , padding: ' 20px ' , background: ' # f1f1f1 ' } } >
Block {fieldSchema.name}{' '}
< Draggable id = {field.address.toString()} data = {{ schema: fieldSchema } } >
Drag
< / Draggable >
< / div >
< / Droppable >
);
2023-06-06 19:33:04 +08:00
}, { displayName: 'Block' });
2022-10-31 11:52:17 +08:00
export default function App() {
return (
< SchemaComponentProvider components = {{ Page , Block } } >
< SchemaComponent
schema={{
type: 'void',
name: 'page',
'x-component': 'Page',
properties: {
block1: {
'x-component': 'Block',
},
block2: {
'x-component': 'Block',
},
block3: {
'x-component': 'Block',
},
},
}}
/>
< / SchemaComponentProvider >
);
}
```
2023-06-06 19:33:04 +08:00
## Applications of `x-designer`
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
`x-designer` is usually used only in wrapper components such as BlockItem, CardItem, FormItem, etc.
2022-10-31 11:52:17 +08:00
```ts
{
type: 'object',
properties: {
title: {
type: 'string',
title: '标题',
'x-decorator': 'FormItem',
'x-component': 'Input',
'x-designer': 'FormItem.Designer',
},
status: {
type: 'string',
title: '状态',
'x-decorator': 'FormItem',
'x-component': 'Select',
'x-designer': 'FormItem.Designer',
},
},
}
```
2022-12-21 09:55:59 +08:00
Note: The Schema designer provided by NocoBase is directly embedded in the interface in the form of a toolbar. When the UI configuration is activated (`designable = true`), the `x-designer` component (designer toolbar) will be displayed and the current schema component can be updated through the toolbar.
2022-10-31 11:52:17 +08:00
2022-12-21 09:55:59 +08:00
- Drag and Drop: DndContext + DragHandler
- Inserting new nodes: SchemaInitializer
- Parameter configuration: SchemaSettings