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;
}
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 = '';

View File

@ -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<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) {
if (!this.isHistoryStateChange) {
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');
});
});
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);
});
});