feat: improve code
This commit is contained in:
parent
43393b4d44
commit
ab457ade22
packages/client/src
application/demos/demo2
schema-component
antd
core
hooks
@ -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];
|
||||
|
@ -5,7 +5,7 @@ import { BlockItem } from '../block-item';
|
||||
export const CardItem: React.FC = (props) => {
|
||||
return (
|
||||
<BlockItem className={'noco-card-item'}>
|
||||
<Card bordered={false} {...props}>
|
||||
<Card style={{ marginBottom: 24 }} bordered={false} {...props}>
|
||||
{props.children}
|
||||
</Card>
|
||||
</BlockItem>
|
||||
|
133
packages/client/src/schema-component/antd/grid/AddBlockItem.tsx
Normal file
133
packages/client/src/schema-component/antd/grid/AddBlockItem.tsx
Normal file
@ -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 (
|
||||
<SchemaInitializer.Item
|
||||
icon={<TableOutlined />}
|
||||
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
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
const FormBlockInitializerItem = itemWrap((props) => {
|
||||
const { insert } = props;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
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
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
export const AddGridBlockItem = observer((props: any) => {
|
||||
return (
|
||||
<SchemaInitializer.Button
|
||||
wrap={gridRowColWrap}
|
||||
insertPosition={'beforeEnd'}
|
||||
items={[
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: 'Data blocks',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Table',
|
||||
component: TableBlockInitializerItem,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Form',
|
||||
component: FormBlockInitializerItem,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
>
|
||||
Add block
|
||||
</SchemaInitializer.Button>
|
||||
);
|
||||
});
|
147
packages/client/src/schema-component/antd/grid/AddFormItem.tsx
Normal file
147
packages/client/src/schema-component/antd/grid/AddFormItem.tsx
Normal file
@ -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 (
|
||||
<SchemaInitializer.Item
|
||||
onClick={() => {
|
||||
if (exists) {
|
||||
return remove();
|
||||
}
|
||||
insert({
|
||||
...item.schema,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
{item.title} <Switch size={'small'} checked={exists} />
|
||||
</div>
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
const InitializeTextFormItem = itemWrap((props) => {
|
||||
const { insert } = props;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
onClick={() => {
|
||||
insert({
|
||||
type: 'void',
|
||||
'x-component': 'Markdown.Void',
|
||||
'x-decorator': 'FormItem',
|
||||
// 'x-editable': false,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const AddGridFormItem = observer((props: any) => {
|
||||
return (
|
||||
<SchemaInitializer.Button
|
||||
wrap={gridRowColWrap}
|
||||
insertPosition={'beforeEnd'}
|
||||
items={[
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: 'Display fields',
|
||||
children: useFormItemInitializerFields(),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Add text',
|
||||
component: InitializeTextFormItem,
|
||||
},
|
||||
]}
|
||||
>
|
||||
Configure fields
|
||||
</SchemaInitializer.Button>
|
||||
);
|
||||
});
|
214
packages/client/src/schema-component/antd/grid/Grid.tsx
Normal file
214
packages/client/src/schema-component/antd/grid/Grid.tsx
Normal file
@ -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 <div ref={setNodeRef} style={{ width: 24, ...droppableStyle }}></div>;
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={{
|
||||
zIndex: active ? 1000 : 0,
|
||||
height: 24,
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
marginTop: -24,
|
||||
...droppableStyle,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className={'nb-grid'} style={{ position: 'relative' }}>
|
||||
<DndContext>
|
||||
<RowDivider
|
||||
id={`${addr}_0`}
|
||||
data={{ wrapSchema: wrapRowSchema, insertAdjacent: 'afterBegin', schema: fieldSchema }}
|
||||
/>
|
||||
{rows.map((schema, index) => {
|
||||
return (
|
||||
<React.Fragment key={schema.name}>
|
||||
<RecursionField name={schema.name} schema={schema} />
|
||||
<RowDivider
|
||||
id={`${addr}_${index + 1}`}
|
||||
data={{ wrapSchema: wrapRowSchema, insertAdjacent: 'afterEnd', schema }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</DndContext>
|
||||
<div>{ItemInitializer && <ItemInitializer />}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Grid.Row = observer((props) => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const addr = field.address.toString();
|
||||
const cols = useColProperties();
|
||||
return (
|
||||
<GridRowContext.Provider value={{ cols }}>
|
||||
<div
|
||||
className={cls(
|
||||
'nb-grid-row',
|
||||
css`
|
||||
margin: 0 -24px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<ColDivider
|
||||
id={`${addr}_0`}
|
||||
data={{ wrapSchema: wrapColSchema, insertAdjacent: 'afterBegin', schema: fieldSchema }}
|
||||
/>
|
||||
{cols.map((schema, index) => {
|
||||
return (
|
||||
<React.Fragment key={schema.name}>
|
||||
<RecursionField name={schema.name} schema={schema} />
|
||||
<ColDivider
|
||||
id={`${addr}_${index + 1}`}
|
||||
data={{ wrapSchema: wrapColSchema, insertAdjacent: 'afterEnd', schema }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</GridRowContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<div
|
||||
style={{ width }}
|
||||
className={cls(
|
||||
'nb-grid-col',
|
||||
css`
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Grid.AddFormItem = AddGridFormItem;
|
||||
Grid.AddBlockItem = AddGridBlockItem;
|
@ -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 (
|
||||
<SchemaInitializer.Button
|
||||
wrap={gridRowColWrap}
|
||||
insertPosition={'beforeEnd'}
|
||||
items={[
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: 'Display fields',
|
||||
children: useFormItemInitializerFields(),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Add text',
|
||||
component: InitializeTextFormItem,
|
||||
},
|
||||
]}
|
||||
>
|
||||
Configure fields
|
||||
</SchemaInitializer.Button>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<SchemaInitializer.Item
|
||||
onClick={() => {
|
||||
if (exists) {
|
||||
return remove();
|
||||
}
|
||||
insert({
|
||||
...item.schema,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
{item.title} <Switch size={'small'} checked={exists} />
|
||||
</div>
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
const InitializeTextFormItem = itemWrap((props) => {
|
||||
const { insert } = props;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
onClick={() => {
|
||||
insert({
|
||||
type: 'void',
|
||||
'x-component': 'Markdown.Void',
|
||||
'x-decorator': 'FormItem',
|
||||
// 'x-editable': false,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ AddGridFormItem, Markdown, Form, Grid, Input, FormItem }}>
|
||||
<SchemaComponentProvider components={{ Markdown, Form, Grid, Input, FormItem }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
|
@ -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 (
|
||||
<SchemaInitializer.Button
|
||||
wrap={gridRowColWrap}
|
||||
insertPosition={'beforeEnd'}
|
||||
items={[
|
||||
{
|
||||
type: 'itemGroup',
|
||||
title: 'Data blocks',
|
||||
children: [
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Table',
|
||||
component: TableBlockInitializerItem,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Form',
|
||||
component: FormBlockInitializerItem,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
>
|
||||
Add block
|
||||
</SchemaInitializer.Button>
|
||||
);
|
||||
});
|
||||
|
||||
const itemWrap = SchemaInitializer.itemWrap;
|
||||
|
||||
const TableBlockInitializerItem = itemWrap((props) => {
|
||||
const { insert } = props;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<TableOutlined />}
|
||||
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
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
const FormBlockInitializerItem = itemWrap((props) => {
|
||||
const { insert } = props;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
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
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ AddGridBlockItem, Grid, CardItem, Markdown, Form, VoidTable }}>
|
||||
<SchemaComponentProvider components={{ Grid, CardItem, Markdown, Form, VoidTable }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
|
@ -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 <div ref={setNodeRef} style={{ width: 24, ...droppableStyle }}></div>;
|
||||
};
|
||||
|
||||
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 (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={{
|
||||
zIndex: active ? 1000 : 0,
|
||||
height: 24,
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
marginTop: -24,
|
||||
...droppableStyle,
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className={'nb-grid'} style={{ position: 'relative' }}>
|
||||
<DndContext>
|
||||
<RowDivider id={`${addr}_0`} data={{ wrapSchema, insertAdjacent: 'afterBegin', schema: fieldSchema }} />
|
||||
{fieldSchema.mapProperties((schema, key, index) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
<RecursionField name={key} schema={schema} />
|
||||
<RowDivider id={`${addr}_${index + 1}`} data={{ wrapSchema, insertAdjacent: 'afterEnd', schema }} />
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</DndContext>
|
||||
<div>{ItemInitializer && <ItemInitializer />}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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 (
|
||||
<div
|
||||
className={cls(
|
||||
'nb-grid-row',
|
||||
css`
|
||||
margin: 0 -24px;
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
<ColDivider
|
||||
id={`${addr}_0`}
|
||||
data={{ wrapSchema, insertAdjacent: 'afterBegin', schema: fieldSchema, onInsertAdjacent }}
|
||||
/>
|
||||
{fieldSchema.mapProperties((schema, key, index) => {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
<RecursionField name={key} schema={schema} />
|
||||
<ColDivider
|
||||
id={`${addr}_${index + 1}`}
|
||||
data={{ wrapSchema, insertAdjacent: 'afterEnd', schema, onInsertAdjacent }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Grid.Col = observer((props) => {
|
||||
return (
|
||||
<div
|
||||
className={cls(
|
||||
'nb-grid-col',
|
||||
css`
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
`,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export * from './Grid';
|
||||
|
@ -38,7 +38,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
|
||||
if (hidden) {
|
||||
return <Spin />;
|
||||
}
|
||||
return <SchemaComponent scope={scope} schema={schemaTransform(data?.data || {})} />;
|
||||
return <SchemaComponent memoized scope={scope} schema={schemaTransform(data?.data || {})} />;
|
||||
};
|
||||
|
||||
export const RemoteSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) => {
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user