StV Groups Drag&Drop

This commit is contained in:
chfhtw
2025-09-10 11:18:23 +02:00
parent ff061a3e95
commit 455698b28e
11 changed files with 758 additions and 76 deletions
@@ -36,6 +36,7 @@ class Student extends FHCAPI_Controller
parent::__construct([
'get' => ['admin:r', 'assistenz:r'],
'save' => ['admin:rw', 'assistenz:rw'],
'saveStudent' => ['admin:rw', 'assistenz:rw'],
'check' => ['admin:rw', 'assistenz:rw'],
'add' => ['admin:rw', 'assistenz:rw'] // TODO(chris): extra permissions
]);
@@ -414,6 +415,31 @@ class Student extends FHCAPI_Controller
), ''));
}
/**
* Saves data to a prestudent using their student_uid
*
* @param string $student_uid
* @param string $studiensemester_kurzbz
* @return void
*/
public function saveStudent($student_uid, $studiensemester_kurzbz)
{
$this->load->model('crm/Student_model', 'StudentModel');
$result = $this->StudentModel->load([$student_uid]);
$data = $this->getDataOrTerminateWithError($result);
if (!$data)
show_404(); // No Student with that ID
$student = current($data);
$this->checkPermissionsForPrestudent($student->prestudent_id, ['admin:rw', 'assistenz:rw']);
return $this->save($student->prestudent_id, $studiensemester_kurzbz);
}
public function check()
{
$this->load->library('form_validation');
@@ -271,6 +271,7 @@ class Verband extends FHCAPI_Controller
$this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, verband, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester AND verband=v.verband ORDER BY gruppe LIMIT 1)) AS name", false);
$this->StudiengangModel->addSelect("CASE WHEN MAX(gruppe)='' OR MAX(gruppe)=' ' THEN TRUE ELSE FALSE END AS leaf");
$this->StudiengangModel->addSelect($this->StudiengangModel->escape($semester) . ' AS semester');
$this->StudiengangModel->addSelect('verband');
$this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false);
@@ -319,6 +320,8 @@ class Verband extends FHCAPI_Controller
$this->StudiengangModel->addSelect("CONCAT(UPPER(CONCAT(typ, kurzbz)), '-', semester, verband, gruppe, (SELECT CASE WHEN bezeichnung IS NULL OR bezeichnung='' THEN ''::TEXT ELSE CONCAT(' (', bezeichnung, ')') END FROM public.tbl_lehrverband WHERE studiengang_kz=v.studiengang_kz AND semester=v.semester AND verband=v.verband AND gruppe=v.gruppe ORDER BY gruppe LIMIT 1)) AS name", false);
$this->StudiengangModel->addSelect("TRUE AS leaf", false);
$this->StudiengangModel->addSelect('v.semester');
$this->StudiengangModel->addSelect('v.verband');
$this->StudiengangModel->addSelect('gruppe');
$this->StudiengangModel->addSelect($this->StudiengangModel->escape($studiengang_kz) . '::integer AS stg_kz', false);
+10
View File
@@ -35,4 +35,14 @@ export default {
params
};
},
saveStudent(student_uid, studiensemester_kurzbz, params) {
return {
method: 'post',
url: 'api/frontend/v1/stv/student/saveStudent/'
+ encodeURIComponent(student_uid)
+ '/'
+ encodeURIComponent(studiensemester_kurzbz),
params
};
}
};
@@ -96,8 +96,11 @@ export default {
},
methods: {
onDragstart(evt) {
DragAndDrop.setTransferData(evt.detail.originalEvent, evt.detail.item.orig);
this.draggedInternalEvent = evt.detail.item;
const data = DragAndDrop.convertToTransferData(evt.detail.item.orig);
if (DragAndDrop.isValidDragObject(data)) {
DragAndDrop.setTransferData(evt.detail.originalEvent, data);
this.draggedInternalEvent = evt.detail.item;
}
},
onDragend() {
this.draggedInternalEvent = null;
@@ -1,6 +1,7 @@
import {CoreFilterCmpt} from "../../filter/Filter.js";
import ListNew from './List/New.js';
import draggable from '../../../directives/draggable.js';
export default {
name: "ListPrestudents",
@@ -8,11 +9,18 @@ export default {
CoreFilterCmpt,
ListNew
},
directives: {
draggable
},
inject: {
'lists': {
lists: {
from: 'lists',
required: true
},
$reloadList: {
from: '$reloadList',
required: true
},
currentSemester: {
from: 'currentSemester',
required: true
@@ -165,6 +173,39 @@ export default {
currentEndpointRawUrl: ''
}
},
computed: {
countsToHTML: function() {
return this.$p.t('global/ausgewaehlt')
+ ': <strong>' + (this.selectedcount || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gefiltert')
+ ': '
+ '<strong>' + (this.filteredcount || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gesamt')
+ ': <strong>' + (this.count || 0) + '</strong>';
},
selectedDragObject() {
return this.selected.map(item => {
let type, id;
if (item.uid) {
type = 'student';
id = item.uid;
} else if (item.prestudent_id) {
type = 'prestudent';
id = item.prestudent_id;
} else if (item.person_id) {
type = 'person';
id = item.person_id;
}
return {
...item,
type,
id
};
});
}
},
methods: {
reload() {
this.$refs.table.reloadTable();
@@ -172,10 +213,19 @@ export default {
actionNewPrestudent() {
this.$refs.new.open();
},
rowSelectionChanged(data) {
rowSelectionChanged(data, rows) {
this.selectedcount = data.length;
this.lastSelected = this.selected;
this.$emit('update:selected', data);
// set selected elements draggable
const tableEl = this.$refs.table?.$refs?.table;
if (tableEl) {
const oldDragables = tableEl.querySelectorAll('[draggable]');
for (const draggable of oldDragables)
draggable.removeAttribute('draggable');
}
rows.forEach(row => row.getElement().draggable = true);
},
autoSelectRows(data) {
if (this.lastSelected) {
@@ -294,26 +344,27 @@ export default {
if (el != this.focusObj)
this.changeFocus(this.focusObj, el);
}
}
},
computed: {
countsToHTML: function() {
return this.$p.t('global/ausgewaehlt')
+ ': <strong>' + (this.selectedcount || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gefiltert')
+ ': '
+ '<strong>' + (this.filteredcount || 0) + '</strong>'
+ ' | '
+ this.$p.t('global/gesamt')
+ ': <strong>' + (this.count || 0) + '</strong>';
},
dragCleanup(evt) {
if (evt.dataTransfer.dropEffect == 'none')
return; // aborted or wrong target
this.$reloadList();
}
},
// TODO(chris): focusin, focusout, keydown and tabindex should be in the filter component
// TODO(chris): filter component column chooser has no accessibilty features
template: `
<div class="stv-list h-100 pt-3">
<div class="tabulator-container d-flex flex-column h-100" :class="{'has-filter': filterKontoCount0 || filterKontoMissingCounter}" tabindex="0" @focusin="onFocus" @keydown="onKeydown">
<div
class="tabulator-container d-flex flex-column h-100"
:class="{'has-filter': filterKontoCount0 || filterKontoMissingCounter}"
tabindex="0"
@focusin="onFocus"
@keydown="onKeydown"
v-draggable:copyLink.capture="selectedDragObject"
@dragend="dragCleanup"
>
<core-filter-cmpt
ref="table"
:description="countsToHTML"
@@ -1,10 +1,28 @@
import ApiStvVerband from '../../../api/factory/stv/verband.js';
import drop from '../../../directives/drop.js';
import dragClick from '../../../directives/dragClick.js';
import ApiStvGroups from '../../../api/factory/stv/group.js';
import ApiStvDetails from '../../../api/factory/stv/details.js';
export default {
components: {
PvTreetable: primevue.treetable,
PvColumn: primevue.column
},
directives: {
drop,
dragClick
},
inject: {
$reloadList: {
from: '$reloadList',
required: true
},
currentSemester: {
from: 'currentSemester',
required: true
}
},
emits: [
'selectVerband'
],
@@ -207,6 +225,51 @@ export default {
this.selectedKey = {[currentNode.key]: true};
this.onSelectTreeNode(currentNode);
},
async toggleTreeNode(node) {
if (this.expandedKeys[node.key]) {
delete this.expandedKeys[node.key];
} else if (!node.leaf) {
await this.onExpandTreeNode(node);
this.expandedKeys[node.key] = true;
}
},
getStudentAjaxId(student) {
let res = student.id;
if (student.vorname && student.nachname)
res += ' (' + student.vorname + ' ' + student.nachname + ')';
return res;
},
dropStudents(node, students, evt) {
const data = node.data;
let endpoint;
if (data.gruppe_kurzbz) {
endpoint = students.map(student => [
this.getStudentAjaxId(student),
ApiStvGroups.add(
student.id,
data.gruppe_kurzbz,
this.currentSemester
)
]);
} else {
const { semester, verband, gruppe } = data;
const params = { semester, verband, gruppe };
endpoint = students.map(student => [
this.getStudentAjaxId(student),
ApiStvDetails.saveStudent(
student.id,
this.currentSemester,
params
)
]);
}
return this.$api
.call(endpoint)
.then(this.$reloadList)
.catch(this.$fhcAlert.handleSystemError);
}
},
mounted() {
@@ -268,10 +331,21 @@ export default {
</template>
<template #body="{ node }">
<span
v-if="['semester', 'verband', 'gruppe', 'gruppe_kurzbz'].some(key => node.data.hasOwnProperty(key))"
:data-tree-item-key="node.key"
:title="node.data.studiengang_kz"
v-drag-click="() => toggleTreeNode(node)"
v-drop:link-strict.student-collection="(evt, students) => dropStudents(node, students, evt)"
>
{{node.data.name}}
{{ node.data.name }}
</span>
<span
v-else
:data-tree-item-key="node.key"
:title="node.data.studiengang_kz"
v-drag-click="() => toggleTreeNode(node)"
>
{{ node.data.name }}
</span>
</template>
</pv-column>
+101
View File
@@ -0,0 +1,101 @@
export default {
mounted(el, binding) {
const delay = parseInt(binding.arg) || 300;
let timeout = null;
function startCountdown() {
timeout = window.setTimeout(binding.value, delay);
}
function stopCountdown() {
if (timeout)
window.clearTimeout(timeout);
timeout = null;
}
function onEnter(evt) {
let lastTarget = evt.target;
let lastX = evt.offsetX;
let 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();
}
});
startCountdown();
}
function onLeave() {
stopCountdown();
}
// 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;
},
beforeUnmount(el) {
el.removeEventListener('dragenter', el.initFunc);
delete el.initFunc;
}
}
+97
View File
@@ -0,0 +1,97 @@
import { setTransferData, convertToValidDragObject, dragendWorker } from '../helpers/DragAndDrop.js';
const EFFECTS = [
'none',
'copy',
'copyLink',
'copyMove',
'link',
'linkMove',
'move',
'all',
'uninitialized'
];
export default {
mounted(el, binding, vnode) {
updateValue(el, binding.value);
updateEffectAllowed(el, binding.arg);
// if modifier capture is set we assume it's on a parent element
// i.e: for dragging multiple elements
// otherwise set draggable attribute
if (!binding.modifiers.capture) {
el.draggable = true;
}
el.addEventListener('dragstart', 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();
} 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;
}
window.removeEventListener('dragend', blockHandler, true);
}
function blockHandler(evt) {
if (evt.dataTransfer.dropEffect == 'none')
return unblockDragend();
unblockDragend(evt);
evt.stopPropagation();
dragendWorker.port.postMessage(['request']);
}
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);
}
},
updated(el, binding) {
updateValue(el, binding.value);
updateEffectAllowed(el, binding.arg);
}
}
// Helper functions
function updateValue(el, value) {
value = convertToValidDragObject(value);
if (value) {
el.dataset.fhcDraggableValue = JSON.stringify(value);
} else if (el.dataset.fhcDraggableValue) {
delete el.dataset.fhcDraggableValue;
}
}
function updateEffectAllowed(el, effectAllowed) {
if (effectAllowed && EFFECTS.includes(effectAllowed)) {
el.dataset.fhcEffectAllowed = effectAllowed;
} else if (el.dataset.fhcEffectAllowed) {
delete el.dataset.fhcEffectAllowed;
}
}
+57
View File
@@ -0,0 +1,57 @@
import { getValidTransferData, eventHasTypes, dragendWorker } from '../helpers/DragAndDrop.js';
const EFFECTS = [
'move',
'copy',
'link',
'none'
];
let id = 0;
export default {
mounted(el, binding, vnode) {
const allowedTypes = Object.keys(binding.modifiers);
allowedTypes.forEach(type => {
if (type.substr(-11) == '-collection') {
const singleType = type.substr(0, type.length-11);
if (!allowedTypes.includes(singleType))
allowedTypes.push(singleType);
}
});
const strict = binding.arg.match(/(strict-|-strict)/);
const arg = binding.arg.replace(/(strict-|-strict)/, '');
const effect = EFFECTS.includes(arg) ? arg : null;
let allowed = false;
el.addEventListener('dragenter', evt => {
allowed = eventHasTypes(evt, allowedTypes, strict);
if (allowed)
evt.preventDefault();
});
el.addEventListener('dragover', evt => {
if (allowed) {
evt.preventDefault();
if (effect)
evt.dataTransfer.dropEffect = effect;
}
});
el.addEventListener('drop', 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]);
return r;
});
}
});
}
}
+286 -56
View File
@@ -1,67 +1,297 @@
/**
* TODO(chris): This is only a prototype!!!
*/
const DragAndDrop = {
TYPE_LE: "lehreinheit",
TYPE_VEVENT: "vevent",
getValidTransferData(event, allowedTypes) {
const json = event.dataTransfer.getData('text');
let obj;
try {
obj = JSON.parse(json);
if (!obj.type)
return null;
if (allowedTypes && !allowedTypes.includes(obj.type))
return null;
} catch (error) {
return null;
}
return obj;
const dragendWorker = new SharedWorker(new URL("../sharedworkers/dragend.js", import.meta.url));
const TYPE_DEFINITION = {
lehreinheit: {
id: "lehreinheit_id",
dragIcon: "fa-solid fa-chalkboard-user",
extras: [
"stundenblockung"
]
},
isValidTransferData(event, allowedTypes) {
return this.getValidTransferData(event, allowedTypes) ? true : false;
vevent: {
id: "uid",
dragIcon: "fa-solid fa-calendar",
extras: [
"dtstart",
"dtend",
"summary"
]
},
getTransferData(event) {
const json = event.dataTransfer.getData('text');
return JSON.parse(json);
person: {
id: "person_id",
dragIcon: "fa-solid fa-user"
},
setTransferData(event, data) {
switch (data.type) {
case DragAndDrop.TYPE_LE:
data = DragAndDrop.fromLe(data);
break;
default:
if (data.dtstart && data.dtend && data.uid && data.summary) {
data = DragAndDrop.fromVEvent(data);
break;
}
return false; // No type found => abort
}
event.dataTransfer.setData('text', JSON.stringify(data));
return true;
student: {
id: "student_uid",
dragIcon: "fa-solid fa-user-graduate"
},
fromLe(data) {
const {
type = DragAndDrop.TYPE_LE,
lehreinheit_id: id,
stundenblockung
} = data;
return { type, id, stundenblockung };
},
fromVEvent(data) {
const {
type = DragAndDrop.TYPE_VEVENT,
uid: id,
dtstart,
dtend,
summary
} = data;
return { type, id, dtstart, dtend, summary };
prestudent: {
id: "prestudent_id",
dragIcon: "fa-solid fa-user-graduate text-muted"
}
// TODO: IMPLEMENT OTHER TYPES
};
export default DragAndDrop;
const VALID_TYPES = Object.keys(TYPE_DEFINITION);
const TYPE_CONSTANTS = Object.keys(TYPE_DEFINITION).reduce((res, type) => {
res['TYPE_' + type.toUpperCase()] = type;
return res;
}, {});
function isValidDragObject(value) {
if (!value)
return false;
if (Array.isArray(value))
return value.every(isValidDragObject);
if (!value.type)
return false;
if (value.type.substr(-11) == '-collection') {
if (!value.hasOwnProperty('values'))
return false;
if (!VALID_TYPES.includes(value.type.substr(0, value.type.length-11)))
return false;
} else {
if (!value.hasOwnProperty('id'))
return false;
if (!VALID_TYPES.includes(value.type))
return false;
if (TYPE_DEFINITION[value.type].extras) {
if (!TYPE_DEFINITION[value.type].extras.every(extra => value.hasOwnProperty(extra)))
return false;
}
}
return true;
}
function getValidTransferData(event, allowedTypes, strict) {
let obj = null;
try {
obj = getTransferData(event, strict);
if (!obj)
return null;
if (!strict && Array.isArray(obj)) {
obj = obj.filter(isValidDragObject);
if (!obj.length)
return null;
} else if (!isValidDragObject(obj))
return null;
if (allowedTypes && allowedTypes.length) {
if (Array.isArray(obj)) {
if (strict && !obj.every(v => allowedTypes.includes(v.type))) {
return null;
} else if (!strict) {
obj = obj.filter(v => allowedTypes.includes(v.type));
if (!obj.length)
return null;
}
} else if (!allowedTypes.includes(obj.type)) {
return null;
}
}
} catch(error) {
return null;
}
if (Array.isArray(obj) && obj.length == 1)
return obj.find(Boolean);
return obj;
}
function isValidTransferData(event, allowedTypes, strict) {
return getValidTransferData(event, allowedTypes, strict) ? true : false;
}
function getTransferData(event, strict) {
const result = [];
for (const type of event.dataTransfer.types) {
if (type.substr(0, 4) != 'fhc/') {
if (strict)
return null;
continue;
}
let base_type = type.substr(4);
let collection = false;
if (base_type.substr(-11) == '-collection') {
base_type = base_type.substr(0, base_type.length-11);
collection = true;
}
if (!VALID_TYPES.includes(base_type)) {
if (strict)
return null;
continue;
}
let data = JSON.parse(event.dataTransfer.getData(type));
if (collection)
result.push(...data.values);
else
result.push(data);
}
if (!result.length)
return null;
if (result.length == 1)
return result[0];
return result;
}
function convertToValidDragObject(data, strict) {
if (Array.isArray(data)) {
const converted = data.map(convertToValidDragObject).filter(Boolean);
if (!converted.length)
return undefined;
if (strict && converted.length != data.length)
return undefined;
const sorted = converted.reduce((res, item) => {
if (!res[item.type])
res[item.type] = [];
res[item.type].push(item);
return res;
}, {});
return Object.entries(sorted).map(([type, values]) => {
if (values.length > 1) {
return {
type: type + '-collection',
values
};
}
return values[0];
});
}
if (data.hasOwnProperty('type') && isValidDragObject(data)) {
return data;
}
const found = Object.entries(TYPE_DEFINITION).find(([type, typedef]) => {
if (!data.hasOwnProperty(typedef.id))
return false;
if (typedef.extras) {
if (!typedef.extras.every(extra => data.hasOwnProperty("extra")))
return false;
}
return true;
});
if (!found) {
return undefined;
}
const [ type, typedef ] = found;
const newData = {};
newData.type = type;
newData.id = data[typedef.id];
if (typedef.extras)
typedef.extras.forEach(extra => newData[extras] = data[extra]);
return newData;
}
function setTransferData(event, validDragObject, setDragImage = false) {
if (setDragImage) {
const dragItems = Array.isArray(validDragObject) ? validDragObject : [ validDragObject ];
const dragElements = dragItems.map(item => {
const icon = document.createElement('i');
const label = document.createElement('span');
const iconContainer = document.createElement('span');
iconContainer.className = 'btn btn-outline-dark bg-light';
label.className = 'small';
if (TYPE_DEFINITION[item.type]) {
icon.className = TYPE_DEFINITION[item.type].dragIcon || 'fa-solid fa-question';
label.textContent = item.id;
} else if (item.type.substr(-11) == '-collection' && TYPE_DEFINITION[item.type.substr(0, item.type.length-11)]) {
iconContainer.style.boxShadow = '3px 3px var(--bs-btn-border-color)';
icon.className = TYPE_DEFINITION[item.type.substr(0, item.type.length-11)].dragIcon || 'fa-solid fa-question';
label.textContent = 'x' + item.values.length;
} else {
icon.className = 'fa-solid fa-question';
label.textContent = item.id || '';
}
iconContainer.append(icon);
const itemContainer = document.createElement('div');
itemContainer.className = 'd-flex flex-column align-items-center gap-2 small';
itemContainer.append(iconContainer, label);
return itemContainer;
});
const container = document.createElement('div');
container.className = 'd-flex flex-row gap-2 small';
container.append(...dragElements);
document.body.append(container);
event.dataTransfer.setDragImage(container, -25, 0);
requestAnimationFrame(() => {
document.body.removeChild(container);
});
}
if (Array.isArray(validDragObject)) {
return validDragObject.forEach(data => setTransferData(event, data));
}
event.dataTransfer.setData('fhc/' + validDragObject.type, JSON.stringify(validDragObject));
}
/**
* check if the dataTransfer types are in the allowed types array
* if strict is disabled at least one type must be the allowed array
* otherwise all types have to be in the allowed array
*
* @param Event event
* @param Array allowedTypes
* @param Boolean strict
*/
function eventHasTypes(event, allowedTypes, strict) {
if (!allowedTypes || !allowedTypes.length)
allowedTypes = VALID_TYPES;
allowedTypes = allowedTypes.map(type => 'fhc/' + type);
if (!strict)
return allowedTypes.some(type => event.dataTransfer.types.includes(type));
return event.dataTransfer.types.every(type => allowedTypes.includes(type));
}
export {
isValidDragObject,
getValidTransferData,
isValidTransferData,
getTransferData,
convertToValidDragObject,
setTransferData,
eventHasTypes,
dragendWorker
};
export default {
...TYPE_CONSTANTS,
isValidDragObject,
getValidTransferData,
isValidTransferData,
getTransferData,
convertToValidDragObject,
setTransferData,
eventHasTypes,
dragendWorker
};
+30
View File
@@ -0,0 +1,30 @@
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;
}
}
};
};