mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-02 21:59:54 +08:00
feat(editor): 组件树中支持多选拖动
This commit is contained in:
parent
83b14767b3
commit
5ac768f15b
@ -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 = '';
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user