diff --git a/packages/core/client/src/schema-component/antd/action/ActionBar.tsx b/packages/core/client/src/schema-component/antd/action/ActionBar.tsx
index 27e77f818..fc8a36442 100644
--- a/packages/core/client/src/schema-component/antd/action/ActionBar.tsx
+++ b/packages/core/client/src/schema-component/antd/action/ActionBar.tsx
@@ -1,3 +1,4 @@
+import { css } from '@emotion/css';
import { observer, RecursionField, useFieldSchema } from '@formily/react';
import { Space } from 'antd';
import React from 'react';
@@ -25,8 +26,15 @@ export const ActionBar = observer((props: any) => {
);
}
return (
-
-
+
+
{fieldSchema.mapProperties((schema, key) => {
diff --git a/packages/core/client/src/schema-component/antd/grid/Grid.tsx b/packages/core/client/src/schema-component/antd/grid/Grid.tsx
index 710bd0500..6c36dc383 100644
--- a/packages/core/client/src/schema-component/antd/grid/Grid.tsx
+++ b/packages/core/client/src/schema-component/antd/grid/Grid.tsx
@@ -1,14 +1,14 @@
-import { useDndContext, useDndMonitor, useDroppable } from '@dnd-kit/core';
+import { useDndContext, useDndMonitor, useDraggable, 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, useEffect, useRef, useState } from 'react';
-import { useFormBlockContext, useSchemaInitializer } from '../../../';
+import { useDesignable, useFormBlockContext, useSchemaInitializer } from '../../../';
import { DndContext } from '../../common/dnd-context';
-const GridRowContext = createContext(null);
-const GridColContext = createContext(null);
+const GridRowContext = createContext({});
+const GridColContext = createContext({});
const GridContext = createContext({});
const breakRemoveOnGrid = (s: Schema) => s['x-component'] === 'Grid';
@@ -19,6 +19,8 @@ const ColDivider = (props) => {
id: props.id,
data: props.data,
});
+ const { dn, designable } = useDesignable();
+ const dividerRef = useRef();
const droppableStyle = {
backgroundColor: isOver ? 'rgba(241, 139, 98, .1)' : undefined,
@@ -38,10 +40,89 @@ const ColDivider = (props) => {
visible = activeSchema !== currentSchema && downSchema !== activeSchema;
}
}
+ const prevSchema = props.cols[props.index];
+ const nextSchema = props.cols[props.index + 1];
+ const {
+ attributes,
+ listeners,
+ setNodeRef: setDraggableNodeRef,
+ isDragging,
+ } = useDraggable({
+ disabled: props.first || props.last || !designable,
+ id: props.id,
+ data: {
+ dividerRef,
+ prevSchema,
+ nextSchema,
+ },
+ });
+
+ const [clientWidths, setClientWidths] = useState([0, 0]);
+
+ useDndMonitor({
+ onDragStart(event) {
+ if (!isDragging) {
+ return;
+ }
+ const el = dividerRef.current;
+ const prev = el.previousElementSibling as HTMLDivElement;
+ const next = el.nextElementSibling as HTMLDivElement;
+ setClientWidths([prev.clientWidth, next.clientWidth]);
+ },
+ onDragMove(event) {
+ if (!isDragging) {
+ return;
+ }
+ const el = dividerRef.current;
+ const prev = el.previousElementSibling as HTMLDivElement;
+ const next = el.nextElementSibling as HTMLDivElement;
+ prev.style.width = `calc(${clientWidths[0]}px + ${event.delta.x}px)`;
+ next.style.width = `calc(${clientWidths[1]}px - ${event.delta.x}px)`;
+ },
+ onDragEnd(event) {
+ if (clientWidths[0] <= 0 || clientWidths[1] <= 0) {
+ return;
+ }
+ setClientWidths([0, 0]);
+ if (!prevSchema || !nextSchema) {
+ return;
+ }
+ const el = dividerRef.current;
+ const prev = el.previousElementSibling as HTMLDivElement;
+ const next = el.nextElementSibling as HTMLDivElement;
+ prevSchema['x-component-props'] = prevSchema['x-component-props'] || {};
+ nextSchema['x-component-props'] = nextSchema['x-component-props'] || {};
+ prevSchema['x-component-props']['width'] =
+ (100 * (prev?.clientWidth + 24 + 24 / props.cols.length)) / el.parentElement.clientWidth;
+ nextSchema['x-component-props']['width'] =
+ (100 * (next?.clientWidth + 24 + 24 / props.cols.length)) / el.parentElement.clientWidth;
+ dn.emit('batchPatch', {
+ schemas: [
+ {
+ ['x-uid']: prevSchema['x-uid'],
+ 'x-component-props': {
+ ...prevSchema['x-component-props'],
+ },
+ },
+ {
+ ['x-uid']: nextSchema['x-uid'],
+ 'x-component-props': {
+ ...nextSchema['x-component-props'],
+ },
+ },
+ ],
+ });
+ },
+ });
return (
{
+ if (visible) {
+ setNodeRef(el);
+ dividerRef.current = el;
+ }
+ }}
className={cls(
'nb-col-divider',
css`
@@ -49,7 +130,36 @@ const ColDivider = (props) => {
`,
)}
style={{ ...droppableStyle }}
- >
+ >
+
+
);
};
@@ -255,7 +365,7 @@ Grid.Row = observer((props) => {
const cols = useColProperties();
return (
-
+
{
{
Grid.Col = observer((props: any) => {
const { cols } = useContext(GridRowContext);
- const w = props.width || 100 / cols.length;
+ const schema = useFieldSchema();
+ const field = useField();
+ const w = schema?.['x-component-props']?.['width'] || 100 / cols.length;
const width = `calc(${w}% - 24px - 24px / ${cols.length})`;
+ const { isOver, setNodeRef } = useDroppable({
+ id: field.address.toString(),
+ data: {
+ insertAdjacent: 'beforeEnd',
+ schema,
+ wrapSchema: (s) => s,
+ },
+ });
return (
-
- {props.children}
-
+
+
+ {props.children}
+
+
);
});
diff --git a/packages/core/client/src/schema-component/common/dnd-context/index.tsx b/packages/core/client/src/schema-component/common/dnd-context/index.tsx
index 8f6013b9a..acf9a80bf 100644
--- a/packages/core/client/src/schema-component/common/dnd-context/index.tsx
+++ b/packages/core/client/src/schema-component/common/dnd-context/index.tsx
@@ -1,7 +1,7 @@
import { DndContext as DndKitContext, DragEndEvent, DragOverlay, rectIntersection } from '@dnd-kit/core';
import { Props } from '@dnd-kit/core/dist/components/DndContext/DndContext';
import { observer } from '@formily/react';
-import React from 'react';
+import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAPIClient } from '../../../';
import { createDesignable, useDesignable } from '../../hooks';
@@ -18,6 +18,7 @@ const useDragEnd = (props?: any) => {
const insertAdjacent = over?.data?.current?.insertAdjacent;
const breakRemoveOn = over?.data?.current?.breakRemoveOn;
const wrapSchema = over?.data?.current?.wrapSchema;
+ const onSuccess = over?.data?.current?.onSuccess;
if (!activeSchema || !overSchema) {
props?.onDragEnd?.(event);
@@ -49,6 +50,7 @@ const useDragEnd = (props?: any) => {
wrap: wrapSchema,
breakRemoveOn,
removeParentsIfNoChildren: true,
+ onSuccess,
});
props?.onDragEnd?.(event);
return;
@@ -58,15 +60,25 @@ const useDragEnd = (props?: any) => {
export const DndContext = observer((props: Props) => {
const { t } = useTranslation();
+ const [visible, setVisible] = useState(true);
return (
-
+ {
+ const { active } = event;
+ const activeSchema = active?.data?.current?.schema;
+ setVisible(!!activeSchema);
+ }}
+ onDragEnd={useDragEnd(props)}
+ >
- {t('Dragging')}
+ {visible && {t('Dragging')}}
{props.children}
diff --git a/packages/core/client/src/schema-component/hooks/useDesignable.tsx b/packages/core/client/src/schema-component/hooks/useDesignable.tsx
index b989cb8b5..a51c98540 100644
--- a/packages/core/client/src/schema-component/hooks/useDesignable.tsx
+++ b/packages/core/client/src/schema-component/hooks/useDesignable.tsx
@@ -111,7 +111,35 @@ export class Designable {
if (!api) {
return;
}
- this.on('insertAdjacent', async ({ onSuccess, current, position, schema, wrap, removed }) => {
+ const updateColumnSize = (parent: Schema) => {
+ if (!parent) {
+ return;
+ }
+ const len = Object.values(parent.properties).length;
+ const schemas = [];
+ parent.mapProperties((s) => {
+ s['x-component-props'] = s['x-component-props'] || {};
+ s['x-component-props']['width'] = 100 / len;
+ if (s['x-uid']) {
+ schemas.push({
+ 'x-uid': s['x-uid'],
+ 'x-component-props': s['x-component-props'],
+ });
+ }
+ });
+ if (parent['x-uid'] && schemas.length) {
+ return schemas;
+ }
+ return [];
+ };
+ this.on('insertAdjacent', async ({ onSuccess, current, position, schema, wrap, wrapped, removed }) => {
+ let schemas = [];
+ if (wrapped?.['x-component'] === 'Grid.Col') {
+ schemas = updateColumnSize(wrapped.parent);
+ }
+ if (removed?.['x-component'] === 'Grid.Col') {
+ schemas = updateColumnSize(removed.parent);
+ }
refresh();
if (!current['x-uid']) {
return;
@@ -124,6 +152,13 @@ export class Designable {
wrap,
},
});
+ if (schemas.length) {
+ await api.request({
+ url: `/uiSchemas:batchPatch`,
+ method: 'post',
+ data: schemas,
+ });
+ }
if (removed?.['x-uid']) {
await api.request({
url: `/uiSchemas:remove/${removed['x-uid']}`,
@@ -147,7 +182,20 @@ export class Designable {
});
message.success(t('Saved successfully'), 0.2);
});
+ this.on('batchPatch', async ({ schemas }) => {
+ refresh();
+ await api.request({
+ url: `/uiSchemas:batchPatch`,
+ method: 'post',
+ data: schemas,
+ });
+ message.success(t('Saved successfully'), 0.2);
+ });
this.on('remove', async ({ removed }) => {
+ let schemas = [];
+ if (removed?.['x-component'] === 'Grid.Col') {
+ schemas = updateColumnSize(removed.parent);
+ }
refresh();
if (!removed?.['x-uid']) {
return;
@@ -156,6 +204,13 @@ export class Designable {
url: `/uiSchemas:remove/${removed['x-uid']}`,
method: 'post',
});
+ if (schemas.length) {
+ await api.request({
+ url: `/uiSchemas:batchPatch`,
+ method: 'post',
+ data: schemas,
+ });
+ }
message.success(t('Saved successfully'), 0.2);
});
}
@@ -177,14 +232,14 @@ export class Designable {
generateUid(schema);
}
- on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch', listener: any) {
+ on(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', listener: any) {
if (!this.events[name]) {
this.events[name] = [];
}
this.events[name].push(listener);
}
- emit(name: 'insertAdjacent' | 'remove' | 'error' | 'patch', ...args) {
+ emit(name: 'insertAdjacent' | 'remove' | 'error' | 'patch' | 'batchPatch', ...args) {
if (!this.events[name]) {
return;
}
@@ -356,6 +411,7 @@ export class Designable {
this.emit('insertAdjacent', {
position: 'beforeBegin',
schema: schema2,
+ wrapped,
wrap: schema1,
...opts,
});
@@ -407,6 +463,7 @@ export class Designable {
position: 'afterBegin',
schema: schema2,
wrap: schema1,
+ wrapped,
...opts,
});
}
@@ -421,6 +478,7 @@ export class Designable {
if (!Schema.isSchemaInstance(this.current)) {
return;
}
+ delete schema['x-index'];
const opts = { onSuccess: options.onSuccess };
const { wrap = defaultWrap, breakRemoveOn, removeParentsIfNoChildren } = options;
if (Schema.isSchemaInstance(schema)) {
@@ -447,6 +505,7 @@ export class Designable {
position: 'beforeEnd',
schema: schema2,
wrap: schema1,
+ wrapped,
...opts,
});
}
@@ -508,6 +567,7 @@ export class Designable {
position: 'afterEnd',
schema: schema2,
wrap: schema1,
+ wrapped,
...opts,
});
}
diff --git a/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts b/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts
index e21290d67..8cc649dc6 100644
--- a/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts
+++ b/packages/plugins/ui-schema-storage/src/actions/ui-schema-action.ts
@@ -43,6 +43,7 @@ export const uiSchemaActions = {
insertNewSchema: callRepositoryMethod('insertNewSchema', 'values'),
remove: callRepositoryMethod('remove', 'resourceIndex'),
patch: callRepositoryMethod('patch', 'values'),
+ batchPatch: callRepositoryMethod('batchPatch', 'values'),
clearAncestor: callRepositoryMethod('clearAncestor', 'resourceIndex'),
async insertAdjacent(ctx: Context, next) {
diff --git a/packages/plugins/ui-schema-storage/src/repository.ts b/packages/plugins/ui-schema-storage/src/repository.ts
index 2807d5ed0..0128497f4 100644
--- a/packages/plugins/ui-schema-storage/src/repository.ts
+++ b/packages/plugins/ui-schema-storage/src/repository.ts
@@ -303,6 +303,14 @@ export class UiSchemaRepository extends Repository {
await traverSchemaTree(newSchema);
}
+ @transaction()
+ async batchPatch(schemas: any[], options?) {
+ const { transaction } = options;
+ for (const schema of schemas) {
+ await this.patch(schema, { ...options, transaction });
+ }
+ }
+
async updateNode(uid: string, schema: any, transaction?: Transaction) {
const nodeModel = await this.findOne({
filter: {