mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
support drag and drop for playlist items
This commit is contained in:
parent
80929558e6
commit
fd64c014a3
45 changed files with 4202 additions and 3 deletions
585
dashboard-ui/bower_components/dragula.js/dragula.js
vendored
Normal file
585
dashboard-ui/bower_components/dragula.js/dragula.js
vendored
Normal file
|
@ -0,0 +1,585 @@
|
|||
'use strict';
|
||||
|
||||
var emitter = require('contra/emitter');
|
||||
var crossvent = require('crossvent');
|
||||
var classes = require('./classes');
|
||||
|
||||
function dragula (initialContainers, options) {
|
||||
var len = arguments.length;
|
||||
if (len === 1 && Array.isArray(initialContainers) === false) {
|
||||
options = initialContainers;
|
||||
initialContainers = [];
|
||||
}
|
||||
var body = document.body;
|
||||
var documentElement = document.documentElement;
|
||||
var _mirror; // mirror image
|
||||
var _source; // source container
|
||||
var _item; // item being dragged
|
||||
var _offsetX; // reference x
|
||||
var _offsetY; // reference y
|
||||
var _moveX; // reference move x
|
||||
var _moveY; // reference move y
|
||||
var _initialSibling; // reference sibling when grabbed
|
||||
var _currentSibling; // reference sibling now
|
||||
var _copy; // item used for copying
|
||||
var _renderTimer; // timer for setTimeout renderMirrorImage
|
||||
var _lastDropTarget = null; // last container item was over
|
||||
var _grabbed; // holds mousedown context until first mousemove
|
||||
|
||||
var o = options || {};
|
||||
if (o.moves === void 0) { o.moves = always; }
|
||||
if (o.accepts === void 0) { o.accepts = always; }
|
||||
if (o.invalid === void 0) { o.invalid = invalidTarget; }
|
||||
if (o.containers === void 0) { o.containers = initialContainers || []; }
|
||||
if (o.isContainer === void 0) { o.isContainer = never; }
|
||||
if (o.copy === void 0) { o.copy = false; }
|
||||
if (o.copySortSource === void 0) { o.copySortSource = false; }
|
||||
if (o.revertOnSpill === void 0) { o.revertOnSpill = false; }
|
||||
if (o.removeOnSpill === void 0) { o.removeOnSpill = false; }
|
||||
if (o.direction === void 0) { o.direction = 'vertical'; }
|
||||
if (o.ignoreInputTextSelection === void 0) { o.ignoreInputTextSelection = true; }
|
||||
if (o.mirrorContainer === void 0) { o.mirrorContainer = body; }
|
||||
|
||||
var drake = emitter({
|
||||
containers: o.containers,
|
||||
start: manualStart,
|
||||
end: end,
|
||||
cancel: cancel,
|
||||
remove: remove,
|
||||
destroy: destroy,
|
||||
dragging: false
|
||||
});
|
||||
|
||||
if (o.removeOnSpill === true) {
|
||||
drake.on('over', spillOver).on('out', spillOut);
|
||||
}
|
||||
|
||||
events();
|
||||
|
||||
return drake;
|
||||
|
||||
function isContainer (el) {
|
||||
return drake.containers.indexOf(el) !== -1 || o.isContainer(el);
|
||||
}
|
||||
|
||||
function events (remove) {
|
||||
var op = remove ? 'remove' : 'add';
|
||||
touchy(documentElement, op, 'mousedown', grab);
|
||||
touchy(documentElement, op, 'mouseup', release);
|
||||
}
|
||||
|
||||
function eventualMovements (remove) {
|
||||
var op = remove ? 'remove' : 'add';
|
||||
touchy(documentElement, op, 'mousemove', startBecauseMouseMoved);
|
||||
}
|
||||
|
||||
function movements (remove) {
|
||||
var op = remove ? 'remove' : 'add';
|
||||
touchy(documentElement, op, 'selectstart', preventGrabbed); // IE8
|
||||
touchy(documentElement, op, 'click', preventGrabbed);
|
||||
}
|
||||
|
||||
function destroy () {
|
||||
events(true);
|
||||
release({});
|
||||
}
|
||||
|
||||
function preventGrabbed (e) {
|
||||
if (_grabbed) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function grab (e) {
|
||||
_moveX = e.clientX;
|
||||
_moveY = e.clientY;
|
||||
|
||||
var ignore = whichMouseButton(e) !== 1 || e.metaKey || e.ctrlKey;
|
||||
if (ignore) {
|
||||
return; // we only care about honest-to-god left clicks and touch events
|
||||
}
|
||||
var item = e.target;
|
||||
var context = canStart(item);
|
||||
if (!context) {
|
||||
return;
|
||||
}
|
||||
_grabbed = context;
|
||||
eventualMovements();
|
||||
if (e.type === 'mousedown') {
|
||||
if (isInput(item)) { // see also: https://github.com/bevacqua/dragula/issues/208
|
||||
item.focus(); // fixes https://github.com/bevacqua/dragula/issues/176
|
||||
} else {
|
||||
e.preventDefault(); // fixes https://github.com/bevacqua/dragula/issues/155
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startBecauseMouseMoved (e) {
|
||||
if (!_grabbed) {
|
||||
return;
|
||||
}
|
||||
if (whichMouseButton(e) === 0) {
|
||||
release({});
|
||||
return; // when text is selected on an input and then dragged, mouseup doesn't fire. this is our only hope
|
||||
}
|
||||
if (e.clientX === _moveX && e.clientY === _moveY) {
|
||||
return;
|
||||
}
|
||||
if (o.ignoreInputTextSelection) {
|
||||
var clientX = getCoord('clientX', e);
|
||||
var clientY = getCoord('clientY', e);
|
||||
var elementBehindCursor = document.elementFromPoint(clientX, clientY);
|
||||
if (isInput(elementBehindCursor)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var grabbed = _grabbed; // call to end() unsets _grabbed
|
||||
eventualMovements(true);
|
||||
movements();
|
||||
end();
|
||||
start(grabbed);
|
||||
|
||||
var offset = getOffset(_item);
|
||||
_offsetX = getCoord('pageX', e) - offset.left;
|
||||
_offsetY = getCoord('pageY', e) - offset.top;
|
||||
|
||||
classes.add(_copy || _item, 'gu-transit');
|
||||
renderMirrorImage();
|
||||
drag(e);
|
||||
}
|
||||
|
||||
function canStart (item) {
|
||||
if (drake.dragging && _mirror) {
|
||||
return;
|
||||
}
|
||||
if (isContainer(item)) {
|
||||
return; // don't drag container itself
|
||||
}
|
||||
var handle = item;
|
||||
while (item.parentElement && isContainer(item.parentElement) === false) {
|
||||
if (o.invalid(item, handle)) {
|
||||
return;
|
||||
}
|
||||
item = item.parentElement; // drag target should be a top element
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var source = item.parentElement;
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
if (o.invalid(item, handle)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var movable = o.moves(item, source, handle, nextEl(item));
|
||||
if (!movable) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
item: item,
|
||||
source: source
|
||||
};
|
||||
}
|
||||
|
||||
function manualStart (item) {
|
||||
var context = canStart(item);
|
||||
if (context) {
|
||||
start(context);
|
||||
}
|
||||
}
|
||||
|
||||
function start (context) {
|
||||
if (isCopy(context.item, context.source)) {
|
||||
_copy = context.item.cloneNode(true);
|
||||
drake.emit('cloned', _copy, context.item, 'copy');
|
||||
}
|
||||
|
||||
_source = context.source;
|
||||
_item = context.item;
|
||||
_initialSibling = _currentSibling = nextEl(context.item);
|
||||
|
||||
drake.dragging = true;
|
||||
drake.emit('drag', _item, _source);
|
||||
}
|
||||
|
||||
function invalidTarget () {
|
||||
return false;
|
||||
}
|
||||
|
||||
function end () {
|
||||
if (!drake.dragging) {
|
||||
return;
|
||||
}
|
||||
var item = _copy || _item;
|
||||
drop(item, item.parentElement);
|
||||
}
|
||||
|
||||
function ungrab () {
|
||||
_grabbed = false;
|
||||
eventualMovements(true);
|
||||
movements(true);
|
||||
}
|
||||
|
||||
function release (e) {
|
||||
ungrab();
|
||||
|
||||
if (!drake.dragging) {
|
||||
return;
|
||||
}
|
||||
var item = _copy || _item;
|
||||
var clientX = getCoord('clientX', e);
|
||||
var clientY = getCoord('clientY', e);
|
||||
var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
|
||||
var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
|
||||
if (dropTarget && ((_copy && o.copySortSource) || (!_copy || dropTarget !== _source))) {
|
||||
drop(item, dropTarget);
|
||||
} else if (o.removeOnSpill) {
|
||||
remove();
|
||||
} else {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
function drop (item, target) {
|
||||
var parent = item.parentElement;
|
||||
if (_copy && o.copySortSource && target === _source) {
|
||||
parent.removeChild(_item);
|
||||
}
|
||||
if (isInitialPlacement(target)) {
|
||||
drake.emit('cancel', item, _source, _source);
|
||||
} else {
|
||||
drake.emit('drop', item, target, _source, _currentSibling);
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
|
||||
function remove () {
|
||||
if (!drake.dragging) {
|
||||
return;
|
||||
}
|
||||
var item = _copy || _item;
|
||||
var parent = item.parentElement;
|
||||
if (parent) {
|
||||
parent.removeChild(item);
|
||||
}
|
||||
drake.emit(_copy ? 'cancel' : 'remove', item, parent, _source);
|
||||
cleanup();
|
||||
}
|
||||
|
||||
function cancel (revert) {
|
||||
if (!drake.dragging) {
|
||||
return;
|
||||
}
|
||||
var reverts = arguments.length > 0 ? revert : o.revertOnSpill;
|
||||
var item = _copy || _item;
|
||||
var parent = item.parentElement;
|
||||
if (parent === _source && _copy) {
|
||||
parent.removeChild(_copy);
|
||||
}
|
||||
var initial = isInitialPlacement(parent);
|
||||
if (initial === false && !_copy && reverts) {
|
||||
_source.insertBefore(item, _initialSibling);
|
||||
}
|
||||
if (initial || reverts) {
|
||||
drake.emit('cancel', item, _source, _source);
|
||||
} else {
|
||||
drake.emit('drop', item, parent, _source, _currentSibling);
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
|
||||
function cleanup () {
|
||||
var item = _copy || _item;
|
||||
ungrab();
|
||||
removeMirrorImage();
|
||||
if (item) {
|
||||
classes.rm(item, 'gu-transit');
|
||||
}
|
||||
if (_renderTimer) {
|
||||
clearTimeout(_renderTimer);
|
||||
}
|
||||
drake.dragging = false;
|
||||
if (_lastDropTarget) {
|
||||
drake.emit('out', item, _lastDropTarget, _source);
|
||||
}
|
||||
drake.emit('dragend', item);
|
||||
_source = _item = _copy = _initialSibling = _currentSibling = _renderTimer = _lastDropTarget = null;
|
||||
}
|
||||
|
||||
function isInitialPlacement (target, s) {
|
||||
var sibling;
|
||||
if (s !== void 0) {
|
||||
sibling = s;
|
||||
} else if (_mirror) {
|
||||
sibling = _currentSibling;
|
||||
} else {
|
||||
sibling = nextEl(_copy || _item);
|
||||
}
|
||||
return target === _source && sibling === _initialSibling;
|
||||
}
|
||||
|
||||
function findDropTarget (elementBehindCursor, clientX, clientY) {
|
||||
var target = elementBehindCursor;
|
||||
while (target && !accepted()) {
|
||||
target = target.parentElement;
|
||||
}
|
||||
return target;
|
||||
|
||||
function accepted () {
|
||||
var droppable = isContainer(target);
|
||||
if (droppable === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var immediate = getImmediateChild(target, elementBehindCursor);
|
||||
var reference = getReference(target, immediate, clientX, clientY);
|
||||
var initial = isInitialPlacement(target, reference);
|
||||
if (initial) {
|
||||
return true; // should always be able to drop it right back where it was
|
||||
}
|
||||
return o.accepts(_item, target, _source, reference);
|
||||
}
|
||||
}
|
||||
|
||||
function drag (e) {
|
||||
if (!_mirror) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
|
||||
var clientX = getCoord('clientX', e);
|
||||
var clientY = getCoord('clientY', e);
|
||||
var x = clientX - _offsetX;
|
||||
var y = clientY - _offsetY;
|
||||
|
||||
_mirror.style.left = x + 'px';
|
||||
_mirror.style.top = y + 'px';
|
||||
|
||||
var item = _copy || _item;
|
||||
var elementBehindCursor = getElementBehindPoint(_mirror, clientX, clientY);
|
||||
var dropTarget = findDropTarget(elementBehindCursor, clientX, clientY);
|
||||
var changed = dropTarget !== null && dropTarget !== _lastDropTarget;
|
||||
if (changed || dropTarget === null) {
|
||||
out();
|
||||
_lastDropTarget = dropTarget;
|
||||
over();
|
||||
}
|
||||
if (dropTarget === _source && _copy && !o.copySortSource) {
|
||||
if (item.parentElement) {
|
||||
item.parentElement.removeChild(item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var reference;
|
||||
var immediate = getImmediateChild(dropTarget, elementBehindCursor);
|
||||
if (immediate !== null) {
|
||||
reference = getReference(dropTarget, immediate, clientX, clientY);
|
||||
} else if (o.revertOnSpill === true && !_copy) {
|
||||
reference = _initialSibling;
|
||||
dropTarget = _source;
|
||||
} else {
|
||||
if (_copy && item.parentElement) {
|
||||
item.parentElement.removeChild(item);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
reference === null ||
|
||||
reference !== item &&
|
||||
reference !== nextEl(item) &&
|
||||
reference !== _currentSibling
|
||||
) {
|
||||
_currentSibling = reference;
|
||||
dropTarget.insertBefore(item, reference);
|
||||
drake.emit('shadow', item, dropTarget, _source);
|
||||
}
|
||||
function moved (type) { drake.emit(type, item, _lastDropTarget, _source); }
|
||||
function over () { if (changed) { moved('over'); } }
|
||||
function out () { if (_lastDropTarget) { moved('out'); } }
|
||||
}
|
||||
|
||||
function spillOver (el) {
|
||||
classes.rm(el, 'gu-hide');
|
||||
}
|
||||
|
||||
function spillOut (el) {
|
||||
if (drake.dragging) { classes.add(el, 'gu-hide'); }
|
||||
}
|
||||
|
||||
function renderMirrorImage () {
|
||||
if (_mirror) {
|
||||
return;
|
||||
}
|
||||
var rect = _item.getBoundingClientRect();
|
||||
_mirror = _item.cloneNode(true);
|
||||
_mirror.style.width = getRectWidth(rect) + 'px';
|
||||
_mirror.style.height = getRectHeight(rect) + 'px';
|
||||
classes.rm(_mirror, 'gu-transit');
|
||||
classes.add(_mirror, 'gu-mirror');
|
||||
o.mirrorContainer.appendChild(_mirror);
|
||||
touchy(documentElement, 'add', 'mousemove', drag);
|
||||
classes.add(o.mirrorContainer, 'gu-unselectable');
|
||||
drake.emit('cloned', _mirror, _item, 'mirror');
|
||||
}
|
||||
|
||||
function removeMirrorImage () {
|
||||
if (_mirror) {
|
||||
classes.rm(o.mirrorContainer, 'gu-unselectable');
|
||||
touchy(documentElement, 'remove', 'mousemove', drag);
|
||||
_mirror.parentElement.removeChild(_mirror);
|
||||
_mirror = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getImmediateChild (dropTarget, target) {
|
||||
var immediate = target;
|
||||
while (immediate !== dropTarget && immediate.parentElement !== dropTarget) {
|
||||
immediate = immediate.parentElement;
|
||||
}
|
||||
if (immediate === documentElement) {
|
||||
return null;
|
||||
}
|
||||
return immediate;
|
||||
}
|
||||
|
||||
function getReference (dropTarget, target, x, y) {
|
||||
var horizontal = o.direction === 'horizontal';
|
||||
var reference = target !== dropTarget ? inside() : outside();
|
||||
return reference;
|
||||
|
||||
function outside () { // slower, but able to figure out any position
|
||||
var len = dropTarget.children.length;
|
||||
var i;
|
||||
var el;
|
||||
var rect;
|
||||
for (i = 0; i < len; i++) {
|
||||
el = dropTarget.children[i];
|
||||
rect = el.getBoundingClientRect();
|
||||
if (horizontal && rect.left > x) { return el; }
|
||||
if (!horizontal && rect.top > y) { return el; }
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function inside () { // faster, but only available if dropped inside a child element
|
||||
var rect = target.getBoundingClientRect();
|
||||
if (horizontal) {
|
||||
return resolve(x > rect.left + getRectWidth(rect) / 2);
|
||||
}
|
||||
return resolve(y > rect.top + getRectHeight(rect) / 2);
|
||||
}
|
||||
|
||||
function resolve (after) {
|
||||
return after ? nextEl(target) : target;
|
||||
}
|
||||
}
|
||||
|
||||
function isCopy (item, container) {
|
||||
return typeof o.copy === 'boolean' ? o.copy : o.copy(item, container);
|
||||
}
|
||||
}
|
||||
|
||||
function touchy (el, op, type, fn) {
|
||||
var touch = {
|
||||
mouseup: 'touchend',
|
||||
mousedown: 'touchstart',
|
||||
mousemove: 'touchmove'
|
||||
};
|
||||
var microsoft = {
|
||||
mouseup: 'MSPointerUp',
|
||||
mousedown: 'MSPointerDown',
|
||||
mousemove: 'MSPointerMove'
|
||||
};
|
||||
if (global.navigator.msPointerEnabled) {
|
||||
crossvent[op](el, microsoft[type], fn);
|
||||
}
|
||||
crossvent[op](el, touch[type], fn);
|
||||
crossvent[op](el, type, fn);
|
||||
}
|
||||
|
||||
function whichMouseButton (e) {
|
||||
if (e.buttons !== void 0) { return e.buttons; }
|
||||
if (e.which !== void 0) { return e.which; }
|
||||
var button = e.button;
|
||||
if (button !== void 0) { // see https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/event.js#L573-L575
|
||||
return button & 1 ? 1 : button & 2 ? 3 : (button & 4 ? 2 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
function getOffset (el) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
return {
|
||||
left: rect.left + getScroll('scrollLeft', 'pageXOffset'),
|
||||
top: rect.top + getScroll('scrollTop', 'pageYOffset')
|
||||
};
|
||||
}
|
||||
|
||||
function getScroll (scrollProp, offsetProp) {
|
||||
if (typeof global[offsetProp] !== 'undefined') {
|
||||
return global[offsetProp];
|
||||
}
|
||||
var documentElement = document.documentElement;
|
||||
if (documentElement.clientHeight) {
|
||||
return documentElement[scrollProp];
|
||||
}
|
||||
var body = document.body;
|
||||
return body[scrollProp];
|
||||
}
|
||||
|
||||
function getElementBehindPoint (point, x, y) {
|
||||
var p = point || {};
|
||||
var state = p.className;
|
||||
var el;
|
||||
p.className += ' gu-hide';
|
||||
el = document.elementFromPoint(x, y);
|
||||
p.className = state;
|
||||
return el;
|
||||
}
|
||||
|
||||
function never () { return false; }
|
||||
function always () { return true; }
|
||||
function getRectWidth (rect) { return rect.width || (rect.right - rect.left); }
|
||||
function getRectHeight (rect) { return rect.height || (rect.bottom - rect.top); }
|
||||
function isInput (el) { return el.tagName === 'INPUT' || el.tagName === 'TEXTAREA'; }
|
||||
|
||||
function nextEl (el) {
|
||||
return el.nextElementSibling || manually();
|
||||
function manually () {
|
||||
var sibling = el;
|
||||
do {
|
||||
sibling = sibling.nextSibling;
|
||||
} while (sibling && sibling.nodeType !== 1);
|
||||
return sibling;
|
||||
}
|
||||
}
|
||||
|
||||
function getEventHost (e) {
|
||||
// on touchend event, we have to use `e.changedTouches`
|
||||
// see http://stackoverflow.com/questions/7192563/touchend-event-properties
|
||||
// see https://github.com/bevacqua/dragula/issues/34
|
||||
if (e.targetTouches && e.targetTouches.length) {
|
||||
return e.targetTouches[0];
|
||||
}
|
||||
if (e.changedTouches && e.changedTouches.length) {
|
||||
return e.changedTouches[0];
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
function getCoord (coord, e) {
|
||||
var host = getEventHost(e);
|
||||
var missMap = {
|
||||
pageX: 'clientX', // IE8
|
||||
pageY: 'clientY' // IE8
|
||||
};
|
||||
if (coord in missMap && !(coord in host) && missMap[coord] in host) {
|
||||
coord = missMap[coord];
|
||||
}
|
||||
return host[coord];
|
||||
}
|
||||
|
||||
module.exports = dragula;
|
Loading…
Add table
Add a link
Reference in a new issue