mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-16 22:42:16 +00:00
finetuning drag&drop
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { bindDragEnterLeave } from '../helpers/DragAndDrop.js';
|
||||
|
||||
export default {
|
||||
mounted(el, binding) {
|
||||
const delay = parseInt(binding.arg) || 300;
|
||||
@@ -12,90 +14,40 @@ export default {
|
||||
timeout = null;
|
||||
}
|
||||
|
||||
let lastTarget;
|
||||
let lastX;
|
||||
let lastY;
|
||||
|
||||
function onDragOver(evt) {
|
||||
if (lastX != evt.offsetX || lastY != evt.offsetY || lastTarget != evt.target) {
|
||||
// moved
|
||||
lastTarget = evt.target;
|
||||
lastX = evt.offsetX;
|
||||
lastY = evt.offsetY;
|
||||
|
||||
stopCountdown();
|
||||
startCountdown();
|
||||
}
|
||||
}
|
||||
|
||||
function onEnter(evt) {
|
||||
let lastTarget = evt.target;
|
||||
let lastX = evt.offsetX;
|
||||
let lastY = evt.offsetY;
|
||||
lastTarget = evt.target;
|
||||
lastX = evt.offsetX;
|
||||
lastY = evt.offsetY;
|
||||
|
||||
el.addEventListener('dragover', evt => {
|
||||
if (lastX != evt.offsetX || lastY != evt.offsetY || lastTarget != evt.target) {
|
||||
// moved
|
||||
lastTarget = evt.target;
|
||||
lastX = evt.offsetX;
|
||||
lastY = evt.offsetY;
|
||||
|
||||
stopCountdown();
|
||||
startCountdown();
|
||||
}
|
||||
});
|
||||
el.addEventListener('dragover', onDragOver);
|
||||
|
||||
startCountdown();
|
||||
}
|
||||
function onLeave() {
|
||||
stopCountdown();
|
||||
el.removeEventListener('dragover', onDragOver);
|
||||
}
|
||||
|
||||
// NOTE(chris): add save dragenter and dragleave events
|
||||
// that won't fire when hovering over child elements
|
||||
|
||||
let skipLeave = false;
|
||||
let skipLeaveParent = true;
|
||||
|
||||
function init(evt) {
|
||||
skipLeave = false;
|
||||
skipLeaveParent = true;
|
||||
// add global listeners
|
||||
window.addEventListener('dragenter', globalDragenter, true);
|
||||
window.addEventListener('dragleave', globalDragleave, true);
|
||||
window.addEventListener('drop', globalDrop, true);
|
||||
// call enter
|
||||
onEnter(evt);
|
||||
// remove self
|
||||
el.removeEventListener('dragenter', init);
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
// remove global listeners
|
||||
window.removeEventListener('dragenter', globalDragenter, true);
|
||||
window.removeEventListener('dragleave', globalDragleave, true);
|
||||
window.removeEventListener('drop', globalDrop, true);
|
||||
// call leave
|
||||
onLeave();
|
||||
// add init
|
||||
el.addEventListener('dragenter', init);
|
||||
}
|
||||
|
||||
function globalDragenter(evt) {
|
||||
skipLeaveParent = false;
|
||||
if (el != evt.target && !el.contains(evt.target)) {
|
||||
cleanup();
|
||||
} else {
|
||||
skipLeave = true;
|
||||
}
|
||||
}
|
||||
function globalDragleave(evt) {
|
||||
if (el != evt.target && !el.contains(evt.target)) {
|
||||
if (skipLeaveParent) {
|
||||
skipLeaveParent = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (skipLeave) {
|
||||
skipLeave = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
function globalDrop(evt) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
el.addEventListener('dragenter', init);
|
||||
el.initFunc = init;
|
||||
el.fhcDragClickCleanup = bindDragEnterLeave(el, onEnter, onLeave);
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
el.removeEventListener('dragenter', el.initFunc);
|
||||
delete el.initFunc;
|
||||
el.fhcDragClickCleanup();
|
||||
delete el.fhcDragClickCleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { setTransferData, convertToValidDragObject, dragendWorker } from '../helpers/DragAndDrop.js';
|
||||
import { setTransferData, convertToValidDragObject } from '../helpers/DragAndDrop.js';
|
||||
|
||||
const EFFECTS = [
|
||||
'none',
|
||||
@@ -24,58 +24,62 @@ export default {
|
||||
el.draggable = true;
|
||||
}
|
||||
|
||||
el.addEventListener('dragstart', evt => {
|
||||
const bcc = new BroadcastChannel('fhc-dnd');
|
||||
let blocked = false;
|
||||
|
||||
function onStart(evt) {
|
||||
const value = el.dataset.fhcDraggableValue;
|
||||
if (value) {
|
||||
setTransferData(evt, JSON.parse(value), true);
|
||||
if (el.dataset.fhcEffectAllowed)
|
||||
evt.dataTransfer.effectAllowed = el.dataset.fhcEffectAllowed;
|
||||
blockDragend();
|
||||
|
||||
bcc.onmessage = e => {
|
||||
if (e.data == 'block') {
|
||||
blocked = true;
|
||||
} else if (e.data == 'release') {
|
||||
let evt = null;
|
||||
if (blocked && blocked.evt) {
|
||||
evt = blocked.evt;
|
||||
}
|
||||
blocked = false;
|
||||
if (evt)
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
evt.preventDefault();
|
||||
}
|
||||
}, binding.modifiers.capture);
|
||||
|
||||
let id;
|
||||
let evt = null;
|
||||
let dataTransfer = null;
|
||||
function blockDragend() {
|
||||
id = el.dataset.fhcDraggableValue;
|
||||
dragendWorker.port.postMessage(['init', id]);
|
||||
window.addEventListener('dragend', blockHandler, true);
|
||||
}
|
||||
function unblockDragend(e) {
|
||||
if (e) {
|
||||
evt = e;
|
||||
dataTransfer = e.dataTransfer;
|
||||
|
||||
function onEnd(evt) {
|
||||
if (blocked) {
|
||||
blocked = {
|
||||
evt,
|
||||
dt: evt.dataTransfer
|
||||
};
|
||||
evt.stopPropagation();
|
||||
el.dispatchEvent(new DragEvent("beforedragend", evt));
|
||||
} else {
|
||||
bcc.onmessage = () => {};
|
||||
}
|
||||
window.removeEventListener('dragend', blockHandler, true);
|
||||
}
|
||||
el.addEventListener('dragstart', onStart, binding.modifiers.capture);
|
||||
|
||||
function blockHandler(evt) {
|
||||
if (evt.dataTransfer.dropEffect == 'none')
|
||||
return unblockDragend();
|
||||
unblockDragend(evt);
|
||||
evt.stopPropagation();
|
||||
dragendWorker.port.postMessage(['request']);
|
||||
}
|
||||
el.addEventListener('dragend', onEnd, true);
|
||||
|
||||
dragendWorker.port.onmessage = e => {
|
||||
const [ func, ...args ] = e.data;
|
||||
if (func != 'fire')
|
||||
return;
|
||||
const [ targetId ] = args;
|
||||
if (targetId != id)
|
||||
return;
|
||||
if (evt === null)
|
||||
unblockDragend();
|
||||
else
|
||||
el.dispatchEvent(evt);
|
||||
}
|
||||
el.fhcDraggableCleanup = () => {
|
||||
el.removeEventListener('dragstart', onStart, binding.modifiers.capture);
|
||||
el.removeEventListener('dragend', onEnd, true);
|
||||
};
|
||||
},
|
||||
updated(el, binding) {
|
||||
updateValue(el, binding.value);
|
||||
updateEffectAllowed(el, binding.arg);
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
el.fhcDraggableCleanup();
|
||||
delete el.fhcDraggableCleanup;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getValidTransferData, eventHasTypes, dragendWorker } from '../helpers/DragAndDrop.js';
|
||||
import { getValidTransferData, eventHasTypes, bindDragEnterLeave } from '../helpers/DragAndDrop.js';
|
||||
|
||||
const EFFECTS = [
|
||||
'move',
|
||||
@@ -24,34 +24,56 @@ export default {
|
||||
const arg = binding.arg.replace(/(strict-|-strict)/, '');
|
||||
const effect = EFFECTS.includes(arg) ? arg : null;
|
||||
|
||||
const bcc = new BroadcastChannel('fhc-dnd');
|
||||
let allowed = false;
|
||||
|
||||
el.addEventListener('dragenter', evt => {
|
||||
function onEnter(evt) {
|
||||
allowed = eventHasTypes(evt, allowedTypes, strict);
|
||||
if (allowed)
|
||||
if (allowed) {
|
||||
evt.preventDefault();
|
||||
});
|
||||
el.addEventListener('dragover', evt => {
|
||||
bcc.postMessage('block');
|
||||
}
|
||||
}
|
||||
function onLeave(evt, wasDropped) {
|
||||
if (allowed && !wasDropped) {
|
||||
bcc.postMessage('release');
|
||||
}
|
||||
}
|
||||
function onOver(evt) {
|
||||
if (allowed) {
|
||||
evt.preventDefault();
|
||||
if (effect)
|
||||
evt.dataTransfer.dropEffect = effect;
|
||||
}
|
||||
});
|
||||
el.addEventListener('drop', evt => {
|
||||
}
|
||||
function onDrop(evt) {
|
||||
let result = getValidTransferData(evt, allowedTypes, strict);
|
||||
if (!Array.isArray(result) && !binding.modifiers[result.type] && allowedTypes.includes(result.type + '-collection'))
|
||||
result = [result];
|
||||
|
||||
const res = binding.value(evt, result);
|
||||
|
||||
if (res instanceof Promise) {
|
||||
const localId = id++;
|
||||
dragendWorker.port.postMessage(['block', localId]);
|
||||
res.then(r => {
|
||||
dragendWorker.port.postMessage(['unblock', localId]);
|
||||
bcc.postMessage('release');
|
||||
return r;
|
||||
});
|
||||
} else {
|
||||
bcc.postMessage('release');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const cleanupEnterLeave = bindDragEnterLeave(el, onEnter, onLeave);
|
||||
el.addEventListener('dragover', onOver);
|
||||
el.addEventListener('drop', onDrop);
|
||||
el.fhcDropCleanup = () => {
|
||||
cleanupEnterLeave();
|
||||
el.removeEventListener('dragover', onOver);
|
||||
el.removeEventListener('drop', onDrop);
|
||||
};
|
||||
},
|
||||
beforeUnmount(el) {
|
||||
el.fhcDropCleanup();
|
||||
delete el.fhcDropCleanup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
* TODO(chris): This is only a prototype!!!
|
||||
*/
|
||||
|
||||
const dragendWorker = new SharedWorker(new URL("../sharedworkers/dragend.js", import.meta.url));
|
||||
|
||||
const TYPE_DEFINITION = {
|
||||
lehreinheit: {
|
||||
id: "lehreinheit_id",
|
||||
@@ -119,12 +117,12 @@ function getTransferData(event, strict) {
|
||||
const result = [];
|
||||
|
||||
for (const type of event.dataTransfer.types) {
|
||||
if (type.substr(0, 4) != 'fhc/') {
|
||||
if (type.substr(0, 16) != 'application/fhc-') {
|
||||
if (strict)
|
||||
return null;
|
||||
continue;
|
||||
}
|
||||
let base_type = type.substr(4);
|
||||
let base_type = type.substr(16);
|
||||
let collection = false;
|
||||
if (base_type.substr(-11) == '-collection') {
|
||||
base_type = base_type.substr(0, base_type.length-11);
|
||||
@@ -251,7 +249,7 @@ function setTransferData(event, validDragObject, setDragImage = false) {
|
||||
return validDragObject.forEach(data => setTransferData(event, data));
|
||||
}
|
||||
|
||||
event.dataTransfer.setData('fhc/' + validDragObject.type, JSON.stringify(validDragObject));
|
||||
event.dataTransfer.setData('application/fhc-' + validDragObject.type, JSON.stringify(validDragObject));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,12 +264,85 @@ function setTransferData(event, validDragObject, setDragImage = false) {
|
||||
function eventHasTypes(event, allowedTypes, strict) {
|
||||
if (!allowedTypes || !allowedTypes.length)
|
||||
allowedTypes = VALID_TYPES;
|
||||
allowedTypes = allowedTypes.map(type => 'fhc/' + type);
|
||||
allowedTypes = allowedTypes.map(type => 'application/fhc-' + type);
|
||||
|
||||
const dataTypes = [...event.dataTransfer.types];
|
||||
|
||||
// NOTE(chris): if dragging across browsers the dataTransfer object is
|
||||
// set to a default one without data. Since we do not support dragging
|
||||
// across browsers (yet) we return false which will disallow dropping.
|
||||
if (!dataTypes.length)
|
||||
return false;
|
||||
|
||||
if (!strict)
|
||||
return allowedTypes.some(type => event.dataTransfer.types.includes(type));
|
||||
return allowedTypes.some(type => [...event.dataTransfer.types].includes(type));
|
||||
|
||||
return event.dataTransfer.types.every(type => allowedTypes.includes(type));
|
||||
return [...event.dataTransfer.types].every(type => allowedTypes.includes(type));
|
||||
}
|
||||
|
||||
function bindDragEnterLeave(el, onEnter, onLeave) {
|
||||
// NOTE(chris): add save dragenter and dragleave events
|
||||
// that won't fire when hovering over child elements
|
||||
|
||||
let skipLeave = false;
|
||||
let skipLeaveParent = true;
|
||||
|
||||
function init(evt) {
|
||||
skipLeave = false;
|
||||
skipLeaveParent = true;
|
||||
// add global listeners
|
||||
window.addEventListener('dragenter', globalDragenter, true);
|
||||
window.addEventListener('dragleave', globalDragleave, true);
|
||||
window.addEventListener('drop', globalDrop, true);
|
||||
// call enter
|
||||
onEnter(evt);
|
||||
// remove self
|
||||
el.removeEventListener('dragenter', init);
|
||||
}
|
||||
|
||||
function cleanup(evt, wasDropped) {
|
||||
// remove global listeners
|
||||
window.removeEventListener('dragenter', globalDragenter, true);
|
||||
window.removeEventListener('dragleave', globalDragleave, true);
|
||||
window.removeEventListener('drop', globalDrop, true);
|
||||
// call leave
|
||||
onLeave(evt, wasDropped);
|
||||
// add init
|
||||
el.addEventListener('dragenter', init);
|
||||
}
|
||||
|
||||
function globalDragenter(evt) {
|
||||
skipLeaveParent = false;
|
||||
if (el != evt.target && !el.contains(evt.target)) {
|
||||
cleanup(evt);
|
||||
} else {
|
||||
skipLeave = true;
|
||||
}
|
||||
}
|
||||
function globalDragleave(evt) {
|
||||
if (el != evt.target && !el.contains(evt.target)) {
|
||||
if (skipLeaveParent) {
|
||||
skipLeaveParent = false;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (skipLeave) {
|
||||
skipLeave = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
cleanup(evt);
|
||||
}
|
||||
function globalDrop(evt) {
|
||||
cleanup(evt, true);
|
||||
}
|
||||
|
||||
el.addEventListener('dragenter', init);
|
||||
|
||||
return () => {
|
||||
// cleanup
|
||||
el.removeEventListener('dragenter', init);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -282,7 +353,7 @@ export {
|
||||
convertToValidDragObject,
|
||||
setTransferData,
|
||||
eventHasTypes,
|
||||
dragendWorker
|
||||
bindDragEnterLeave
|
||||
};
|
||||
export default {
|
||||
...TYPE_CONSTANTS,
|
||||
@@ -293,5 +364,5 @@ export default {
|
||||
convertToValidDragObject,
|
||||
setTransferData,
|
||||
eventHasTypes,
|
||||
dragendWorker
|
||||
bindDragEnterLeave
|
||||
};
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
let dragEndCallback = null;
|
||||
|
||||
onconnect = e => {
|
||||
const port = e.ports[0];
|
||||
|
||||
const cbList = [];
|
||||
|
||||
port.onmessage = e => {
|
||||
const [ func, ...args ] = e.data;
|
||||
switch (func) {
|
||||
case 'init':
|
||||
dragEndCallback = () => {
|
||||
port.postMessage(['fire', args]);
|
||||
};
|
||||
break;
|
||||
case 'block':
|
||||
cbList[args[0]] = dragEndCallback;
|
||||
dragEndCallback = null;
|
||||
break;
|
||||
case 'unblock':
|
||||
cbList[args[0]]();
|
||||
break;
|
||||
case 'request':
|
||||
if (dragEndCallback) {
|
||||
dragEndCallback();
|
||||
dragEndCallback = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user