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 = ` ${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 = `${DICT[position]}`;
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;
}
}