feat(editor): 组件树中支持多选拖动

This commit is contained in:
roymondchen 2024-06-11 19:28:40 +08:00
parent 83b14767b3
commit 5ac768f15b
4 changed files with 140 additions and 24 deletions

View File

@ -144,7 +144,13 @@ export const useDrag = (services: Services | undefined) => {
targetIndex += 1; 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 = ''; dragState.dragOverNodeId = '';

View File

@ -49,11 +49,23 @@ import {
getPageFragmentList, getPageFragmentList,
getPageList, getPageList,
isFixed, isFixed,
moveItemsInContainer,
setChildrenLayout, setChildrenLayout,
setLayout, setLayout,
} from '@editor/utils/editor'; } from '@editor/utils/editor';
import { beforePaste, getAddParent } from '@editor/utils/operator'; 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 = { const canUsePluginMethods = {
async: [ async: [
'getLayout', 'getLayout',
@ -139,7 +151,7 @@ class Editor extends BaseService {
this.state.stageLoading = false; 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; if (!targetParent || !Array.isArray(targetParent.items)) return;
const configs = Array.isArray(config) ? config : [config];
const sourceIndicesInTargetParent: number[] = [];
const sourceOutTargetParent: MNode[] = [];
const newLayout = await this.getLayout(targetParent);
for (const config of configs) {
const { parent, node: curNode } = this.getNodeInfo(config.id, false); const { parent, node: curNode } = this.getNodeInfo(config.id, false);
if (!parent || !curNode) throw new Error('找不要删除的节点'); if (!parent || !curNode) throw new Error('找不要删除的节点');
const index = getNodeIndex(curNode.id, parent); const index = getNodeIndex(curNode.id, parent);
if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
if (parent.id === targetParent.id) { if (parent.id === targetParent.id) {
if (index === targetIndex) return; if (typeof index !== 'number' || index === -1) {
return;
if (index < targetIndex) {
targetIndex -= 1;
} }
} sourceIndicesInTargetParent.push(index);
} else {
const layout = await this.getLayout(parent); const layout = await this.getLayout(parent);
const newLayout = await this.getLayout(targetParent);
if (newLayout !== layout) { if (newLayout !== layout) {
setLayout(config, newLayout); setLayout(config, newLayout);
} }
parent.items?.splice(index, 1); parent.items?.splice(index, 1);
sourceOutTargetParent.push(config);
this.addModifiedNodeId(parent.id);
}
}
targetParent.items?.splice(targetIndex, 0, config); moveItemsInContainer(sourceIndicesInTargetParent, targetParent, targetIndex);
sourceOutTargetParent.forEach((config, index) => {
targetParent.items?.splice(targetIndex + index, 0, config);
this.addModifiedNodeId(config.id);
});
const page = this.get('page'); const page = this.get('page');
const root = this.get('root'); const root = this.get('root');
@ -918,12 +942,9 @@ class Editor extends BaseService {
}); });
} }
this.addModifiedNodeId(config.id);
this.addModifiedNodeId(parent.id);
this.pushHistoryState(); 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); super.usePlugin(options);
} }
public on<Name extends keyof EditorEvents, Param extends EditorEvents[Name]>(
eventName: Name,
listener: (...args: Param) => void | Promise<void>,
) {
return super.on(eventName, listener as any);
}
public once<Name extends keyof EditorEvents, Param extends EditorEvents[Name]>(
eventName: Name,
listener: (...args: Param) => void | Promise<void>,
) {
return super.once(eventName, listener as any);
}
public emit<Name extends keyof EditorEvents, Param extends EditorEvents[Name]>(eventName: Name, ...args: Param) {
return super.emit(eventName, ...args);
}
private addModifiedNodeId(id: Id) { private addModifiedNodeId(id: Id) {
if (!this.isHistoryStateChange) { if (!this.isHistoryStateChange) {
this.get('modifiedNodeIds').set(id, id); this.get('modifiedNodeIds').set(id, id);

View File

@ -292,3 +292,22 @@ export const traverseNode = <T extends NodeItem = NodeItem>(
}); });
} }
}; };
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;
}
}
}
};

View File

@ -136,3 +136,55 @@ describe('getRelativeStyle', () => {
expect(style?.color).toBe('red'); 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);
});
});