feat(client): add designable test cases
This commit is contained in:
parent
be489996c3
commit
1a7f638f85
@ -19,6 +19,7 @@ export interface IResource {
|
||||
create?: (params?: ActionParams) => Promise<any>;
|
||||
update?: (params?: ActionParams) => Promise<any>;
|
||||
destroy?: (params?: ActionParams) => Promise<any>;
|
||||
[key: string]: (params?: ActionParams) => Promise<any>;
|
||||
}
|
||||
|
||||
export class APIClient {
|
||||
|
@ -103,6 +103,96 @@ const FormBlockInitializerItem = itemWrap((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
const TestInitializerItem = itemWrap((props) => {
|
||||
const { insert } = props;
|
||||
return (
|
||||
<SchemaInitializer.Item
|
||||
icon={<FormOutlined />}
|
||||
onClick={() => {
|
||||
insert({
|
||||
type: 'void',
|
||||
name: uid(),
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'Grid',
|
||||
'x-uid': uid(),
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-uid': uid(),
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block1: {
|
||||
type: 'void',
|
||||
title: '1',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
block2: {
|
||||
type: 'void',
|
||||
title: '2',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
col12: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block3: {
|
||||
type: 'void',
|
||||
title: '3',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-uid': uid(),
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block4: {
|
||||
type: 'void',
|
||||
title: '4',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block5: {
|
||||
type: 'void',
|
||||
title: '5',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
Test
|
||||
</SchemaInitializer.Item>
|
||||
);
|
||||
});
|
||||
|
||||
export const AddGridBlockItem = observer((props: any) => {
|
||||
return (
|
||||
<SchemaInitializer.Button
|
||||
@ -123,6 +213,11 @@ export const AddGridBlockItem = observer((props: any) => {
|
||||
title: 'Form',
|
||||
component: FormBlockInitializerItem,
|
||||
},
|
||||
{
|
||||
type: 'item',
|
||||
title: 'Test',
|
||||
component: TestInitializerItem,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
|
@ -74,8 +74,10 @@ const useCurrentFieldSchema = (path: string) => {
|
||||
remove() {
|
||||
schema &&
|
||||
remove(schema, {
|
||||
breakComponent: 'Grid',
|
||||
removeEmptyParents: true,
|
||||
removeParentsIfNoChildren: true,
|
||||
breakSchema: (s) => {
|
||||
return s['x-component'] === 'Grid';
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
13
packages/client/src/schema-component/antd/grid/Block.tsx
Normal file
13
packages/client/src/schema-component/antd/grid/Block.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { observer, useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { DragHandler } from '../../';
|
||||
|
||||
export const Block = observer((props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
|
||||
Block {fieldSchema.title}
|
||||
<DragHandler />
|
||||
</div>
|
||||
);
|
||||
});
|
@ -1,5 +1,4 @@
|
||||
import { observer, useFieldSchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { BlockItem, DragHandler, Grid, SchemaComponent, SchemaComponentProvider } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
@ -13,87 +12,151 @@ const Block = observer((props) => {
|
||||
);
|
||||
});
|
||||
|
||||
const schema = {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
name: 'grid1',
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'Grid',
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col11: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block1: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '1',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
'x-uid': 'a9m97uffyku',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
block2: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '2.1',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
'x-uid': 'lensw462z8w',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
block234: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '2.2',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
'x-uid': 'lensw462z81',
|
||||
'x-async': false,
|
||||
'x-index': 3,
|
||||
},
|
||||
},
|
||||
'x-uid': '4shevom50rl',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
col12: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block3: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '3',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
'x-uid': 'kp4kjknbs2l',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'u7n3beze0gr',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': 'qfl04eq71tt',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
row2: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
properties: {
|
||||
col21: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block4: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '4',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
'x-uid': 'v5bd5kcyhat',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'bwg0mv89atk',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
col22: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block5: {
|
||||
_isJSONSchemaObject: true,
|
||||
version: '2.0',
|
||||
type: 'void',
|
||||
title: '5',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
'x-uid': 'noh1flz5oqg',
|
||||
'x-async': false,
|
||||
'x-index': 1,
|
||||
},
|
||||
},
|
||||
'x-uid': 'oz0rypr2rlj',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
'x-uid': '5igqpmvilz1',
|
||||
'x-async': false,
|
||||
'x-index': 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Grid, Block, BlockItem }}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
type: 'void',
|
||||
name: 'grid1',
|
||||
'x-component': 'Grid',
|
||||
'x-uid': uid(),
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-uid': uid(),
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block1: {
|
||||
type: 'void',
|
||||
title: '1',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
block2: {
|
||||
type: 'void',
|
||||
title: '2',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
col12: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block3: {
|
||||
type: 'void',
|
||||
title: '3',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
row2: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Row',
|
||||
'x-uid': uid(),
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block4: {
|
||||
type: 'void',
|
||||
title: '4',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
col22: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid.Col',
|
||||
properties: {
|
||||
block5: {
|
||||
type: 'void',
|
||||
title: '5',
|
||||
'x-decorator': 'BlockItem',
|
||||
'x-component': 'Block',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
}
|
||||
|
@ -1,20 +1,44 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { ISchema, observer, useFieldSchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
import { CardItem, Form, Grid, Markdown, SchemaComponent, SchemaComponentProvider, VoidTable } from '@nocobase/client';
|
||||
import {
|
||||
BlockItem,
|
||||
CardItem,
|
||||
DragHandler,
|
||||
Form,
|
||||
Grid,
|
||||
Markdown,
|
||||
SchemaComponent,
|
||||
SchemaComponentProvider,
|
||||
VoidTable
|
||||
} from '@nocobase/client';
|
||||
import React from 'react';
|
||||
|
||||
const Block = observer((props) => {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return (
|
||||
<div style={{ marginBottom: 20, padding: '0 20px', height: 50, lineHeight: '50px', background: '#f1f1f1' }}>
|
||||
Block {fieldSchema.title}
|
||||
<DragHandler />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const schema: ISchema = {
|
||||
type: 'void',
|
||||
name: 'grid1',
|
||||
'x-component': 'Grid',
|
||||
'x-item-initializer': 'Grid.AddBlockItem',
|
||||
'x-uid': uid(),
|
||||
properties: {},
|
||||
type: 'object',
|
||||
properties: {
|
||||
grid: {
|
||||
type: 'void',
|
||||
'x-component': 'Grid',
|
||||
'x-item-initializer': 'Grid.AddBlockItem',
|
||||
'x-uid': uid(),
|
||||
properties: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<SchemaComponentProvider components={{ Grid, CardItem, Markdown, Form, VoidTable }}>
|
||||
<SchemaComponentProvider components={{ BlockItem, Block, Grid, CardItem, Markdown, Form, VoidTable }}>
|
||||
<SchemaComponent schema={schema} />
|
||||
</SchemaComponentProvider>
|
||||
);
|
||||
|
@ -1 +1,3 @@
|
||||
export * from './Block';
|
||||
export * from './Grid';
|
||||
|
||||
|
@ -7,7 +7,6 @@ const useDragEnd = () => {
|
||||
const { refresh } = useDesignable();
|
||||
|
||||
return ({ active, over }: DragEndEvent) => {
|
||||
console.log({ active, over });
|
||||
const activeSchema = active?.data?.current?.schema;
|
||||
const overSchema = over?.data?.current?.schema;
|
||||
const insertAdjacent = over?.data?.current?.insertAdjacent;
|
||||
@ -35,7 +34,7 @@ const useDragEnd = () => {
|
||||
if (insertAdjacent) {
|
||||
dn.insertAdjacent(insertAdjacent, activeSchema, {
|
||||
wrap: wrapSchema,
|
||||
removeEmptyParents: true,
|
||||
removeParentsIfNoChildren: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -34,16 +34,11 @@ export const Sortable = (props: any) => {
|
||||
export const SortableItem: React.FC<any> = observer((props) => {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const onInsertAdjacent = ({ dn, orginDraggedParentSchema }) => {
|
||||
dn.removeIfChildrenEmpty(orginDraggedParentSchema, {
|
||||
removeEmptyParents: true,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Sortable
|
||||
{...props}
|
||||
id={field.address.toString()}
|
||||
data={{ insertAdjacent: 'afterEnd', onInsertAdjacent, schema: fieldSchema }}
|
||||
data={{ insertAdjacent: 'afterEnd', schema: fieldSchema }}
|
||||
>
|
||||
{props.children}
|
||||
</Sortable>
|
||||
|
@ -0,0 +1,453 @@
|
||||
import { Schema } from '@formily/react';
|
||||
import { createDesignable, Designable } from '../useDesignable';
|
||||
|
||||
describe('createDesignable', () => {
|
||||
let dn: Designable;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(() => {
|
||||
schema = new Schema({
|
||||
type: 'void',
|
||||
name: 'global',
|
||||
'x-uid': 'global',
|
||||
properties: {
|
||||
current: {
|
||||
type: 'void',
|
||||
'x-uid': 'current',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'void',
|
||||
'x-uid': 'child',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
dn = createDesignable({
|
||||
current: schema.properties.current,
|
||||
});
|
||||
});
|
||||
|
||||
describe('insert a new node', () => {
|
||||
test('insertBeforeBegin', () => {
|
||||
dn.insertBeforeBegin({
|
||||
name: 'abc',
|
||||
});
|
||||
expect(schema.toJSON()).toMatchObject({
|
||||
type: 'void',
|
||||
name: 'global',
|
||||
'x-uid': 'global',
|
||||
properties: {
|
||||
abc: {
|
||||
name: 'abc',
|
||||
type: 'void',
|
||||
},
|
||||
current: {
|
||||
type: 'void',
|
||||
name: 'current',
|
||||
'x-uid': 'current',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'void',
|
||||
'x-uid': 'child',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(schema.properties.abc['x-uid']).toBeDefined();
|
||||
expect(Object.keys(schema.properties)).toEqual(['abc', 'current']);
|
||||
});
|
||||
|
||||
test('insertAfterBegin', () => {
|
||||
dn.insertAfterBegin({
|
||||
name: 'abc',
|
||||
});
|
||||
expect(Object.keys(schema.properties.current.properties)).toEqual(['abc', 'child']);
|
||||
expect(schema.toJSON()).toMatchObject({
|
||||
type: 'void',
|
||||
name: 'global',
|
||||
'x-uid': 'global',
|
||||
properties: {
|
||||
current: {
|
||||
type: 'void',
|
||||
name: 'current',
|
||||
'x-uid': 'current',
|
||||
properties: {
|
||||
abc: {
|
||||
name: 'abc',
|
||||
type: 'void',
|
||||
},
|
||||
child: {
|
||||
type: 'void',
|
||||
'x-uid': 'child',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(schema.properties.current.properties.abc['x-uid']).toBeDefined();
|
||||
});
|
||||
|
||||
test('insertBeforeEnd', () => {
|
||||
dn.insertBeforeEnd({
|
||||
name: 'abc',
|
||||
});
|
||||
expect(schema.toJSON()).toMatchObject({
|
||||
type: 'void',
|
||||
name: 'global',
|
||||
'x-uid': 'global',
|
||||
properties: {
|
||||
current: {
|
||||
type: 'void',
|
||||
name: 'current',
|
||||
'x-uid': 'current',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'void',
|
||||
'x-uid': 'child',
|
||||
},
|
||||
abc: {
|
||||
name: 'abc',
|
||||
type: 'void',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(schema.properties.current.properties.abc['x-uid']).toBeDefined();
|
||||
});
|
||||
|
||||
test('insertAfterEnd', () => {
|
||||
dn.insertAfterEnd({
|
||||
name: 'abc',
|
||||
});
|
||||
expect(schema.toJSON()).toMatchObject({
|
||||
type: 'void',
|
||||
name: 'global',
|
||||
'x-uid': 'global',
|
||||
properties: {
|
||||
current: {
|
||||
type: 'void',
|
||||
name: 'current',
|
||||
'x-uid': 'current',
|
||||
properties: {
|
||||
child: {
|
||||
type: 'void',
|
||||
'x-uid': 'child',
|
||||
},
|
||||
},
|
||||
},
|
||||
abc: {
|
||||
name: 'abc',
|
||||
type: 'void',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(schema.properties.abc['x-uid']).toBeDefined();
|
||||
expect(Object.keys(schema.properties)).toEqual(['current', 'abc']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('move an existing node', () => {
|
||||
test('insertBeforeBegin', () => {
|
||||
dn.insertBeforeBegin(schema.properties.current.properties.child);
|
||||
expect(Object.keys(schema.properties)).toEqual(['child', 'current']);
|
||||
expect(schema.properties.current?.properties?.child).toBeUndefined();
|
||||
});
|
||||
|
||||
test('insertBeforeBegin', () => {
|
||||
dn.insertBeforeBegin({
|
||||
type: 'void',
|
||||
name: 'a1',
|
||||
});
|
||||
dn.insertBeforeBegin({
|
||||
type: 'void',
|
||||
name: 'a2',
|
||||
});
|
||||
dn.insertBeforeBegin({
|
||||
type: 'void',
|
||||
name: 'a3',
|
||||
});
|
||||
expect(Object.keys(schema.properties)).toEqual(['a1', 'a2', 'a3', 'current']);
|
||||
dn.insertBeforeBegin(schema.properties.a2);
|
||||
expect(Object.keys(schema.properties)).toEqual(['a1', 'a3', 'a2', 'current']);
|
||||
});
|
||||
|
||||
test('insertAfterBegin', () => {
|
||||
dn.insertBeforeBegin({
|
||||
name: 'a1',
|
||||
});
|
||||
dn.insertAfterBegin(schema.properties.a1);
|
||||
expect(Object.keys(schema.properties.current.properties)).toEqual(['a1', 'child']);
|
||||
expect(schema.properties.a1).toBeUndefined();
|
||||
});
|
||||
|
||||
test('insertBeforeEnd', () => {
|
||||
dn.insertBeforeBegin({
|
||||
name: 'a1',
|
||||
});
|
||||
dn.insertBeforeEnd(schema.properties.a1);
|
||||
expect(Object.keys(schema.properties.current.properties)).toEqual(['child', 'a1']);
|
||||
expect(schema.properties.a1).toBeUndefined();
|
||||
});
|
||||
|
||||
test('insertAfterEnd', () => {
|
||||
dn.insertAfterEnd(schema.properties.current.properties.child);
|
||||
expect(Object.keys(schema.properties)).toEqual(['current', 'child']);
|
||||
expect(schema.properties.current?.properties?.child).toBeUndefined();
|
||||
});
|
||||
|
||||
test('insertAfterEnd', () => {
|
||||
dn.insertAfterEnd({
|
||||
type: 'void',
|
||||
name: 'a1',
|
||||
});
|
||||
dn.insertAfterEnd({
|
||||
type: 'void',
|
||||
name: 'a2',
|
||||
});
|
||||
dn.insertAfterEnd({
|
||||
type: 'void',
|
||||
name: 'a3',
|
||||
});
|
||||
expect(Object.keys(schema.properties)).toEqual(['current', 'a3', 'a2', 'a1']);
|
||||
dn.insertAfterEnd(schema.properties.a2);
|
||||
expect(Object.keys(schema.properties)).toEqual(['current', 'a2', 'a3', 'a1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeIfNoChildren', () => {
|
||||
test('has child nodes', () => {
|
||||
dn.removeIfNoChildren(schema.properties.current);
|
||||
expect(schema.properties.current).toBeDefined();
|
||||
});
|
||||
|
||||
test('no child nodes', () => {
|
||||
dn.removeIfNoChildren(schema.properties.current.properties.child);
|
||||
expect(schema.properties.current?.properties?.child).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
test('without schema', () => {
|
||||
dn.remove();
|
||||
expect(schema?.properties?.current).toBeUndefined();
|
||||
});
|
||||
|
||||
test('without options', () => {
|
||||
dn.remove(schema.properties.current.properties.child);
|
||||
expect(schema.properties.current).toBeDefined();
|
||||
expect(schema.properties.current?.properties?.child).toBeUndefined();
|
||||
});
|
||||
|
||||
test('removeParentsIfNoChildren + breakSchema json', () => {
|
||||
dn.remove(schema.properties.current.properties.child, {
|
||||
removeParentsIfNoChildren: true,
|
||||
breakSchema: {
|
||||
'x-uid': 'global',
|
||||
},
|
||||
});
|
||||
expect(schema?.properties?.current).toBeUndefined();
|
||||
});
|
||||
|
||||
test('removeParentsIfNoChildren + breakSchema function', () => {
|
||||
dn.remove(schema.properties.current.properties.child, {
|
||||
removeParentsIfNoChildren: true,
|
||||
breakSchema: (s) => s['x-uid'] === 'global',
|
||||
});
|
||||
expect(schema?.properties?.current).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('x-index', () => {
|
||||
let dn: Designable;
|
||||
let schema: Schema;
|
||||
|
||||
beforeEach(() => {
|
||||
schema = new Schema({
|
||||
type: 'void',
|
||||
name: 'col1',
|
||||
'x-uid': 'col1',
|
||||
properties: {
|
||||
block1: {
|
||||
type: 'void',
|
||||
'x-index': 1,
|
||||
},
|
||||
block2: {
|
||||
type: 'void',
|
||||
'x-index': 2,
|
||||
},
|
||||
block3: {
|
||||
type: 'void',
|
||||
'x-index': 3,
|
||||
},
|
||||
block4: {
|
||||
type: 'void',
|
||||
'x-index': 4,
|
||||
},
|
||||
block5: {
|
||||
type: 'void',
|
||||
'x-index': 5,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('insertBeforeBeginOrAfterEnd', () => {
|
||||
dn = createDesignable({
|
||||
current: schema.properties.block2,
|
||||
});
|
||||
dn.insertBeforeBeginOrAfterEnd(schema.properties.block5);
|
||||
expect(Object.keys(schema.properties)).toEqual(['block1', 'block5', 'block2', 'block3', 'block4']);
|
||||
});
|
||||
|
||||
test('insertBeforeBegin', () => {
|
||||
dn = createDesignable({
|
||||
current: schema.properties.block2,
|
||||
});
|
||||
dn.insertBeforeBegin({
|
||||
name: 'block0',
|
||||
});
|
||||
expect(Object.keys(schema.properties)).toEqual(['block1', 'block0', 'block2', 'block3', 'block4', 'block5']);
|
||||
});
|
||||
|
||||
test('insertAfterEnd', () => {
|
||||
dn = createDesignable({
|
||||
current: schema.properties.block2,
|
||||
});
|
||||
dn.insertAfterEnd({
|
||||
name: 'block0',
|
||||
});
|
||||
expect(Object.keys(schema.properties)).toEqual(['block1', 'block2', 'block0', 'block3', 'block4', 'block5']);
|
||||
});
|
||||
|
||||
test('insertAfterBegin', () => {
|
||||
dn = createDesignable({
|
||||
current: schema,
|
||||
});
|
||||
dn.insertAfterBegin({
|
||||
name: 'block0',
|
||||
});
|
||||
expect(Object.keys(schema.properties)).toEqual(['block0', 'block1', 'block2', 'block3', 'block4', 'block5']);
|
||||
});
|
||||
|
||||
test('insertBeforeEnd', () => {
|
||||
dn = createDesignable({
|
||||
current: schema,
|
||||
});
|
||||
dn.insertBeforeEnd({
|
||||
name: 'block0',
|
||||
});
|
||||
expect(Object.keys(schema.properties)).toEqual(['block1', 'block2', 'block3', 'block4', 'block5', 'block0']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('wrap', () => {
|
||||
let dn: Designable;
|
||||
let schema: Schema;
|
||||
const colWrap = (s) => {
|
||||
return {
|
||||
name: 'col12',
|
||||
type: 'void',
|
||||
'x-uid': 'col12',
|
||||
properties: {
|
||||
[s.name]: s,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const rowWrap = (s) => {
|
||||
return {
|
||||
name: 'row2',
|
||||
type: 'void',
|
||||
'x-uid': 'row2',
|
||||
properties: {
|
||||
col21: {
|
||||
type: 'void',
|
||||
'x-uid': 'col21',
|
||||
properties: {
|
||||
[s.name]: s,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
schema = new Schema({
|
||||
type: 'void',
|
||||
name: 'grid1',
|
||||
'x-uid': 'grid1',
|
||||
properties: {
|
||||
row1: {
|
||||
type: 'void',
|
||||
'x-uid': 'row1',
|
||||
properties: {
|
||||
col11: {
|
||||
type: 'void',
|
||||
'x-uid': 'col11',
|
||||
properties: {
|
||||
block1: {
|
||||
type: 'void',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('insertBeforeBegin', () => {
|
||||
dn = createDesignable({
|
||||
current: schema.properties.row1.properties.col11,
|
||||
});
|
||||
const s = {
|
||||
type: 'void',
|
||||
name: 'block2',
|
||||
};
|
||||
dn.insertBeforeBegin(s, {
|
||||
wrap: colWrap,
|
||||
});
|
||||
expect(Object.keys(schema.properties.row1.properties)).toEqual(['col12', 'col11']);
|
||||
expect(schema.properties.row1.properties.col12.properties.block2).toBeDefined();
|
||||
});
|
||||
|
||||
test('insertAfterEnd', () => {
|
||||
dn = createDesignable({
|
||||
current: schema.properties.row1.properties.col11,
|
||||
});
|
||||
const s = {
|
||||
type: 'void',
|
||||
name: 'block2',
|
||||
};
|
||||
dn.insertAfterEnd(s, {
|
||||
wrap: colWrap,
|
||||
});
|
||||
expect(Object.keys(schema.properties.row1.properties)).toEqual(['col11', 'col12']);
|
||||
expect(schema.properties.row1.properties.col12.properties.block2).toBeDefined();
|
||||
});
|
||||
|
||||
test('removeParentsIfNoChildren', () => {
|
||||
const coldn = createDesignable({
|
||||
current: schema.properties.row1.properties.col11,
|
||||
});
|
||||
const s = {
|
||||
type: 'void',
|
||||
name: 'block2',
|
||||
};
|
||||
coldn.insertAfterEnd(s, { wrap: colWrap });
|
||||
const griddn = createDesignable({
|
||||
current: schema,
|
||||
});
|
||||
griddn.insertBeforeEnd(schema.properties.row1.properties.col12.properties.block2, {
|
||||
wrap: rowWrap,
|
||||
removeParentsIfNoChildren: true,
|
||||
});
|
||||
expect(Object.keys(schema.properties.row1.properties)).toEqual(['col11']);
|
||||
expect(Object.keys(schema.properties.row2.properties)).toEqual(['col21']);
|
||||
expect(schema.properties.row2.properties.col21.properties.block2).toBeDefined();
|
||||
});
|
||||
});
|
@ -3,6 +3,7 @@ import { uid } from '@formily/shared';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import React, { useContext } from 'react';
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { SchemaComponentContext } from '../context';
|
||||
|
||||
interface CreateDesignableProps {
|
||||
@ -20,12 +21,14 @@ type Position = 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd';
|
||||
|
||||
interface InsertAdjacentOptions {
|
||||
wrap?: (s: ISchema) => ISchema;
|
||||
removeEmptyParents?: boolean;
|
||||
removeParentsIfNoChildren?: boolean;
|
||||
}
|
||||
|
||||
type BreakFn = (s: ISchema) => boolean;
|
||||
|
||||
interface RemoveOptions {
|
||||
removeEmptyParents?: boolean;
|
||||
breakComponent?: string;
|
||||
removeParentsIfNoChildren?: boolean;
|
||||
breakSchema?: ISchema | BreakFn;
|
||||
}
|
||||
|
||||
const generateUid = (s: ISchema) => {
|
||||
@ -76,7 +79,7 @@ export class Designable {
|
||||
if (!this.events[name]) {
|
||||
return;
|
||||
}
|
||||
this.events[name].forEach((fn) => fn.bind(this)(...args));
|
||||
this.events[name].forEach((fn) => fn.bind(this)(this.current, ...args));
|
||||
}
|
||||
|
||||
insertAdjacent(position: Position, schema: ISchema, options: InsertAdjacentOptions = {}) {
|
||||
@ -92,17 +95,16 @@ export class Designable {
|
||||
}
|
||||
}
|
||||
|
||||
removeIfChildrenEmpty(schema?: Schema) {
|
||||
removeIfNoChildren(schema?: Schema) {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
let s = schema;
|
||||
const count = Object.keys(s.properties || {}).length;
|
||||
console.log('removeIfChildrenEmpty', count, s.properties);
|
||||
if (count > 0) {
|
||||
return;
|
||||
}
|
||||
let removed;
|
||||
let removed: Schema;
|
||||
while (s.parent) {
|
||||
removed = s.parent.removeProperty(s.name);
|
||||
const count = Object.keys(s.parent.properties || {}).length;
|
||||
@ -111,18 +113,42 @@ export class Designable {
|
||||
}
|
||||
s = s.parent;
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
remove(schema?: Schema, options: RemoveOptions = {}) {
|
||||
const { removeEmptyParents, breakComponent } = options;
|
||||
const { removeParentsIfNoChildren, breakSchema } = options;
|
||||
let s = schema || this.current;
|
||||
let removed;
|
||||
const matchSchema = (source: ISchema, target: ISchema) => {
|
||||
if (!source || !target) {
|
||||
return;
|
||||
}
|
||||
for (const key in target) {
|
||||
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
||||
const value = target[key];
|
||||
if (value !== source?.[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
while (s.parent) {
|
||||
removed = s.parent.removeProperty(s.name);
|
||||
if (!removeEmptyParents) {
|
||||
if (!removeParentsIfNoChildren) {
|
||||
break;
|
||||
}
|
||||
if (s?.parent?.['x-component'] === breakComponent) {
|
||||
if (typeof breakSchema === 'function') {
|
||||
if (breakSchema(s?.parent)) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (matchSchema(s?.parent, breakSchema)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s?.parent?.['x-component'] === breakSchema) {
|
||||
break;
|
||||
}
|
||||
const count = Object.keys(s.parent.properties || {}).length;
|
||||
@ -131,7 +157,7 @@ export class Designable {
|
||||
}
|
||||
s = s.parent;
|
||||
}
|
||||
this.emit('afterRemove', removed);
|
||||
this.emit('afterRemove', removed, options);
|
||||
}
|
||||
|
||||
insertBeforeBeginOrAfterEnd(schema: ISchema, options: InsertAdjacentOptions = {}) {
|
||||
@ -164,33 +190,38 @@ export class Designable {
|
||||
if (!Schema.isSchemaInstance(this.current)) {
|
||||
return;
|
||||
}
|
||||
const { wrap = defaultWrap, removeEmptyParents } = options;
|
||||
const properties = {};
|
||||
let start = false;
|
||||
|
||||
const opts = {};
|
||||
const { wrap = defaultWrap, removeParentsIfNoChildren } = options;
|
||||
if (Schema.isSchemaInstance(schema)) {
|
||||
schema.parent.removeProperty(schema.name);
|
||||
if (removeEmptyParents) {
|
||||
this.removeIfChildrenEmpty(schema.parent);
|
||||
if (removeParentsIfNoChildren) {
|
||||
opts['removed'] = this.removeIfNoChildren(schema.parent);
|
||||
}
|
||||
}
|
||||
|
||||
const properties = {};
|
||||
let start = false;
|
||||
let order = 0;
|
||||
let newOrder = 0;
|
||||
this.current.parent.mapProperties((property, key) => {
|
||||
if (key === this.current.name) {
|
||||
newOrder = order;
|
||||
start = true;
|
||||
++order;
|
||||
}
|
||||
property['x-index'] = order;
|
||||
++order;
|
||||
if (start) {
|
||||
properties[key] = property;
|
||||
this.current.parent.removeProperty(key);
|
||||
}
|
||||
});
|
||||
|
||||
this.prepareProperty(schema);
|
||||
const wrapped = wrap(schema);
|
||||
const s = this.current.parent.addProperty(wrapped.name, wrapped);
|
||||
const s = this.current.parent.addProperty(wrapped.name || uid(), wrapped);
|
||||
s['x-index'] = newOrder;
|
||||
s.parent = this.current.parent;
|
||||
this.current.parent.setProperties(properties);
|
||||
this.emit('afterInsertAdjacent', 'beforeBegin', s);
|
||||
this.emit('afterInsertAdjacent', 'beforeBegin', s, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,24 +234,29 @@ export class Designable {
|
||||
if (!Schema.isSchemaInstance(this.current)) {
|
||||
return;
|
||||
}
|
||||
const { wrap = defaultWrap, removeEmptyParents } = options;
|
||||
const opts = {};
|
||||
const { wrap = defaultWrap, removeParentsIfNoChildren } = options;
|
||||
if (Schema.isSchemaInstance(schema)) {
|
||||
schema.parent.removeProperty(schema.name);
|
||||
if (removeEmptyParents) {
|
||||
this.removeIfChildrenEmpty(schema.parent);
|
||||
if (removeParentsIfNoChildren) {
|
||||
opts['removed'] = this.removeIfNoChildren(schema.parent);
|
||||
}
|
||||
}
|
||||
const properties = {};
|
||||
this.current.mapProperties((schema, key) => {
|
||||
properties[key] = schema;
|
||||
let order = 1;
|
||||
this.current.mapProperties((s, key) => {
|
||||
s['x-index'] = order;
|
||||
++order;
|
||||
properties[key] = s;
|
||||
});
|
||||
this.current.properties = {};
|
||||
this.prepareProperty(schema);
|
||||
const wrapped = wrap(schema);
|
||||
const s = this.current.addProperty(wrapped.name, wrapped);
|
||||
const s = this.current.addProperty(wrapped.name || uid(), wrapped);
|
||||
s['x-index'] = 0;
|
||||
s.parent = this.current;
|
||||
this.current.setProperties(properties);
|
||||
this.emit('afterInsertAdjacent', 'afterBegin', s);
|
||||
this.emit('afterInsertAdjacent', 'afterBegin', s, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,18 +269,19 @@ export class Designable {
|
||||
if (!Schema.isSchemaInstance(this.current)) {
|
||||
return;
|
||||
}
|
||||
const { wrap = defaultWrap, removeEmptyParents } = options;
|
||||
const opts = {};
|
||||
const { wrap = defaultWrap, removeParentsIfNoChildren } = options;
|
||||
if (Schema.isSchemaInstance(schema)) {
|
||||
schema.parent.removeProperty(schema.name);
|
||||
if (removeEmptyParents) {
|
||||
this.removeIfChildrenEmpty(schema.parent);
|
||||
if (removeParentsIfNoChildren) {
|
||||
opts['removed'] = this.removeIfNoChildren(schema.parent);
|
||||
}
|
||||
}
|
||||
this.prepareProperty(schema);
|
||||
const wrapped = wrap(schema);
|
||||
const s = this.current.addProperty(wrapped.name || uid(), wrapped);
|
||||
s.parent = this.current;
|
||||
this.emit('afterInsertAdjacent', 'beforeEnd', s);
|
||||
this.emit('afterInsertAdjacent', 'beforeEnd', s, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -254,22 +291,29 @@ export class Designable {
|
||||
if (!Schema.isSchemaInstance(this.current)) {
|
||||
return;
|
||||
}
|
||||
const properties = {};
|
||||
let start = false;
|
||||
const { wrap = defaultWrap, removeEmptyParents } = options;
|
||||
const opts = {};
|
||||
const { wrap = defaultWrap, removeParentsIfNoChildren } = options;
|
||||
if (Schema.isSchemaInstance(schema)) {
|
||||
schema.parent.removeProperty(schema.name);
|
||||
console.log('insertAfterEnd', removeEmptyParents);
|
||||
if (removeEmptyParents) {
|
||||
this.removeIfChildrenEmpty(schema.parent);
|
||||
if (removeParentsIfNoChildren) {
|
||||
opts['removed'] = this.removeIfNoChildren(schema.parent);
|
||||
}
|
||||
schema.parent = null;
|
||||
}
|
||||
|
||||
let order = 0;
|
||||
let newOrder = 0;
|
||||
let start = false;
|
||||
const properties = {};
|
||||
|
||||
this.current.parent.mapProperties((property, key) => {
|
||||
property['x-index'] = order;
|
||||
if (key === this.current.name) {
|
||||
++order;
|
||||
newOrder = order;
|
||||
start = true;
|
||||
}
|
||||
++order;
|
||||
if (start && key !== this.current.name) {
|
||||
properties[key] = property;
|
||||
this.current.parent.removeProperty(key);
|
||||
@ -277,13 +321,12 @@ export class Designable {
|
||||
});
|
||||
|
||||
this.prepareProperty(schema);
|
||||
|
||||
const wrapped = wrap(schema);
|
||||
|
||||
const s = this.current.parent.addProperty(wrapped.name || uid(), wrapped);
|
||||
s.parent = this.current.parent;
|
||||
s['x-index'] = newOrder;
|
||||
this.current.parent.setProperties(properties);
|
||||
this.emit('afterInsertAdjacent', 'afterEnd', s);
|
||||
this.emit('afterInsertAdjacent', 'afterEnd', s, opts);
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,7 +340,16 @@ export function useDesignable() {
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const dn = createDesignable({ current: fieldSchema });
|
||||
dn.on('afterInsertAdjacent', refresh);
|
||||
const api = useAPIClient();
|
||||
dn.on('afterInsertAdjacent', async (current, position, schema) => {
|
||||
refresh();
|
||||
// await api.request({
|
||||
// url: `/ui_schemas:insertAdjacent/${current['x-uid']}?position=${position}`,
|
||||
// method: 'post',
|
||||
// data: schema.toJSON(),
|
||||
// });
|
||||
// console.log(current, position, schema);
|
||||
});
|
||||
dn.on('afterRemove', refresh);
|
||||
return {
|
||||
designable,
|
||||
|
Loading…
Reference in New Issue
Block a user