2020-03-28 03:46:34 +08:00

575 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ReactDOM from 'react-dom';
import Debug from 'debug';
import { isFileSchema, isEmpty, throttle, deepEqual } from './index';
const DICT = {
left: '左',
right: '右',
top: '上',
bottom: '下',
in: '里',
};
const TOP_COMPONENT = ['Page', 'Component', 'Temp']; // 顶端模块,不支持放置兄弟节点
const debug = Debug('utils:dndHelper');
export default class DndHelper {
constructor(appHelper) {
this.appHelper = appHelper;
this.dragDom = null;
this.canvasEffectDom = null;
this.treeEffectDom = null;
this.containrDom = null;
this.sourceEntity = null;
this.tempEntity = null;
this.dragInfo = null;
this.canvasClearTimer = null;
this.treeClearTimer = null;
this.isDragging = false;
this.dragOverFunc = throttle(this.dragOverFunc, 50);
}
setCanvasWin(win) {
this.canvasWin = win;
if (this.canvasEffectDom) {
this.canvasWin.document.body.appendChild(this.canvasEffectDom);
}
}
emit(msg, ...args) {
this.appHelper && this.appHelper.emit(msg, ...args);
}
dragOverFunc(ev, schemaOrNode, isTree) {
if (!this.isDragging || !this.sourceEntity) return;
const entity = isTree
? this.getTreeEntity(schemaOrNode, ev)
: {
target: ev.currentTarget,
schema: schemaOrNode,
};
if (this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaKey === entity.schema.__ctx.lunaKey)
return;
let dragInfo = null;
if (isTree) {
dragInfo = this.getTreeDragInfo(ev, entity);
} else {
dragInfo = this.getDragInfo(ev, entity);
}
if (!dragInfo || deepEqual(this.dragInfo, dragInfo)) return;
this.dragInfo = dragInfo;
this.tempEntity = dragInfo.entity;
this.clearEffect(isTree);
this.addEffect(isTree);
}
changeCanvas() {
debug('change canvas', this.sourceEntity, this.tempEntity);
if (!this.sourceEntity || !this.tempEntity) return;
if (this.sourceEntity.isAdd) {
debug('add material', this.sourceEntity.schema, this.tempEntity.schema.__ctx.lunaKey, this.dragInfo.position);
this.emit('material.add', {
schema: this.sourceEntity.schema,
targetKey: this.tempEntity.schema.__ctx.lunaKey,
direction: this.dragInfo.position,
});
} else {
this.emit('material.move', {
lunaKey: this.sourceEntity.schema.__ctx.lunaKey,
targetKey: this.tempEntity.schema.__ctx.lunaKey,
direction: this.dragInfo.position,
});
}
}
getTreeEntity(node, ev) {
if (!node) return;
const schemaHelper = this.appHelper.schemaHelper;
const lunaKey = node.props.eventKey;
const schema = schemaHelper.schemaMap[lunaKey];
if (!schema) return;
const ref = schemaHelper.compThisMap[lunaKey];
const currentTarget = ev.currentTarget;
return {
schema,
target: ref && ReactDOM.findDOMNode(ref),
treeNodeTarget: currentTarget,
};
}
getDragTagDom(tagName) {
if (!this.dragDom) {
const dragDom = document.createElement('div');
dragDom.id = 'luna-drag-dom';
dragDom.style.height = '24px';
dragDom.style.position = 'absolute';
dragDom.style.zIndex = 10000000;
dragDom.style.transform = 'translateY(-10000px)';
dragDom.style.background = 'rgba(0, 0, 0, .5)';
dragDom.style.lineHeight = '24px';
dragDom.style.color = '#fff';
dragDom.style.padding = '0px 10px';
dragDom.style.display = 'inline-block';
document.body.appendChild(dragDom);
this.dragDom = dragDom;
}
this.dragDom.innerHTML = `<i class="next-icon next-icon-zujianku next-small"></i> ${tagName}`;
return this.dragDom;
}
getCanvasEffectDom() {
if (!this.canvasWin) {
throw new Error('should set the canvasWin first');
}
if (this.canvasClearTimer) {
clearTimeout(this.canvasClearTimer);
this.canvasClearTimer = null;
}
const { position } = this.dragInfo;
let canvasEffectDom = this.canvasEffectDom;
if (!canvasEffectDom) {
canvasEffectDom = document.createElement('div');
this.canvasWin.document.body.appendChild(canvasEffectDom);
this.canvasEffectDom = canvasEffectDom;
}
canvasEffectDom.id = 'luna-canvas-effect';
canvasEffectDom.innerHTML = `<b>${DICT[position]}</b>`;
canvasEffectDom.className = position;
canvasEffectDom.style.display = 'block';
return canvasEffectDom;
}
getTreeEffectDom() {
if (this.treeClearTimer) {
clearTimeout(this.treeClearTimer);
this.treeClearTimer = null;
}
let treeEffectDom = this.treeEffectDom;
if (!treeEffectDom) {
treeEffectDom = document.createElement('div');
this.treeEffectDom = treeEffectDom;
}
treeEffectDom.id = 'luna-tree-effect';
treeEffectDom.style.display = 'block';
return treeEffectDom;
}
getLunaContainerDom(target) {
if (!target) return null;
let parent = target.parentNode;
while (parent && (!parent.dataset || !parent.dataset.lunaKey)) {
parent = parent.parentNode;
}
return parent;
}
clearCompTreeEffect() {
const container = document.querySelector('.luna-comp-tree');
if (!container) return;
let treeItems = container.querySelectorAll('.tree-item');
(treeItems || []).forEach((item) => {
const classList = item.classList;
if (classList) {
classList.remove('top');
classList.remove('in');
classList.remove('bottom');
classList.remove('tree-item');
}
});
}
getDragInfo(ev, entity) {
if (!this.sourceEntity || !entity) return null;
const { target, schema } = entity;
const sourcePath = this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaPath;
const targetPath = schema.__ctx.lunaPath;
const sourceTarget = this.sourceEntity.target;
if (sourcePath === targetPath) return null;
if (targetPath && targetPath.startsWith(sourcePath)) return null;
const componentsMap = this.appHelper.get('componentsMap');
// if (!componentsMap || !componentsMap[schema.componentName]) return null;
let isContainer =
(componentsMap[schema.componentName] && componentsMap[schema.componentName].isContainer) || isFileSchema(schema); //是否是容器组件
if (schema.children && typeof schema.children !== 'object') {
//如果children是文本, 非模型结构,则非容器;
isContainer = false;
}
const rect = target.getBoundingClientRect();
const isSupportIn =
isContainer &&
(!schema.children || (schema.children && typeof schema.children === 'object' && isEmpty(schema.children)));
const sourceIsInline = sourceTarget && ['inline-block', 'inline'].includes(getComputedStyle(sourceTarget).display);
const isInline = ['inline-block', 'inline'].includes(getComputedStyle(target).display) && sourceIsInline;
const measure = isInline ? 'width' : 'height';
let sn = 0;
let position = 'top';
if (isContainer) {
sn = isSupportIn ? rect[measure] * 0.25 : Math.min(rect[measure] * 0.5, 10);
} else {
sn = rect[measure] * 0.5;
}
if (TOP_COMPONENT.includes(schema.componentName)) {
// 顶端组件拖拽over时只能放在其内部
position = 'in';
} else if (isInline && !isContainer) {
if (Math.abs(ev.clientX - rect.left) <= sn) {
position = 'left';
} else if (Math.abs(ev.clientX - rect.right) <= sn) {
position = 'right';
}
} else {
if (Math.abs(ev.clientY - rect.top) <= sn) {
position = 'top';
} else if (Math.abs(ev.clientY - rect.bottom) <= sn) {
position = 'bottom';
} else {
position = 'in';
}
}
// 判断是否是相邻元素, 往左|上拖
const isPrevSibling = sourceTarget === target.nextElementSibling;
if (isPrevSibling) {
if (position === 'right') position = 'left';
if (position === 'bottom') {
position = isContainer ? 'in' : 'top';
}
}
// 判断是否相邻元素,往右|下拖
const isPostSibling = sourceTarget === target.previousElementSibling;
if (isPostSibling) {
if (position === 'left') position = 'right';
if (position === 'top') {
position = isContainer ? 'in' : 'bottom';
}
}
//如果是容器组件且包含有子组件且是in状态进行智能识别处理
let subChildren = [];
const getChildren = (node) => {
if (!node || !node.childNodes || node.childNodes.length === 0) return;
node.childNodes.forEach((child) => {
if (child === sourceTarget) return;
if (child && child.getAttribute && child.getAttribute('draggable')) {
const isInline = ['inline', 'inline-block'].includes(getComputedStyle(child).display) && sourceIsInline;
const rect = child.getBoundingClientRect();
const l = Math.abs(ev.clientX - rect.left);
const r = Math.abs(ev.clientX - rect.right);
const t = Math.abs(ev.clientY - rect.top);
const b = Math.abs(ev.clientY - rect.bottom);
const minXDistance = Math.min(l, r);
const minYDistance = Math.min(t, b);
subChildren.push({
lunaKey: child.dataset.lunaKey,
node: child,
minDistance: isInline ? [minXDistance, minYDistance] : [minYDistance, minXDistance],
position: isInline ? (l > r ? 'right' : 'left') : b > t ? 'top' : 'bottom',
});
} else {
getChildren(child);
}
});
};
if (position === 'in' && isContainer && !isSupportIn) {
getChildren(target);
subChildren = subChildren.sort((a, b) => {
if (a.minDistance[0] === b.minDistance[0]) {
return a.minDistance[1] - b.minDistance[1];
}
return a.minDistance[0] - b.minDistance[0];
});
const tempChild = subChildren[0];
if (tempChild) {
if (sourceTarget === tempChild.node.nextElementSibling && ['bottom', 'right'].includes(tempChild.position))
return null;
if (sourceTarget === tempChild.node.previousElementSibling && ['top', 'left'].includes(tempChild.position))
return null;
position = tempChild.position;
entity = {
target: tempChild.node,
schema: this.appHelper.schemaHelper.schemaMap[tempChild.lunaKey],
};
}
}
const containrDom = position === 'in' ? entity.target : this.getLunaContainerDom(entity.target);
if (this.containrDom !== containrDom) {
if (this.containrDom) {
this.containrDom.style.outline = '';
}
this.containrDom = containrDom;
}
if (this.containrDom) {
containrDom.style.outline = '1px solid #1aab11';
}
// debug('drag info:', position, isSupportIn, isContainer, entity);
return {
position,
isSupportIn,
isContainer,
entity,
};
}
getTreeDragInfo(ev, entity) {
if (!this.sourceEntity || !entity) return null;
const { schema, treeNodeTarget } = entity;
const sourcePath = this.sourceEntity.schema.__ctx && this.sourceEntity.schema.__ctx.lunaPath;
const targetPath = schema.__ctx.lunaPath;
if (sourcePath === targetPath) return null;
if (targetPath && targetPath.startsWith(sourcePath)) return null;
const componentsMap = this.appHelper.get('componentsMap');
// if (!componentsMap || !componentsMap[schema.componentName]) return null;
let isContainer =
(componentsMap[schema.componentName] && componentsMap[schema.componentName].isContainer) || isFileSchema(schema); //是否是容器组件
if (schema.children && typeof schema.children !== 'object') {
//如果children是文本, 非模型结构,则非容器;
isContainer = false;
}
const rect = treeNodeTarget.getBoundingClientRect();
const isSupportIn =
isContainer &&
(!schema.children || (schema.children && typeof schema.children === 'object' && isEmpty(schema.children)));
const sn = isContainer && isSupportIn ? rect.height * 0.25 : rect.height * 0.5;
let position = 'in';
if (Math.abs(ev.clientY - rect.top) <= sn) {
position = 'top';
} else if (Math.abs(ev.clientY - rect.bottom) <= sn) {
position = 'bottom';
}
return {
position,
isSupportIn,
isContainer,
entity,
};
}
addEffect(isTree) {
if (!this.tempEntity) return;
const { position } = this.dragInfo;
const { target, treeNodeTarget } = this.tempEntity;
// this.clearCompTreeEffect();
if (isTree) {
//画父元素外框
let status = true;
let node = treeNodeTarget.parentNode;
while (status) {
if (node && node.parentNode) {
if (node.parentNode.tagName == 'LI' && node.parentNode.classList.contains('next-tree-node')) {
status = false;
if (this.treeNodeTargetParent !== node.parentNode || position === 'in') {
this.treeNodeTargetParent && this.treeNodeTargetParent.classList.remove('selected');
}
this.treeNodeTargetParent = node.parentNode;
if (position !== 'in') this.treeNodeTargetParent.classList.add('selected');
} else {
node = node.parentNode;
}
} else {
status = false;
}
}
treeNodeTarget.appendChild(this.getTreeEffectDom());
this.treeEffectDom.className = position;
} else {
const effectDom = this.getCanvasEffectDom();
const rect = target.getBoundingClientRect();
effectDom.style.left = (position === 'right' ? rect.right : rect.left) + 'px';
effectDom.style.top =
(position === 'bottom' ? rect.bottom : position === 'in' ? (rect.top + rect.bottom) / 2 : rect.top) + 'px';
effectDom.style.height = ['top', 'in', 'bottom'].includes(position) ? '2px' : rect.height + 'px';
effectDom.style.width = ['left', 'right'].includes(position) ? '2px' : rect.width + 'px';
}
}
clearCanvasEffect() {
if (this.canvasEffectDom) {
this.canvasEffectDom.style.display = 'none';
}
if (this.containrDom) {
this.containrDom.style.outline = '';
}
}
clearTreeEffect() {
if (this.treeEffectDom) {
this.treeEffectDom.style.display = 'none';
}
if (this.treeNodeTargetParent) {
this.treeNodeTargetParent.classList.remove('selected');
}
const tempTarget = this.tempEntity && this.tempEntity.treeNodeTarget;
const classList = tempTarget && tempTarget.classList;
if (classList) {
classList.remove('top');
classList.remove('bottom');
classList.remove('in');
classList.remove('tree-item');
}
}
clearEffect(isTree) {
if (this.isDragging) {
// if (isTree) {
if (this.treeClearTimer) {
clearTimeout(this.treeClearTimer);
this.treeClearTimer = null;
}
this.treeClearTimer = setTimeout(() => {
this.clearTreeEffect();
}, 300);
// } else {
if (this.canvasClearTimer) {
clearTimeout(this.canvasClearTimer);
this.canvasClearTimer = null;
}
this.canvasClearTimer = setTimeout(() => {
this.clearCanvasEffect();
}, 300);
// }
} else {
// if (isTree) {
this.clearTreeEffect();
// } else {
this.clearCanvasEffect();
// }
}
}
handleDragStart(ev, lunaKey) {
ev.stopPropagation();
const target = ev.currentTarget;
target.style.filter = 'blur(2px)';
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
ev.dataTransfer.setDragImage(this.getDragTagDom(schema.componentName), 0, 0);
this.sourceEntity = {
target,
schema,
};
this.isDragging = true;
}
handleDragEnd(ev) {
ev.stopPropagation();
ev.preventDefault();
this.isDragging = false;
if (!this.sourceEntity) return;
if (this.sourceEntity.target) {
this.sourceEntity.target.style.filter = '';
}
this.clearEffect();
}
handleDragOver(ev, lunaKey) {
ev.preventDefault();
ev.stopPropagation();
this.isDragging = true;
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
this.dragOverFunc(
{
clientX: ev.clientX,
clientY: ev.clientY,
currentTarget: ev.currentTarget,
},
schema,
);
}
handleDragLeave(ev) {
//避免移动到treeEffectDom上的抖动
ev.stopPropagation();
if (!this.tempEntity) return;
const rect = ev.target.getBoundingClientRect();
// 如果鼠标位置还在当前元素范围内则不认为是dragLeave
if (ev.x >= rect.left && ev.x <= rect.right && ev.y >= rect.top && ev.y <= rect.bottom) return;
debug('canvas drag leave', ev);
this.clearEffect();
this.dragInfo = null;
this.isDragging = false;
}
handleDrop(ev) {
ev.stopPropagation();
debug('drop+++++');
this.isDragging = false;
this.changeCanvas();
this.clearEffect();
}
handleTreeDragStart(ev) {
const { event, node } = ev;
event.stopPropagation();
const lunaKey = node.props.eventKey;
const schema = this.appHelper.schemaHelper.schemaMap[lunaKey];
if (!schema) return;
event.dataTransfer.setDragImage(this.getDragTagDom(schema.componentName), 0, 0);
this.sourceEntity = this.getTreeEntity(node, event);
if (this.sourceEntity.target) {
this.sourceEntity.target.style.filter = 'blur(2px)';
}
this.isDragging = true;
}
handleTreeDragEnd(ev) {
const { event } = ev;
event.stopPropagation();
event.preventDefault();
this.isDragging = false;
if (!this.sourceEntity) return;
if (this.sourceEntity.target) {
this.sourceEntity.target.style.filter = '';
}
this.clearEffect(true);
}
handleTreeDragOver(ev) {
const { event, node } = ev;
event.preventDefault();
event.stopPropagation();
this.isDragging = true;
this.dragOverFunc(
{
clientX: event.clientX,
clientY: event.clientY,
currentTarget: event.currentTarget.children[0],
},
node,
true,
);
}
handleTreeDragLeave(ev) {
const { event } = ev;
event.stopPropagation();
if (!this.tempEntity) return;
//避免移动到treeEffectDom上的抖动
if (this.treeEffectDom && this.treeEffectDom.parentNode.parentNode === event.currentTarget) return;
debug('++++ drag leave tree', ev, this.isDragging);
this.clearEffect(true);
this.isDragging = false;
}
handleTreeDrop(ev) {
const { event } = ev;
event.stopPropagation();
this.isDragging = false;
this.changeCanvas();
this.clearEffect(true);
}
handleResourceDragStart(ev, title, schema) {
ev.stopPropagation();
ev.dataTransfer.setDragImage(this.getDragTagDom(title), -2, -2);
this.sourceEntity = {
isAdd: true,
schema,
};
this.isDragging = true;
}
}