feat: improve code
This commit is contained in:
parent
43393b4d44
commit
ab457ade22
@ -1,3 +1,4 @@
|
|||||||
|
import { uid } from '@formily/shared';
|
||||||
import { APIClient } from '@nocobase/client';
|
import { APIClient } from '@nocobase/client';
|
||||||
import MockAdapter from 'axios-mock-adapter';
|
import MockAdapter from 'axios-mock-adapter';
|
||||||
|
|
||||||
@ -119,6 +120,16 @@ export default (apiClient: APIClient) => {
|
|||||||
name: name,
|
name: name,
|
||||||
'x-uid': name,
|
'x-uid': name,
|
||||||
'x-component': 'Page',
|
'x-component': 'Page',
|
||||||
|
properties: {
|
||||||
|
[uid()]: {
|
||||||
|
type: 'void',
|
||||||
|
name: 'grid1',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-item-initializer': 'Grid.AddBlockItem',
|
||||||
|
'x-uid': uid(),
|
||||||
|
properties: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return [200, response];
|
return [200, response];
|
||||||
|
@ -5,7 +5,7 @@ import { BlockItem } from '../block-item';
|
|||||||
export const CardItem: React.FC = (props) => {
|
export const CardItem: React.FC = (props) => {
|
||||||
return (
|
return (
|
||||||
<BlockItem className={'noco-card-item'}>
|
<BlockItem className={'noco-card-item'}>
|
||||||
<Card bordered={false} {...props}>
|
<Card style={{ marginBottom: 24 }} bordered={false} {...props}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Card>
|
</Card>
|
||||||
</BlockItem>
|
</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 { uid } from '@formily/shared';
|
||||||
import {
|
import { Form, FormItem, Grid, Input, Markdown, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
Grid,
|
|
||||||
Input,
|
|
||||||
Markdown,
|
|
||||||
SchemaComponent,
|
|
||||||
SchemaComponentProvider,
|
|
||||||
SchemaInitializer,
|
|
||||||
SchemaInitializerItemOptions,
|
|
||||||
useDesignable
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import { Switch } from 'antd';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
@ -20,7 +8,7 @@ const schema: ISchema = {
|
|||||||
name: 'grid1',
|
name: 'grid1',
|
||||||
'x-decorator': 'Form',
|
'x-decorator': 'Form',
|
||||||
'x-component': 'Grid',
|
'x-component': 'Grid',
|
||||||
'x-item-initializer': 'AddGridFormItem',
|
'x-item-initializer': 'Grid.AddFormItem',
|
||||||
'x-uid': uid(),
|
'x-uid': uid(),
|
||||||
properties: {
|
properties: {
|
||||||
row1: {
|
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() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<SchemaComponentProvider components={{ AddGridFormItem, Markdown, Form, Grid, Input, FormItem }}>
|
<SchemaComponentProvider components={{ Markdown, Form, Grid, Input, FormItem }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
);
|
);
|
||||||
|
@ -1,155 +1,20 @@
|
|||||||
import { FormOutlined, TableOutlined } from '@ant-design/icons';
|
import { ISchema } from '@formily/react';
|
||||||
import { ISchema, observer } from '@formily/react';
|
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import {
|
import { CardItem, Form, Grid, Markdown, SchemaComponent, SchemaComponentProvider, VoidTable } from '@nocobase/client';
|
||||||
CardItem,
|
|
||||||
Form,
|
|
||||||
Grid,
|
|
||||||
Markdown,
|
|
||||||
SchemaComponent,
|
|
||||||
SchemaComponentProvider,
|
|
||||||
SchemaInitializer,
|
|
||||||
VoidTable
|
|
||||||
} from '@nocobase/client';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const schema: ISchema = {
|
const schema: ISchema = {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
name: 'grid1',
|
name: 'grid1',
|
||||||
'x-component': 'Grid',
|
'x-component': 'Grid',
|
||||||
'x-item-initializer': 'AddGridBlockItem',
|
'x-item-initializer': 'Grid.AddBlockItem',
|
||||||
'x-uid': uid(),
|
'x-uid': uid(),
|
||||||
properties: {
|
properties: {},
|
||||||
row1: {
|
|
||||||
type: 'void',
|
|
||||||
'x-component': 'Grid.Row',
|
|
||||||
'x-uid': uid(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<SchemaComponentProvider components={{ AddGridBlockItem, Grid, CardItem, Markdown, Form, VoidTable }}>
|
<SchemaComponentProvider components={{ Grid, CardItem, Markdown, Form, VoidTable }}>
|
||||||
<SchemaComponent schema={schema} />
|
<SchemaComponent schema={schema} />
|
||||||
</SchemaComponentProvider>
|
</SchemaComponentProvider>
|
||||||
);
|
);
|
||||||
|
@ -1,176 +1 @@
|
|||||||
import { useDndMonitor, useDroppable } from '@dnd-kit/core';
|
export * from './Grid';
|
||||||
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>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
@ -38,7 +38,7 @@ const RequestSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) =>
|
|||||||
if (hidden) {
|
if (hidden) {
|
||||||
return <Spin />;
|
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) => {
|
export const RemoteSchemaComponent: React.FC<RemoteSchemaComponentProps> = (props) => {
|
||||||
|
@ -23,6 +23,11 @@ interface InsertAdjacentOptions {
|
|||||||
removeEmptyParents?: boolean;
|
removeEmptyParents?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RemoveOptions {
|
||||||
|
removeEmptyParents?: boolean;
|
||||||
|
breakComponent?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const generateUid = (s: ISchema) => {
|
const generateUid = (s: ISchema) => {
|
||||||
if (!s['x-uid']) {
|
if (!s['x-uid']) {
|
||||||
s['x-uid'] = uid();
|
s['x-uid'] = uid();
|
||||||
@ -108,8 +113,8 @@ export class Designable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(schema?: Schema, options: { removeEmptyParents?: boolean } = {}) {
|
remove(schema?: Schema, options: RemoveOptions = {}) {
|
||||||
const { removeEmptyParents } = options;
|
const { removeEmptyParents, breakComponent } = options;
|
||||||
let s = schema || this.current;
|
let s = schema || this.current;
|
||||||
let removed;
|
let removed;
|
||||||
while (s.parent) {
|
while (s.parent) {
|
||||||
@ -117,6 +122,9 @@ export class Designable {
|
|||||||
if (!removeEmptyParents) {
|
if (!removeEmptyParents) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (s?.parent?.['x-component'] === breakComponent) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
const count = Object.keys(s.parent.properties || {}).length;
|
const count = Object.keys(s.parent.properties || {}).length;
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
break;
|
break;
|
||||||
@ -332,10 +340,10 @@ export function useDesignable() {
|
|||||||
update(key);
|
update(key);
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
remove(schema: any, options?: any) {
|
remove(schema: any, options?: RemoveOptions) {
|
||||||
dn.remove(schema, options);
|
dn.remove(schema, options);
|
||||||
},
|
},
|
||||||
insertAdjacent(position: Position, schema: ISchema, options: InsertAdjacentOptions = {}) {
|
insertAdjacent(position: Position, schema: ISchema, options?: InsertAdjacentOptions) {
|
||||||
dn.insertAdjacent(position, schema, options);
|
dn.insertAdjacent(position, schema, options);
|
||||||
},
|
},
|
||||||
insertBeforeBegin(schema: ISchema) {
|
insertBeforeBegin(schema: ISchema) {
|
||||||
|
Loading…
Reference in New Issue
Block a user