mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-03 06:16:11 +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;
|
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 = '';
|
||||||
|
@ -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);
|
||||||
|
@ -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');
|
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