This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

168
resources/app/client/ui/dragdrop.js vendored Normal file
View File

@@ -0,0 +1,168 @@
/**
* @typedef {object} DragDropConfiguration
* @property {string} dragSelector The CSS selector used to target draggable elements.
* @property {string} dropSelector The CSS selector used to target viable drop targets.
* @property {Record<string,Function>} permissions An object of permission test functions for each action
* @property {Record<string,Function>} callbacks An object of callback functions for each action
*/
/**
* A controller class for managing drag and drop workflows within an Application instance.
* The controller manages the following actions: dragstart, dragover, drop
* @see {@link Application}
*
* @param {DragDropConfiguration}
* @example Activate drag-and-drop handling for a certain set of elements
* ```js
* const dragDrop = new DragDrop({
* dragSelector: ".item",
* dropSelector: ".items",
* permissions: { dragstart: this._canDragStart.bind(this), drop: this._canDragDrop.bind(this) },
* callbacks: { dragstart: this._onDragStart.bind(this), drop: this._onDragDrop.bind(this) }
* });
* dragDrop.bind(html);
* ```
*/
class DragDrop {
constructor({dragSelector, dropSelector, permissions={}, callbacks={}} = {}) {
/**
* The HTML selector which identifies draggable elements
* @type {string}
*/
this.dragSelector = dragSelector;
/**
* The HTML selector which identifies drop targets
* @type {string}
*/
this.dropSelector = dropSelector;
/**
* A set of permission checking functions for each action of the Drag and Drop workflow
* @type {Object}
*/
this.permissions = permissions;
/**
* A set of callback functions for each action of the Drag and Drop workflow
* @type {Object}
*/
this.callbacks = callbacks;
}
/* -------------------------------------------- */
/**
* Bind the DragDrop controller to an HTML application
* @param {HTMLElement} html The HTML element to which the handler is bound
*/
bind(html) {
// Identify and activate draggable targets
if ( this.can("dragstart", this.dragSelector) ) {
const draggables = html.querySelectorAll(this.dragSelector);
for (let el of draggables) {
el.setAttribute("draggable", true);
el.ondragstart = this._handleDragStart.bind(this);
}
}
// Identify and activate drop targets
if ( this.can("drop", this.dropSelector) ) {
const droppables = !this.dropSelector || html.matches(this.dropSelector) ? [html] :
html.querySelectorAll(this.dropSelector);
for ( let el of droppables ) {
el.ondragover = this._handleDragOver.bind(this);
el.ondrop = this._handleDrop.bind(this);
}
}
return this;
}
/* -------------------------------------------- */
/**
* Execute a callback function associated with a certain action in the workflow
* @param {DragEvent} event The drag event being handled
* @param {string} action The action being attempted
*/
callback(event, action) {
const fn = this.callbacks[action];
if ( fn instanceof Function ) return fn(event);
}
/* -------------------------------------------- */
/**
* Test whether the current user has permission to perform a step of the workflow
* @param {string} action The action being attempted
* @param {string} selector The selector being targeted
* @return {boolean} Can the action be performed?
*/
can(action, selector) {
const fn = this.permissions[action];
if ( fn instanceof Function ) return fn(selector);
return true;
}
/* -------------------------------------------- */
/**
* Handle the start of a drag workflow
* @param {DragEvent} event The drag event being handled
* @private
*/
_handleDragStart(event) {
this.callback(event, "dragstart");
if ( event.dataTransfer.items.length ) event.stopPropagation();
}
/* -------------------------------------------- */
/**
* Handle a dragged element over a droppable target
* @param {DragEvent} event The drag event being handled
* @private
*/
_handleDragOver(event) {
event.preventDefault();
this.callback(event, "dragover");
return false;
}
/* -------------------------------------------- */
/**
* Handle a dragged element dropped on a droppable target
* @param {DragEvent} event The drag event being handled
* @private
*/
_handleDrop(event) {
event.preventDefault();
return this.callback(event, "drop");
}
/* -------------------------------------------- */
static createDragImage(img, width, height) {
let div = document.getElementById("drag-preview");
// Create the drag preview div
if ( !div ) {
div = document.createElement("div");
div.setAttribute("id", "drag-preview");
const img = document.createElement("img");
img.classList.add("noborder");
div.appendChild(img);
document.body.appendChild(div);
}
// Add the preview image
const i = div.children[0];
i.src = img.src;
i.width = width;
i.height = height;
return div;
}
}