From 5ac768f15be33526dcbec4a69f969e9dcf36cbc5 Mon Sep 17 00:00:00 2001 From: roymondchen Date: Tue, 11 Jun 2024 19:28:40 +0800 Subject: [PATCH] =?UTF-8?q?feat(editor):=20=E7=BB=84=E4=BB=B6=E6=A0=91?= =?UTF-8?q?=E4=B8=AD=E6=94=AF=E6=8C=81=E5=A4=9A=E9=80=89=E6=8B=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/layouts/sidebar/layer/use-drag.ts | 8 +- packages/editor/src/services/editor.ts | 85 ++++++++++++++----- packages/editor/src/utils/editor.ts | 19 +++++ .../editor/tests/unit/utils/editor.spec.ts | 52 ++++++++++++ 4 files changed, 140 insertions(+), 24 deletions(-) diff --git a/packages/editor/src/layouts/sidebar/layer/use-drag.ts b/packages/editor/src/layouts/sidebar/layer/use-drag.ts index 53a1699e..6355fde2 100644 --- a/packages/editor/src/layouts/sidebar/layer/use-drag.ts +++ b/packages/editor/src/layouts/sidebar/layer/use-drag.ts @@ -144,7 +144,13 @@ export const useDrag = (services: Services | undefined) => { targetIndex += 1; } - services?.editorService.dragTo(node, targetParent, targetIndex); + const selectedNodes = services.editorService.get('nodes'); + + if (selectedNodes.find((n) => `${n.id}` === `${node.id}`)) { + services.editorService.dragTo(selectedNodes, targetParent, targetIndex); + } else { + services.editorService.dragTo([node], targetParent, targetIndex); + } } dragState.dragOverNodeId = ''; diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 71f18c11..efa2a480 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -49,11 +49,23 @@ import { getPageFragmentList, getPageList, isFixed, + moveItemsInContainer, setChildrenLayout, setLayout, } from '@editor/utils/editor'; import { beforePaste, getAddParent } from '@editor/utils/operator'; +export interface EditorEvents { + 'root-change': [value: StoreState['root'], preValue?: StoreState['root']]; + select: [node: MNode | null]; + add: [nodes: MNode[]]; + remove: [nodes: MNode[]]; + update: [nodes: MNode[]]; + 'move-layer': [offset: number | LayerOffset]; + 'drag-to': [data: { targetIndex: number; configs: MNode | MNode[]; targetParent: MContainer }]; + 'history-change': [data: MPage | MPageFragment]; +} + const canUsePluginMethods = { async: [ 'getLayout', @@ -139,7 +151,7 @@ class Editor extends BaseService { this.state.stageLoading = false; } - this.emit('root-change', value, preValue); + this.emit('root-change', value as StoreState['root'], preValue as StoreState['root']); } } @@ -877,34 +889,46 @@ class Editor extends BaseService { } } - public async dragTo(config: MNode, targetParent: MContainer, targetIndex: number) { + public async dragTo(config: MNode | MNode[], targetParent: MContainer, targetIndex: number) { if (!targetParent || !Array.isArray(targetParent.items)) return; - const { parent, node: curNode } = this.getNodeInfo(config.id, false); - if (!parent || !curNode) throw new Error('找不要删除的节点'); + const configs = Array.isArray(config) ? config : [config]; - const index = getNodeIndex(curNode.id, parent); + const sourceIndicesInTargetParent: number[] = []; + const sourceOutTargetParent: MNode[] = []; - if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点'); + const newLayout = await this.getLayout(targetParent); - if (parent.id === targetParent.id) { - if (index === targetIndex) return; + for (const config of configs) { + const { parent, node: curNode } = this.getNodeInfo(config.id, false); + if (!parent || !curNode) throw new Error('找不要删除的节点'); - if (index < targetIndex) { - targetIndex -= 1; + const index = getNodeIndex(curNode.id, parent); + + if (parent.id === targetParent.id) { + if (typeof index !== 'number' || index === -1) { + return; + } + sourceIndicesInTargetParent.push(index); + } else { + const layout = await this.getLayout(parent); + + if (newLayout !== layout) { + setLayout(config, newLayout); + } + + parent.items?.splice(index, 1); + sourceOutTargetParent.push(config); + this.addModifiedNodeId(parent.id); } } - const layout = await this.getLayout(parent); - const newLayout = await this.getLayout(targetParent); + moveItemsInContainer(sourceIndicesInTargetParent, targetParent, targetIndex); - if (newLayout !== layout) { - setLayout(config, newLayout); - } - - parent.items?.splice(index, 1); - - targetParent.items?.splice(targetIndex, 0, config); + sourceOutTargetParent.forEach((config, index) => { + targetParent.items?.splice(targetIndex + index, 0, config); + this.addModifiedNodeId(config.id); + }); const page = this.get('page'); const root = this.get('root'); @@ -918,12 +942,9 @@ class Editor extends BaseService { }); } - this.addModifiedNodeId(config.id); - this.addModifiedNodeId(parent.id); - this.pushHistoryState(); - this.emit('drag-to', { index, targetIndex, config, parent, targetParent }); + this.emit('drag-to', { targetIndex, configs, targetParent }); } /** @@ -1019,6 +1040,24 @@ class Editor extends BaseService { super.usePlugin(options); } + public on( + eventName: Name, + listener: (...args: Param) => void | Promise, + ) { + return super.on(eventName, listener as any); + } + + public once( + eventName: Name, + listener: (...args: Param) => void | Promise, + ) { + return super.once(eventName, listener as any); + } + + public emit(eventName: Name, ...args: Param) { + return super.emit(eventName, ...args); + } + private addModifiedNodeId(id: Id) { if (!this.isHistoryStateChange) { this.get('modifiedNodeIds').set(id, id); diff --git a/packages/editor/src/utils/editor.ts b/packages/editor/src/utils/editor.ts index f2b94782..e7af210c 100644 --- a/packages/editor/src/utils/editor.ts +++ b/packages/editor/src/utils/editor.ts @@ -292,3 +292,22 @@ export const traverseNode = ( }); } }; + +export const moveItemsInContainer = (sourceIndices: number[], parent: MContainer, targetIndex: number) => { + sourceIndices.sort((a, b) => a - b); + for (let i = sourceIndices.length - 1; i >= 0; i--) { + const sourceIndex = sourceIndices[i]; + if (sourceIndex === targetIndex) { + continue; + } + const [item] = parent.items.splice(sourceIndex, 1); + parent.items.splice(sourceIndex < targetIndex ? targetIndex - 1 : targetIndex, 0, item); + + // 更新后续源索引(因为数组已经改变) + for (let j = i - 1; j >= 0; j--) { + if (sourceIndices[j] >= targetIndex) { + sourceIndices[j] += 1; + } + } + } +}; diff --git a/packages/editor/tests/unit/utils/editor.spec.ts b/packages/editor/tests/unit/utils/editor.spec.ts index 7f3373ad..1e050574 100644 --- a/packages/editor/tests/unit/utils/editor.spec.ts +++ b/packages/editor/tests/unit/utils/editor.spec.ts @@ -136,3 +136,55 @@ describe('getRelativeStyle', () => { expect(style?.color).toBe('red'); }); }); + +describe('moveItemsInContainer', () => { + test('向下移动', () => { + const container = { id: 1, type: NodeType.CONTAINER, items: [{ id: 2 }, { id: 3 }, { id: 4 }] }; + editor.moveItemsInContainer([0], container, 0); + expect(container.items[0].id).toBe(2); + editor.moveItemsInContainer([0], container, 1); + expect(container.items[0].id).toBe(2); + editor.moveItemsInContainer([0], container, 2); + expect(container.items[0].id).toBe(3); + expect(container.items[1].id).toBe(2); + expect(container.items[2].id).toBe(4); + }); + test('向下移动到最后', () => { + const container = { id: 1, type: NodeType.CONTAINER, items: [{ id: 2 }, { id: 3 }, { id: 4 }] }; + editor.moveItemsInContainer([0], container, 3); + expect(container.items[0].id).toBe(3); + expect(container.items[1].id).toBe(4); + expect(container.items[2].id).toBe(2); + }); + + test('向上移动', () => { + const container = { id: 1, type: NodeType.CONTAINER, items: [{ id: 2 }, { id: 3 }, { id: 4 }] }; + editor.moveItemsInContainer([2], container, 3); + expect(container.items[2].id).toBe(4); + editor.moveItemsInContainer([2], container, 2); + expect(container.items[2].id).toBe(4); + editor.moveItemsInContainer([2], container, 1); + expect(container.items[0].id).toBe(2); + expect(container.items[1].id).toBe(4); + expect(container.items[2].id).toBe(3); + }); + test('向上移动到最后', () => { + const container = { id: 1, type: NodeType.CONTAINER, items: [{ id: 2 }, { id: 3 }, { id: 4 }] }; + editor.moveItemsInContainer([2], container, 0); + expect(container.items[0].id).toBe(4); + expect(container.items[1].id).toBe(2); + expect(container.items[2].id).toBe(3); + }); + + test('移动多个', () => { + const container = { + id: 1, + type: NodeType.CONTAINER, + items: [{ id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }, { id: 6 }, { id: 7 }], + }; + editor.moveItemsInContainer([0, 5], container, 0); + expect(container.items[0].id).toBe(2); + expect(container.items[1].id).toBe(7); + expect(container.items[2].id).toBe(3); + }); +});