From f4298034b9a34197438d716096a6a980e46db02b Mon Sep 17 00:00:00 2001 From: Johannes Meyer Date: Mon, 17 Dec 2018 14:40:47 +0100 Subject: [PATCH] js: Add drag&drop functionality --- configuration.php | 3 + public/css/module.less | 9 + public/js/behavior/sortable.js | 41 + public/js/module.js | 27 + public/js/vendor/jquery.fn.sortable.js | 1704 ++++++++++++++++++++ public/js/vendor/jquery.fn.sortable.min.js | 3 + 6 files changed, 1787 insertions(+) create mode 100644 public/js/behavior/sortable.js create mode 100644 public/js/vendor/jquery.fn.sortable.js create mode 100644 public/js/vendor/jquery.fn.sortable.min.js diff --git a/configuration.php b/configuration.php index 63c8ed9..358287d 100644 --- a/configuration.php +++ b/configuration.php @@ -57,3 +57,6 @@ $this->provideRestriction( 'businessprocess/prefix', $this->translate('Restrict access to configurations with the given prefix') ); + +$this->provideJsFile('behavior/sortable.js'); +$this->provideJsFile('vendor/jquery.fn.sortable.js'); \ No newline at end of file diff --git a/public/css/module.less b/public/css/module.less index 5e18cbf..b0547d8 100644 --- a/public/css/module.less +++ b/public/css/module.less @@ -23,6 +23,10 @@ div.bp { margin-bottom: 4px; } +div.bp.sortable > .sortable-ghost { + opacity: 0.5; +} + .simulation div.bp { border-right: 1em solid @colorCriticalHandled; padding-right: 1em; @@ -458,6 +462,11 @@ td > a > .badges { clear: both; } +.tiles.sortable > .sortable-ghost { + opacity: 0.5; + border: .2em dashed black; +} + .tiles > div { color: white; width: 12em; diff --git a/public/js/behavior/sortable.js b/public/js/behavior/sortable.js new file mode 100644 index 0000000..f641ca0 --- /dev/null +++ b/public/js/behavior/sortable.js @@ -0,0 +1,41 @@ +/*! Icinga Web 2 | (c) 2018 Icinga Development Team | GPLv2+ */ + +(function(Icinga, $) { + + 'use strict'; + + Icinga.Behaviors = Icinga.Behaviors || {}; + + var Sortable = function (icinga) { + Icinga.EventListener.call(this, icinga); + this.on('rendered', this.onRendered, this); + }; + + Sortable.prototype = new Icinga.EventListener(); + + Sortable.prototype.onRendered = function(e) { + $(e.target).find('.sortable').each(function() { + var $el = $(this); + var options = { + onMove: function (/**Event*/ event, /**Event*/ originalEvent) { + if (typeof this.options['filter'] !== 'undefined' && $(event.related).is(this.options['filter'])) { + // Assumes the filtered item is either at the very start or end of the list and prevents the + // user from dropping other items before (if at the very start) or after it. + return false; + } + } + }; + + $.each($el.data(), function (i, v) { + if (i.length > 8 && i.startsWith('sortable')) { + options[i.charAt(8).toLowerCase() + i.substr(9)] = v; + } + }); + + $(this).sortable(options); + }); + }; + + Icinga.Behaviors.Sortable = Sortable; + +})(Icinga, jQuery); diff --git a/public/js/module.js b/public/js/module.js index 7ae5a85..4240811 100644 --- a/public/js/module.js +++ b/public/js/module.js @@ -35,6 +35,7 @@ this.module.on('click', 'div.tiles > div', this.tileClick); this.module.on('click', '.dashboard-tile', this.dashboardTileClick); + this.module.on('end', 'div.tiles.sortable', this.tileDropped); this.module.icinga.logger.debug('BP module initialized'); }, @@ -89,6 +90,32 @@ $(event.currentTarget).find('> .bp-link > a').first().trigger('click'); }, + tileDropped: function(event) { + var evt = event.originalEvent; + if (evt.oldIndex !== evt.newIndex) { + var $target = $(evt.to); + var actionUrl = icinga.utils.addUrlParams($target.data('actionUrl'), { + action: 'move', + movenode: $(evt.item).data('nodeName') + }); + + if (! $target.is('.few') && $('.addnew', $target).length === 2) { + // This assumes we're not moving things between different lists + evt.oldIndex -= 1; + evt.newIndex -= 1; + } + + var data = { + csrfToken: $target.data('csrfToken'), + movenode: 'movenode', // That's the submit button.. + from: evt.oldIndex, + to: evt.newIndex + }; + + icinga.loader.loadUrl(actionUrl, $target.closest('.container'), data, 'post'); + } + }, + /** * Add 'hovered' class to hovered title elements * diff --git a/public/js/vendor/jquery.fn.sortable.js b/public/js/vendor/jquery.fn.sortable.js new file mode 100644 index 0000000..d2b308c --- /dev/null +++ b/public/js/vendor/jquery.fn.sortable.js @@ -0,0 +1,1704 @@ +/** + * jQuery plugin for Sortable + * @author RubaXa + * @license MIT + */ +(function (factory) { + "use strict"; + + if (typeof define === "function" && define.amd) { + define(["jquery"], factory); + } + else { + /* jshint sub:true */ + factory(jQuery); + } +})(function ($) { + "use strict"; + + + var dragEl, + parentEl, + ghostEl, + cloneEl, + rootEl, + nextEl, + lastDownEl, + + scrollEl, + scrollParentEl, + scrollCustomFn, + + lastEl, + lastCSS, + lastParentCSS, + + oldIndex, + newIndex, + + activeGroup, + putSortable, + + autoScrolls = [], + + pointerElemChangedInterval, + lastPointerElemX, + lastPointerElemY, + + tapEvt, + touchEvt, + + moved, + + forRepaintDummy, + + /** @const */ + R_SPACE = /\s+/g, + R_FLOAT = /left|right|inline/, + + expando = 'Sortable' + (new Date).getTime(), + + win = window, + document = win.document, + parseInt = win.parseInt, + setTimeout = win.setTimeout, + + $ = win.jQuery || win.Zepto, + Polymer = win.Polymer, + + captureMode = false, + passiveMode = false, + + supportDraggable = ('draggable' in document.createElement('div')), + supportCssPointerEvents = (function (el) { + // false when IE11 + if (!!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)) { + return false; + } + el = document.createElement('x'); + el.style.cssText = 'pointer-events:auto'; + return el.style.pointerEvents === 'auto'; + })(), + + _silent = false, + + abs = Math.abs, + min = Math.min, + + savedInputChecked = [], + touchDragOverListeners = [], + + alwaysFalse = function () { return false; }, + + _getParentAutoScrollElement = function(rootEl, includeSelf) { + // will skip to window in _autoScroll + if (!rootEl || !rootEl.getBoundingClientRect) return; + + var elem = rootEl; + var gotSelf = false; + do { + if ( + (elem.clientWidth < elem.scrollWidth) || + (elem.clientHeight < elem.scrollHeight) + ) { + if (!elem || !elem.getBoundingClientRect || elem === document.body) return; + + if (gotSelf || includeSelf) return elem; + gotSelf = true; + } + + } while (elem = elem.parentNode); + }, + + _autoScroll = _throttle(function (/**Event*/evt, /**Object*/options, /**HTMLElement*/rootEl) { + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 + if (options.scroll) { + var _this = rootEl ? rootEl[expando] : window, + rect, + sens = options.scrollSensitivity, + speed = options.scrollSpeed, + + x = evt.clientX, + y = evt.clientY, + + winWidth = window.innerWidth, + winHeight = window.innerHeight, + + vx, + vy, + + scrollOffsetX, + scrollOffsetY + ; + + // Detect scrollEl + if (scrollParentEl !== rootEl) { + _clearAutoScrolls(); + + scrollEl = options.scroll; + scrollCustomFn = options.scrollFn; + + if (scrollEl === true) { + scrollEl = _getParentAutoScrollElement(rootEl, true); + scrollParentEl = scrollEl; + } + } + + + var layersOut = 0; + var currentParent = scrollEl; + do { + var el; + + if (currentParent) { + el = currentParent; + rect = currentParent.getBoundingClientRect(); + vx = (abs(rect.right - x) <= sens) - (abs(rect.left - x) <= sens); + vy = (abs(rect.bottom - y) <= sens) - (abs(rect.top - y) <= sens); + } + + + if (!(vx || vy)) { + vx = (winWidth - x <= sens) - (x <= sens); + vy = (winHeight - y <= sens) - (y <= sens); + + /* jshint expr:true */ + (vx || vy) && (el = win); + } + + if (!autoScrolls[layersOut]) { + for (var i = 0; i <= layersOut; i++) { + if (!autoScrolls[i]) { + autoScrolls[i] = {}; + } + } + } + + if (autoScrolls[layersOut].vx !== vx || autoScrolls[layersOut].vy !== vy || autoScrolls[layersOut].el !== el) { + autoScrolls[layersOut].el = el; + autoScrolls[layersOut].vx = vx; + autoScrolls[layersOut].vy = vy; + + clearInterval(autoScrolls[layersOut].pid); + + if (el) { + autoScrolls[layersOut].pid = setInterval((function () { + scrollOffsetY = autoScrolls[this.layersOut].vy ? autoScrolls[this.layersOut].vy * speed : 0; + scrollOffsetX = autoScrolls[this.layersOut].vx ? autoScrolls[this.layersOut].vx * speed : 0; + + if ('function' === typeof(scrollCustomFn)) { + if (scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt, touchEvt, autoScrolls[this.layersOut].el) !== 'continue') { + return; + } + } + + if (autoScrolls[this.layersOut].el === win) { + win.scrollTo(win.pageXOffset + scrollOffsetX, win.pageYOffset + scrollOffsetY); + } else { + autoScrolls[this.layersOut].el.scrollTop += scrollOffsetY; + autoScrolls[this.layersOut].el.scrollLeft += scrollOffsetX; + } + }).bind({layersOut: layersOut}), 24); + } + } + layersOut++; + } while (options.bubbleScroll && (currentParent = _getParentAutoScrollElement(currentParent, false))); + } + }, 30), + + _clearAutoScrolls = function () { + autoScrolls.forEach(function(autoScroll) { + clearInterval(autoScroll.pid); + }); + autoScrolls = []; + }, + + _prepareGroup = function (options) { + function toFn(value, pull) { + return function(to, from, dragEl, evt) { + var ret; + + if (value == null || value === false) { + ret = false; + } else if (pull && value === 'clone') { + ret = value; + } else if (typeof value === 'function') { + ret = value(to, from, dragEl, evt); + } else { + var otherGroup = (pull ? to : from).options.group.name; + + ret = (value === true || + (typeof value === 'string' && value === otherGroup) || + (value.join && value.indexOf(otherGroup) > -1)); + } + + return ret || to.options.group.name === from.options.group.name; + } + } + + var group = {}; + var originalGroup = options.group; + + if (!originalGroup || typeof originalGroup != 'object') { + originalGroup = {name: originalGroup}; + } + + group.name = originalGroup.name; + group.checkPull = toFn(originalGroup.pull, true); + group.checkPut = toFn(originalGroup.put); + group.revertClone = originalGroup.revertClone; + + options.group = group; + } + ; + + // Detect support a passive mode + try { + window.addEventListener('test', null, Object.defineProperty({}, 'passive', { + get: function () { + // `false`, because everything starts to work incorrectly and instead of d'n'd, + // begins the page has scrolled. + passiveMode = false; + captureMode = { + capture: false, + passive: passiveMode + }; + } + })); + } catch (err) {} + + /** + * @class Sortable + * @param {HTMLElement} el + * @param {Object} [options] + */ + function Sortable(el, options) { + if (!(el && el.nodeType && el.nodeType === 1)) { + throw 'Sortable: `el` must be HTMLElement, and not ' + {}.toString.call(el); + } + + this.el = el; // root element + this.options = options = _extend({}, options); + + + // Export instance + el[expando] = this; + + // Default options + var defaults = { + group: null, + sort: true, + disabled: false, + store: null, + handle: null, + scroll: true, + scrollSensitivity: 30, + scrollSpeed: 10, + bubbleScroll: true, + draggable: /[uo]l/i.test(el.nodeName) ? 'li' : '>*', + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + ignore: 'a, img', + filter: null, + preventOnFilter: true, + animation: 0, + setData: function (dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + dropBubble: false, + dragoverBubble: false, + dataIdAttr: 'data-id', + delay: 0, + touchStartThreshold: parseInt(window.devicePixelRatio, 10) || 1, + forceFallback: false, + fallbackClass: 'sortable-fallback', + fallbackOnBody: false, + fallbackTolerance: 0, + fallbackOffset: {x: 0, y: 0}, + supportPointer: Sortable.supportPointer !== false + }; + + + // Set default options + for (var name in defaults) { + !(name in options) && (options[name] = defaults[name]); + } + + _prepareGroup(options); + + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + + // Setup drag mode + this.nativeDraggable = options.forceFallback ? false : supportDraggable; + + // Bind events + _on(el, 'mousedown', this._onTapStart); + _on(el, 'touchstart', this._onTapStart); + options.supportPointer && _on(el, 'pointerdown', this._onTapStart); + + if (this.nativeDraggable) { + _on(el, 'dragover', this); + _on(el, 'dragenter', this); + } + + touchDragOverListeners.push(this._onDragOver); + + // Restore sorting + options.store && this.sort(options.store.get(this)); + } + + + Sortable.prototype = /** @lends Sortable.prototype */ { + constructor: Sortable, + + _onTapStart: function (/** Event|TouchEvent */evt) { + var _this = this, + el = this.el, + options = this.options, + preventOnFilter = options.preventOnFilter, + type = evt.type, + touch = evt.touches && evt.touches[0], + target = (touch || evt).target, + originalTarget = evt.target.shadowRoot && ((evt.path && evt.path[0]) || (evt.composedPath && evt.composedPath()[0])) || target, + filter = options.filter, + startIndex; + + _saveInputCheckedState(el); + + + // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. + if (dragEl) { + return; + } + + if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { + return; // only left button or enabled + } + + // cancel dnd if original target is content editable + if (originalTarget.isContentEditable) { + return; + } + + target = _closest(target, options.draggable, el); + + if (!target) { + return; + } + + if (lastDownEl === target) { + // Ignoring duplicate `down` + return; + } + + // Get the index of the dragged element within its parent + startIndex = _index(target, options.draggable); + + // Check filter + if (typeof filter === 'function') { + if (filter.call(this, evt, target, this)) { + _dispatchEvent(_this, originalTarget, 'filter', target, el, el, startIndex); + preventOnFilter && evt.preventDefault(); + return; // cancel dnd + } + } + else if (filter) { + filter = filter.split(',').some(function (criteria) { + criteria = _closest(originalTarget, criteria.trim(), el); + + if (criteria) { + _dispatchEvent(_this, criteria, 'filter', target, el, el, startIndex); + return true; + } + }); + + if (filter) { + preventOnFilter && evt.preventDefault(); + return; // cancel dnd + } + } + + if (options.handle && !_closest(originalTarget, options.handle, el)) { + return; + } + + // Prepare `dragstart` + this._prepareDragStart(evt, touch, target, startIndex); + }, + + + _handleAutoScroll: function(evt) { + if (!dragEl || !this.options.scroll || (this.options.supportPointer && evt.type == 'touchmove')) return; + var + x = (evt.touches ? evt.touches[0] : evt).clientX, + y = (evt.touches ? evt.touches[0] : evt).clientY, + + elem = document.elementFromPoint(x, y), + _this = this + ; + + + // touch does not have native autoscroll, even with DnD enabled + if (!_this.nativeDraggable || evt.touches || (evt.pointerType && evt.pointerType == 'touch')) { + + _autoScroll(evt.touches ? evt.touches[0] : evt, _this.options, elem); + + // Listener for pointer element change + var ogElemScroller = _getParentAutoScrollElement(elem, true); + if (!pointerElemChangedInterval || + x != lastPointerElemX || + y != lastPointerElemY) { + + pointerElemChangedInterval && clearInterval(pointerElemChangedInterval); + // Detect for pointer elem change, emulating native DnD behaviour + pointerElemChangedInterval = setInterval(function() { + if (!dragEl) return; + var newElem = _getParentAutoScrollElement(document.elementFromPoint(x, y), true); + if (newElem != ogElemScroller) { + ogElemScroller = newElem; + _clearAutoScrolls(); + _autoScroll(evt.touches ? evt.touches[0] : evt, _this.options, ogElemScroller); + } + }, 10); + lastPointerElemX = x; + lastPointerElemY = y; + } + + } else { + // if DnD is enabled, first autoscroll will already scroll, so get parent autoscroll of first autoscroll + if (!_this.options.bubbleScroll) return; + _autoScroll(evt, _this.options, _getParentAutoScrollElement(elem, false)); + } + }, + + _prepareDragStart: function (/** Event */evt, /** Touch */touch, /** HTMLElement */target, /** Number */startIndex) { + var _this = this, + el = _this.el, + options = _this.options, + ownerDocument = el.ownerDocument, + dragStartFn; + + if (target && !dragEl && (target.parentNode === el)) { + tapEvt = evt; + + rootEl = el; + dragEl = target; + parentEl = dragEl.parentNode; + nextEl = dragEl.nextSibling; + lastDownEl = target; + activeGroup = options.group; + oldIndex = startIndex; + + this._lastX = (touch || evt).clientX; + this._lastY = (touch || evt).clientY; + + dragEl.style['will-change'] = 'all'; + + dragStartFn = function () { + // Delayed drag has been triggered + // we can re-enable the events: touchmove/mousemove + _this._disableDelayedDrag(); + + // Make the element draggable + dragEl.draggable = _this.nativeDraggable; + + // Bind the events: dragstart/dragend + _this._triggerDragStart(evt, touch); + + // Drag start event + _dispatchEvent(_this, rootEl, 'choose', dragEl, rootEl, rootEl, oldIndex); + + // Chosen item + _toggleClass(dragEl, options.chosenClass, true); + }; + + // Disable "draggable" + options.ignore.split(',').forEach(function (criteria) { + _find(dragEl, criteria.trim(), _disableDraggable); + }); + + _on(ownerDocument, 'mouseup', _this._onDrop); + _on(ownerDocument, 'touchend', _this._onDrop); + _on(ownerDocument, 'touchcancel', _this._onDrop); + _on(ownerDocument, 'selectstart', _this); + options.supportPointer && _on(ownerDocument, 'pointercancel', _this._onDrop); + + if (options.delay) { + // If the user moves the pointer or let go the click or touch + // before the delay has been reached: + // disable the delayed drag + _on(ownerDocument, 'mouseup', _this._disableDelayedDrag); + _on(ownerDocument, 'touchend', _this._disableDelayedDrag); + _on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); + _on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler); + _on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler); + options.supportPointer && _on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler); + + _this._dragStartTimer = setTimeout(dragStartFn.bind(_this), options.delay); + } else { + dragStartFn(); + } + + + } + }, + + _delayedDragTouchMoveHandler: function (/** TouchEvent|PointerEvent **/e) { + var touch = e.touches ? e.touches[0] : e; + if (min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) + >= this.options.touchStartThreshold + ) { + this._disableDelayedDrag(); + } + }, + + _disableDelayedDrag: function () { + var ownerDocument = this.el.ownerDocument; + + clearTimeout(this._dragStartTimer); + _off(ownerDocument, 'mouseup', this._disableDelayedDrag); + _off(ownerDocument, 'touchend', this._disableDelayedDrag); + _off(ownerDocument, 'touchcancel', this._disableDelayedDrag); + _off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler); + _off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler); + _off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler); + }, + + _triggerDragStart: function (/** Event */evt, /** Touch */touch) { + touch = touch || (evt.pointerType == 'touch' ? evt : null); + + + if (touch) { + // Touch device support + tapEvt = { + target: dragEl, + clientX: touch.clientX, + clientY: touch.clientY + }; + + this._onDragStart(tapEvt, 'touch'); + } + else if (!this.nativeDraggable) { + this._onDragStart(tapEvt, true); + } + else { + _on(dragEl, 'dragend', this); + _on(rootEl, 'dragstart', this._onDragStart); + } + + try { + if (document.selection) { + // Timeout neccessary for IE9 + _nextTick(function () { + document.selection.empty(); + }); + } else { + window.getSelection().removeAllRanges(); + } + } catch (err) { + } + }, + + _dragStarted: function () { + if (rootEl && dragEl) { + _on(document, 'drag', this._handleAutoScroll); + var options = this.options; + + // Apply effect + _toggleClass(dragEl, options.ghostClass, true); + _toggleClass(dragEl, options.dragClass, false); + + Sortable.active = this; + + // Drag start event + _dispatchEvent(this, rootEl, 'start', dragEl, rootEl, rootEl, oldIndex); + } else { + this._nulling(); + } + }, + + _emulateDragOver: function () { + if (touchEvt) { + if (this._lastX === touchEvt.clientX && this._lastY === touchEvt.clientY) { + return; + } + + this._lastX = touchEvt.clientX; + this._lastY = touchEvt.clientY; + + if (!supportCssPointerEvents) { + _css(ghostEl, 'display', 'none'); + } + + var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + var parent = target; + + while (target && target.shadowRoot) { + target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + parent = target; + } + + if (parent) { + do { + if (parent[expando]) { + var i = touchDragOverListeners.length; + while (i--) { + touchDragOverListeners[i]({ + clientX: touchEvt.clientX, + clientY: touchEvt.clientY, + target: target, + rootEl: parent + }); + } + + if (!this.options.dragoverBubble) { + break; + } + } + + target = parent; // store last element + } + /* jshint boss:true */ + while (parent = parent.parentNode); + } + + if (!supportCssPointerEvents) { + _css(ghostEl, 'display', ''); + } + } + }, + + + _onTouchMove: function (/**TouchEvent*/evt) { + if (tapEvt) { + var options = this.options, + fallbackTolerance = options.fallbackTolerance, + fallbackOffset = options.fallbackOffset, + touch = evt.touches ? evt.touches[0] : evt, + dx = (touch.clientX - tapEvt.clientX) + fallbackOffset.x, + dy = (touch.clientY - tapEvt.clientY) + fallbackOffset.y, + translate3d = evt.touches ? 'translate3d(' + dx + 'px,' + dy + 'px,0)' : 'translate(' + dx + 'px,' + dy + 'px)'; + + // only set the status to dragging, when we are actually dragging + if (!Sortable.active) { + if (fallbackTolerance && + min(abs(touch.clientX - this._lastX), abs(touch.clientY - this._lastY)) < fallbackTolerance + ) { + return; + } + + this._dragStarted(); + } + + // as well as creating the ghost element on the document body + this._appendGhost(); + + moved = true; + touchEvt = touch; + + _css(ghostEl, 'webkitTransform', translate3d); + _css(ghostEl, 'mozTransform', translate3d); + _css(ghostEl, 'msTransform', translate3d); + _css(ghostEl, 'transform', translate3d); + + evt.preventDefault(); + } + }, + + _appendGhost: function () { + if (!ghostEl) { + var rect = dragEl.getBoundingClientRect(), + css = _css(dragEl), + options = this.options, + ghostRect; + + ghostEl = dragEl.cloneNode(true); + + _toggleClass(ghostEl, options.ghostClass, false); + _toggleClass(ghostEl, options.fallbackClass, true); + _toggleClass(ghostEl, options.dragClass, true); + + _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); + _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); + _css(ghostEl, 'width', rect.width); + _css(ghostEl, 'height', rect.height); + _css(ghostEl, 'opacity', '0.8'); + _css(ghostEl, 'position', 'fixed'); + _css(ghostEl, 'zIndex', '100000'); + _css(ghostEl, 'pointerEvents', 'none'); + + options.fallbackOnBody && document.body.appendChild(ghostEl) || rootEl.appendChild(ghostEl); + + // Fixing dimensions. + ghostRect = ghostEl.getBoundingClientRect(); + _css(ghostEl, 'width', rect.width * 2 - ghostRect.width); + _css(ghostEl, 'height', rect.height * 2 - ghostRect.height); + } + }, + + _onDragStart: function (/**Event*/evt, /**boolean*/useFallback) { + var _this = this; + var dataTransfer = evt.dataTransfer; + var options = _this.options; + + _this._offUpEvents(); + + if (activeGroup.checkPull(_this, _this, dragEl, evt)) { + cloneEl = _clone(dragEl); + + cloneEl.draggable = false; + cloneEl.style['will-change'] = ''; + + this._hideClone(); + + _toggleClass(cloneEl, _this.options.chosenClass, false); + + // #1143: IFrame support workaround + _this._cloneId = _nextTick(function () { + rootEl.insertBefore(cloneEl, dragEl); + _dispatchEvent(_this, rootEl, 'clone', dragEl); + }); + } + + _toggleClass(dragEl, options.dragClass, true); + + if (useFallback) { + if (useFallback === 'touch') { + // Bind touch events + _on(document, 'touchmove', _this._onTouchMove); + // onTouchMove before handleAutoScroll in this case, because onTouchMove sets touchEvt + _on(document, 'touchmove', _this._handleAutoScroll); + _on(document, 'touchend', _this._onDrop); + _on(document, 'touchcancel', _this._onDrop); + + if (options.supportPointer) { + _on(document, 'pointermove', _this._handleAutoScroll); + _on(document, 'pointermove', _this._onTouchMove); + _on(document, 'pointerup', _this._onDrop); + } + } else { + // Old brwoser + _on(document, 'mousemove', _this._handleAutoScroll); + _on(document, 'mousemove', _this._onTouchMove); + _on(document, 'mouseup', _this._onDrop); + } + + _this._loopId = setInterval(_this._emulateDragOver, 50); + } + else { + if (dataTransfer) { + dataTransfer.effectAllowed = 'move'; + options.setData && options.setData.call(_this, dataTransfer, dragEl); + } + + _on(document, 'drop', _this); + + // #1143: Бывает элемент с IFrame внутри блокирует `drop`, + // поэтому если вызвался `mouseover`, значит надо отменять весь d'n'd. + // Breaking Chrome 62+ + // _on(document, 'mouseover', _this); + + _this._dragStartId = _nextTick(_this._dragStarted); + } + }, + + _onDragOver: function (/**Event*/evt) { + var el = this.el, + target, + dragRect, + targetRect, + revert, + options = this.options, + group = options.group, + activeSortable = Sortable.active, + isOwner = (activeGroup === group), + isMovingBetweenSortable = false, + canSort = options.sort; + + if (evt.preventDefault !== void 0) { + evt.preventDefault(); + !options.dragoverBubble && evt.stopPropagation(); + } + + if (dragEl.animated) { + return; + } + + moved = true; + + target = evt.target == el ? evt.target : _closest(evt.target, options.draggable, el); + + if (target === el) return; + + if (activeSortable && !options.disabled && + (isOwner + ? canSort || (revert = !rootEl.contains(dragEl)) // Reverting item into the original list + : ( + putSortable === this || + ( + (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) && + group.checkPut(this, activeSortable, dragEl, evt) + ) + ) + ) && + (evt.rootEl === void 0 || evt.rootEl === this.el) // touch fallback + ) { + if (_silent) { + return; + } + + dragRect = dragEl.getBoundingClientRect(); + + if (putSortable !== this) { + putSortable = this; + isMovingBetweenSortable = true; + } + + if (revert) { + this._hideClone(); + parentEl = rootEl; // actualization + + if (cloneEl || nextEl) { + rootEl.insertBefore(dragEl, cloneEl || nextEl); + } + else if (!canSort) { + rootEl.appendChild(dragEl); + } + + return; + } + + + if ((el.children.length === 0) || (el.children[0] === ghostEl) || + (el === evt.target) && (_ghostIsLast(el, evt)) + ) { + //assign target only if condition is true + if (el.children.length !== 0 && el.children[0] !== ghostEl && el === evt.target) { + target = el.lastElementChild; + } + + if (target) { + if (target.animated) { + return; + } + + targetRect = target.getBoundingClientRect(); + } + + if (isOwner) { + activeSortable._hideClone(); + } else { + activeSortable._showClone(this); + } + + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt) !== false) { + if (!dragEl.contains(el)) { + el.appendChild(dragEl); + parentEl = el; // actualization + } + + this._animate(dragRect, dragEl); + target && this._animate(targetRect, target); + } + } + else if (target && !target.animated && target !== dragEl && (target.parentNode[expando] !== void 0)) { + if (lastEl !== target) { + lastEl = target; + lastCSS = _css(target); + lastParentCSS = _css(target.parentNode); + } + + targetRect = target.getBoundingClientRect(); + + var width = targetRect.right - targetRect.left, + height = targetRect.bottom - targetRect.top, + floating = R_FLOAT.test(lastCSS.cssFloat + lastCSS.display) + || (lastParentCSS.display == 'flex' && lastParentCSS['flex-direction'].indexOf('row') === 0), + isWide = (target.offsetWidth > dragEl.offsetWidth), + isLong = (target.offsetHeight > dragEl.offsetHeight), + halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, + nextSibling = target.nextElementSibling, + after = false + ; + + if (floating) { + var elTop = dragEl.offsetTop, + tgTop = target.offsetTop; + + if (elTop === tgTop) { + after = (target.previousElementSibling === dragEl) && !isWide || halfway && isWide; + } + else if (target.previousElementSibling === dragEl || dragEl.previousElementSibling === target) { + after = (evt.clientY - targetRect.top) / height > 0.5; + } else { + after = tgTop > elTop; + } + } else if (!isMovingBetweenSortable) { + after = (nextSibling !== dragEl) && !isLong || halfway && isLong; + } + + var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); + + if (moveVector !== false) { + if (moveVector === 1 || moveVector === -1) { + after = (moveVector === 1); + } + + _silent = true; + setTimeout(_unsilent, 30); + + if (isOwner) { + activeSortable._hideClone(); + } else { + activeSortable._showClone(this); + } + + if (!dragEl.contains(el)) { + if (after && !nextSibling) { + el.appendChild(dragEl); + } else { + target.parentNode.insertBefore(dragEl, after ? nextSibling : target); + } + } + + parentEl = dragEl.parentNode; // actualization + + this._animate(dragRect, dragEl); + this._animate(targetRect, target); + } + } + } + }, + + _animate: function (prevRect, target) { + var ms = this.options.animation; + + if (ms) { + var currentRect = target.getBoundingClientRect(); + + if (prevRect.nodeType === 1) { + prevRect = prevRect.getBoundingClientRect(); + } + + _css(target, 'transition', 'none'); + _css(target, 'transform', 'translate3d(' + + (prevRect.left - currentRect.left) + 'px,' + + (prevRect.top - currentRect.top) + 'px,0)' + ); + + forRepaintDummy = target.offsetWidth; // repaint + + _css(target, 'transition', 'all ' + ms + 'ms'); + _css(target, 'transform', 'translate3d(0,0,0)'); + + clearTimeout(target.animated); + target.animated = setTimeout(function () { + _css(target, 'transition', ''); + _css(target, 'transform', ''); + target.animated = false; + }, ms); + } + }, + + _offUpEvents: function () { + var ownerDocument = this.el.ownerDocument; + + _off(document, 'touchmove', this._handleAutoScroll); + _off(document, 'pointermove', this._handleAutoScroll); + _off(document, 'mousemove', this._handleAutoScroll); + _off(document, 'touchmove', this._onTouchMove); + _off(document, 'pointermove', this._onTouchMove); + _off(ownerDocument, 'mouseup', this._onDrop); + _off(ownerDocument, 'touchend', this._onDrop); + _off(ownerDocument, 'pointerup', this._onDrop); + _off(ownerDocument, 'touchcancel', this._onDrop); + _off(ownerDocument, 'pointercancel', this._onDrop); + _off(ownerDocument, 'selectstart', this); + }, + + _onDrop: function (/**Event*/evt) { + var el = this.el, + options = this.options; + + clearInterval(this._loopId); + + clearInterval(pointerElemChangedInterval); + _clearAutoScrolls(); + _cancelThrottle(); + + clearTimeout(this._dragStartTimer); + + _cancelNextTick(this._cloneId); + _cancelNextTick(this._dragStartId); + + // Unbind events + _off(document, 'mouseover', this); + _off(document, 'mousemove', this._onTouchMove); + + + if (this.nativeDraggable) { + _off(document, 'drop', this); + _off(el, 'dragstart', this._onDragStart); + _off(document, 'drag', this._handleAutoScroll); + } + + this._offUpEvents(); + + if (evt) { + if (moved) { + evt.preventDefault(); + !options.dropBubble && evt.stopPropagation(); + } + + ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); + + if (rootEl === parentEl || this.lastPutMode !== 'clone') { + // Remove clone + cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); + } + + if (dragEl) { + if (this.nativeDraggable) { + _off(dragEl, 'dragend', this); + } + + _disableDraggable(dragEl); + dragEl.style['will-change'] = ''; + + // Remove class's + _toggleClass(dragEl, this.options.ghostClass, false); + _toggleClass(dragEl, this.options.chosenClass, false); + + // Drag stop event + _dispatchEvent(this, rootEl, 'unchoose', dragEl, parentEl, rootEl, oldIndex, null, evt); + + if (rootEl !== parentEl) { + newIndex = _index(dragEl, options.draggable); + + if (newIndex >= 0) { + // Add event + _dispatchEvent(null, parentEl, 'add', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + + // Remove event + _dispatchEvent(this, rootEl, 'remove', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + + // drag from one list and drop into another + _dispatchEvent(null, parentEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + } + } + else { + if (dragEl.nextSibling !== nextEl) { + // Get the index of the dragged element within its parent + newIndex = _index(dragEl, options.draggable); + + if (newIndex >= 0) { + // drag & drop within the same list + _dispatchEvent(this, rootEl, 'update', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + _dispatchEvent(this, rootEl, 'sort', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + } + } + } + + if (Sortable.active) { + /* jshint eqnull:true */ + if (newIndex == null || newIndex === -1) { + newIndex = oldIndex; + } + + _dispatchEvent(this, rootEl, 'end', dragEl, parentEl, rootEl, oldIndex, newIndex, evt); + + // Save sorting + this.save(); + } + } + + } + + this._nulling(); + }, + + _nulling: function() { + rootEl = + dragEl = + parentEl = + ghostEl = + nextEl = + cloneEl = + lastDownEl = + + scrollEl = + scrollParentEl = + + tapEvt = + touchEvt = + + moved = + newIndex = + + lastEl = + lastCSS = + + putSortable = + activeGroup = + Sortable.active = null; + + savedInputChecked.forEach(function (el) { + el.checked = true; + }); + savedInputChecked.length = 0; + }, + + handleEvent: function (/**Event*/evt) { + switch (evt.type) { + case 'drop': + case 'dragend': + this._onDrop(evt); + break; + + case 'dragover': + case 'dragenter': + if (dragEl) { + this._onDragOver(evt); + _globalDragOver(evt); + } + break; + + case 'mouseover': + this._onDrop(evt); + break; + + case 'selectstart': + evt.preventDefault(); + break; + } + }, + + + /** + * Serializes the item into an array of string. + * @returns {String[]} + */ + toArray: function () { + var order = [], + el, + children = this.el.children, + i = 0, + n = children.length, + options = this.options; + + for (; i < n; i++) { + el = children[i]; + if (_closest(el, options.draggable, this.el)) { + order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); + } + } + + return order; + }, + + + /** + * Sorts the elements according to the array. + * @param {String[]} order order of the items + */ + sort: function (order) { + var items = {}, rootEl = this.el; + + this.toArray().forEach(function (id, i) { + var el = rootEl.children[i]; + + if (_closest(el, this.options.draggable, rootEl)) { + items[id] = el; + } + }, this); + + order.forEach(function (id) { + if (items[id]) { + rootEl.removeChild(items[id]); + rootEl.appendChild(items[id]); + } + }); + }, + + + /** + * Save the current sorting + */ + save: function () { + var store = this.options.store; + store && store.set(this); + }, + + + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * @param {HTMLElement} el + * @param {String} [selector] default: `options.draggable` + * @returns {HTMLElement|null} + */ + closest: function (el, selector) { + return _closest(el, selector || this.options.draggable, this.el); + }, + + + /** + * Set/get option + * @param {string} name + * @param {*} [value] + * @returns {*} + */ + option: function (name, value) { + var options = this.options; + + if (value === void 0) { + return options[name]; + } else { + options[name] = value; + + if (name === 'group') { + _prepareGroup(options); + } + } + }, + + + /** + * Destroy + */ + destroy: function () { + var el = this.el; + + el[expando] = null; + + _off(el, 'mousedown', this._onTapStart); + _off(el, 'touchstart', this._onTapStart); + _off(el, 'pointerdown', this._onTapStart); + + if (this.nativeDraggable) { + _off(el, 'dragover', this); + _off(el, 'dragenter', this); + } + + // Remove draggable attributes + Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { + el.removeAttribute('draggable'); + }); + + touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); + + this._onDrop(); + + this.el = el = null; + }, + + _hideClone: function() { + if (!cloneEl.cloneHidden) { + _css(cloneEl, 'display', 'none'); + cloneEl.cloneHidden = true; + } + }, + + _showClone: function(putSortable) { + if (putSortable.lastPutMode !== 'clone') return; + + if (cloneEl.cloneHidden) { + // show clone at dragEl or original position + rootEl.insertBefore(cloneEl, rootEl.contains(dragEl) && !this.options.group.revertClone ? dragEl : nextEl); + + if (this.options.group.revertClone) { + this._animate(dragEl, cloneEl); + } + _css(cloneEl, 'display', ''); + cloneEl.cloneHidden = false; + } + } + }; + + function _closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx) { + if (el) { + ctx = ctx || document; + + do { + if ((selector === '>*' && el.parentNode === ctx) || _matches(el, selector)) { + return el; + } + /* jshint boss:true */ + } while (el = _getParentOrHost(el)); + } + + return null; + } + + + function _getParentOrHost(el) { + return (el.host && el !== document && el.host.nodeType) + ? el.host + : el.parentNode; + } + + + function _globalDragOver(/**Event*/evt) { + if (evt.dataTransfer) { + evt.dataTransfer.dropEffect = 'move'; + } + evt.preventDefault(); + } + + + function _on(el, event, fn) { + el.addEventListener(event, fn, captureMode); + } + + + function _off(el, event, fn) { + el.removeEventListener(event, fn, captureMode); + } + + + function _toggleClass(el, name, state) { + if (el) { + if (el.classList) { + el.classList[state ? 'add' : 'remove'](name); + } + else { + var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); + el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); + } + } + } + + + function _css(el, prop, val) { + var style = el && el.style; + + if (style) { + if (val === void 0) { + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, ''); + } + else if (el.currentStyle) { + val = el.currentStyle; + } + + return prop === void 0 ? val : val[prop]; + } + else { + if (!(prop in style)) { + prop = '-webkit-' + prop; + } + + style[prop] = val + (typeof val === 'string' ? '' : 'px'); + } + } + } + + + function _find(ctx, tagName, iterator) { + if (ctx) { + var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; + + if (iterator) { + for (; i < n; i++) { + iterator(list[i], i); + } + } + + return list; + } + + return []; + } + + + + function _dispatchEvent(sortable, rootEl, name, targetEl, toEl, fromEl, startIndex, newIndex, originalEvt) { + sortable = (sortable || rootEl[expando]); + + var evt = document.createEvent('Event'), + options = sortable.options, + onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); + + evt.initEvent(name, true, true); + + evt.to = toEl || rootEl; + evt.from = fromEl || rootEl; + evt.item = targetEl || rootEl; + evt.clone = cloneEl; + + evt.oldIndex = startIndex; + evt.newIndex = newIndex; + + evt.originalEvent = originalEvt; + + rootEl.dispatchEvent(evt); + + if (options[onName]) { + options[onName].call(sortable, evt); + } + } + + + function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvt, willInsertAfter) { + var evt, + sortable = fromEl[expando], + onMoveFn = sortable.options.onMove, + retVal; + + evt = document.createEvent('Event'); + evt.initEvent('move', true, true); + + evt.to = toEl; + evt.from = fromEl; + evt.dragged = dragEl; + evt.draggedRect = dragRect; + evt.related = targetEl || toEl; + evt.relatedRect = targetRect || toEl.getBoundingClientRect(); + evt.willInsertAfter = willInsertAfter; + + evt.originalEvent = originalEvt; + + fromEl.dispatchEvent(evt); + + if (onMoveFn) { + retVal = onMoveFn.call(sortable, evt, originalEvt); + } + + return retVal; + } + + + function _disableDraggable(el) { + el.draggable = false; + } + + + function _unsilent() { + _silent = false; + } + + + /** @returns {HTMLElement|false} */ + function _ghostIsLast(el, evt) { + var lastEl = el.lastElementChild, + rect = lastEl.getBoundingClientRect(); + + // 5 — min delta + // abs — нельзя добавлять, а то глюки при наведении сверху + return (evt.clientY - (rect.top + rect.height) > 5) || + (evt.clientX - (rect.left + rect.width) > 5); + } + + + /** + * Generate id + * @param {HTMLElement} el + * @returns {String} + * @private + */ + function _generateId(el) { + var str = el.tagName + el.className + el.src + el.href + el.textContent, + i = str.length, + sum = 0; + + while (i--) { + sum += str.charCodeAt(i); + } + + return sum.toString(36); + } + + /** + * Returns the index of an element within its parent for a selected set of + * elements + * @param {HTMLElement} el + * @param {selector} selector + * @return {number} + */ + function _index(el, selector) { + var index = 0; + + if (!el || !el.parentNode) { + return -1; + } + + while (el && (el = el.previousElementSibling)) { + if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && (selector === '>*' || _matches(el, selector))) { + index++; + } + } + + return index; + } + + function _matches(/**HTMLElement*/el, /**String*/selector) { + if (el) { + try { + if (el.matches) { + return el.matches(selector); + } else if (el.msMatchesSelector) { + return el.msMatchesSelector(selector); + } + } catch(_) { + return false; + } + } + + return false; + } + + var _throttleTimeout; + function _throttle(callback, ms) { + return function () { + if (!_throttleTimeout) { + var args = arguments, + _this = this + ; + + _throttleTimeout = setTimeout(function () { + if (args.length === 1) { + callback.call(_this, args[0]); + } else { + callback.apply(_this, args); + } + + _throttleTimeout = void 0; + }, ms); + } + }; + } + + function _cancelThrottle() { + clearTimeout(_throttleTimeout); + _throttleTimeout = void 0; + } + + function _extend(dst, src) { + if (dst && src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + } + + return dst; + } + + function _clone(el) { + if (Polymer && Polymer.dom) { + return Polymer.dom(el).cloneNode(true); + } + else if ($) { + return $(el).clone(true)[0]; + } + else { + return el.cloneNode(true); + } + } + + function _saveInputCheckedState(root) { + savedInputChecked.length = 0; + + var inputs = root.getElementsByTagName('input'); + var idx = inputs.length; + + while (idx--) { + var el = inputs[idx]; + el.checked && savedInputChecked.push(el); + } + } + + function _nextTick(fn) { + return setTimeout(fn, 0); + } + + function _cancelNextTick(id) { + return clearTimeout(id); + } + + // Fixed #973: + _on(document, 'touchmove', function (evt) { + if (Sortable.active) { + evt.preventDefault(); + } + }); + + // Export utils + Sortable.utils = { + on: _on, + off: _off, + css: _css, + find: _find, + is: function (el, selector) { + return !!_closest(el, selector, el); + }, + extend: _extend, + throttle: _throttle, + closest: _closest, + toggleClass: _toggleClass, + clone: _clone, + index: _index, + nextTick: _nextTick, + cancelNextTick: _cancelNextTick + }; + + + /** + * Create sortable instance + * @param {HTMLElement} el + * @param {Object} [options] + */ + Sortable.create = function (el, options) { + return new Sortable(el, options); + }; + + + + + + /** + * jQuery plugin for Sortable + * @param {Object|String} options + * @param {..*} [args] + * @returns {jQuery|*} + */ + $.fn.sortable = function (options) { + var retVal, + args = arguments; + + this.each(function () { + var $el = $(this), + sortable = $el.data('sortable'); + + if (!sortable && (options instanceof Object || !options)) { + sortable = new Sortable(this, options); + $el.data('sortable', sortable); + } + + if (sortable) { + if (options === 'widget') { + retVal = sortable; + } + else if (options === 'destroy') { + sortable.destroy(); + $el.removeData('sortable'); + } + else if (typeof sortable[options] === 'function') { + retVal = sortable[options].apply(sortable, [].slice.call(args, 1)); + } + else if (options in sortable.options) { + retVal = sortable.option.apply(sortable, args); + } + } + }); + + return (retVal === void 0) ? this : retVal; + }; +}); diff --git a/public/js/vendor/jquery.fn.sortable.min.js b/public/js/vendor/jquery.fn.sortable.min.js new file mode 100644 index 0000000..b0eeaa5 --- /dev/null +++ b/public/js/vendor/jquery.fn.sortable.min.js @@ -0,0 +1,3 @@ +/*! Sortable 1.7.0 - MIT | git://github.com/rubaxa/Sortable.git */ + +!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(r){"use strict";var C,E,x,O,P,N,d,b,y,D,k,A,B,c,o,M,Y,a,l,s,h,T,X,t,i,S=[],u=/\s+/g,I=/left|right|inline/,R="Sortable"+(new Date).getTime(),w=window,p=w.document,f=w.parseInt,H=w.setTimeout,e=(r=w.jQuery||w.Zepto,w.Polymer),g=!1,n=!1,v="draggable"in p.createElement("div"),m=!navigator.userAgent.match(/(?:Trident.*rv[ :]?11\.|msie)/i)&&((t=p.createElement("x")).style.cssText="pointer-events:auto","auto"===t.style.pointerEvents),F=!1,L=Math.abs,_=Math.min,j=[],W=[],U=function(t,e){if(t&&t.getBoundingClientRect){var n=t,o=!1;do{if(n.clientWidth*",ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,touchStartThreshold:f(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==G.supportPointer};for(var o in n)!(o in e)&&(e[o]=n[o]);for(var i in z(e),this)"_"===i.charAt(0)&&"function"==typeof this[i]&&(this[i]=this[i].bind(this));this.nativeDraggable=!e.forceFallback&&v,Z(t,"mousedown",this._onTapStart),Z(t,"touchstart",this._onTapStart),e.supportPointer&&Z(t,"pointerdown",this._onTapStart),this.nativeDraggable&&(Z(t,"dragover",this),Z(t,"dragenter",this)),W.push(this._onDragOver),e.store&&this.sort(e.store.get(this))}function Q(t,e,n){if(t){n=n||p;do{if(">*"===e&&t.parentNode===n||lt(t,e))return t}while(t=(o=t).host&&o!==p&&o.host.nodeType?o.host:o.parentNode)}var o;return null}function Z(t,e,n){t.addEventListener(e,n,g)}function J(t,e,n){t.removeEventListener(e,n,g)}function K(t,e,n){if(t)if(t.classList)t.classList[n?"add":"remove"](e);else{var o=(" "+t.className+" ").replace(u," ").replace(" "+e+" "," ");t.className=(o+(n?" "+e:"")).replace(u," ")}}function $(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return p.defaultView&&p.defaultView.getComputedStyle?n=p.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];e in o||(e="-webkit-"+e),o[e]=n+("string"==typeof n?"":"px")}}function tt(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i*"!==e&&!lt(t,e)||n++;return n}function lt(t,e){if(t)try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e)}catch(t){return!1}return!1}function st(n,o){return function(){if(!i){var t=arguments,e=this;i=H(function(){1===t.length?n.call(e,t[0]):n.apply(e,t),i=void 0},o)}}}function ct(t,e){if(t&&e)for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function ht(t){return e&&e.dom?e.dom(t).cloneNode(!0):r?r(t).clone(!0)[0]:t.cloneNode(!0)}function dt(t){return H(t,0)}function ut(t){return clearTimeout(t)}G.prototype={constructor:G,_onTapStart:function(t){var e,n=this,o=this.el,i=this.options,r=i.preventOnFilter,a=t.type,l=t.touches&&t.touches[0],s=(l||t).target,c=t.target.shadowRoot&&(t.path&&t.path[0]||t.composedPath&&t.composedPath()[0])||s,h=i.filter;if(function(t){j.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&j.push(o)}}(o),!C&&!(/mousedown|pointerdown/.test(a)&&0!==t.button||i.disabled)&&!c.isContentEditable&&(s=Q(s,i.draggable,o))&&d!==s){if(e=at(s,i.draggable),"function"==typeof h){if(h.call(this,t,s,this))return et(n,c,"filter",s,o,o,e),void(r&&t.preventDefault())}else if(h&&(h=h.split(",").some(function(t){if(t=Q(c,t.trim(),o))return et(n,t,"filter",s,o,o,e),!0})))return void(r&&t.preventDefault());i.handle&&!Q(c,i.handle,o)||this._prepareDragStart(t,l,s,e)}},_handleAutoScroll:function(e){if(C&&this.options.scroll&&(!this.options.supportPointer||"touchmove"!=e.type)){var n=(e.touches?e.touches[0]:e).clientX,o=(e.touches?e.touches[0]:e).clientY,t=p.elementFromPoint(n,o),i=this;if(!i.nativeDraggable||e.touches||e.pointerType&&"touch"==e.pointerType){V(e.touches?e.touches[0]:e,i.options,t);var r=U(t,!0);a&&n==l&&o==s||(a&&clearInterval(a),a=setInterval(function(){if(C){var t=U(p.elementFromPoint(n,o),!0);t!=r&&(r=t,q(),V(e.touches?e.touches[0]:e,i.options,r))}},10),l=n,s=o)}else{if(!i.options.bubbleScroll)return;V(e,i.options,U(t,!1))}}},_prepareDragStart:function(t,e,n,o){var i,r=this,a=r.el,l=r.options,s=a.ownerDocument;n&&!C&&n.parentNode===a&&(h=t,P=a,E=(C=n).parentNode,N=C.nextSibling,d=n,M=l.group,c=o,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,C.style["will-change"]="all",i=function(){r._disableDelayedDrag(),C.draggable=r.nativeDraggable,r._triggerDragStart(t,e),et(r,P,"choose",C,P,P,c),K(C,l.chosenClass,!0)},l.ignore.split(",").forEach(function(t){tt(C,t.trim(),ot)}),Z(s,"mouseup",r._onDrop),Z(s,"touchend",r._onDrop),Z(s,"touchcancel",r._onDrop),Z(s,"selectstart",r),l.supportPointer&&Z(s,"pointercancel",r._onDrop),l.delay?(Z(s,"mouseup",r._disableDelayedDrag),Z(s,"touchend",r._disableDelayedDrag),Z(s,"touchcancel",r._disableDelayedDrag),Z(s,"mousemove",r._delayedDragTouchMoveHandler),Z(s,"touchmove",r._delayedDragTouchMoveHandler),l.supportPointer&&Z(s,"pointermove",r._delayedDragTouchMoveHandler),r._dragStartTimer=H(i.bind(r),l.delay)):i())},_delayedDragTouchMoveHandler:function(t){var e=t.touches?t.touches[0]:t;_(L(e.clientX-this._lastX),L(e.clientY-this._lastY))>=this.options.touchStartThreshold&&this._disableDelayedDrag()},_disableDelayedDrag:function(){var t=this.el.ownerDocument;clearTimeout(this._dragStartTimer),J(t,"mouseup",this._disableDelayedDrag),J(t,"touchend",this._disableDelayedDrag),J(t,"touchcancel",this._disableDelayedDrag),J(t,"mousemove",this._delayedDragTouchMoveHandler),J(t,"touchmove",this._delayedDragTouchMoveHandler),J(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){(e=e||("touch"==t.pointerType?t:null))?(h={target:C,clientX:e.clientX,clientY:e.clientY},this._onDragStart(h,"touch")):this.nativeDraggable?(Z(C,"dragend",this),Z(P,"dragstart",this._onDragStart)):this._onDragStart(h,!0);try{p.selection?dt(function(){p.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(){if(P&&C){Z(p,"drag",this._handleAutoScroll);var t=this.options;K(C,t.ghostClass,!0),K(C,t.dragClass,!1),et(G.active=this,P,"start",C,P,P,c)}else this._nulling()},_emulateDragOver:function(){if(T){if(this._lastX===T.clientX&&this._lastY===T.clientY)return;this._lastX=T.clientX,this._lastY=T.clientY,m||$(x,"display","none");for(var t=p.elementFromPoint(T.clientX,T.clientY),e=t;t&&t.shadowRoot;)e=t=t.shadowRoot.elementFromPoint(T.clientX,T.clientY);if(e)do{if(e[R]){for(var n=W.length;n--;)W[n]({clientX:T.clientX,clientY:T.clientY,target:t,rootEl:e});if(!this.options.dragoverBubble)break}t=e}while(e=e.parentNode);m||$(x,"display","")}},_onTouchMove:function(t){if(h){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=i.clientX-h.clientX+o.x,a=i.clientY-h.clientY+o.y,l=t.touches?"translate3d("+r+"px,"+a+"px,0)":"translate("+r+"px,"+a+"px)";if(!G.active){if(n&&_(L(i.clientX-this._lastX),L(i.clientY-this._lastY))C.offsetWidth,_=e.offsetHeight>C.offsetHeight,b=.5<(v?(t.clientX-o.left)/f:(t.clientY-o.top)/g),y=e.nextElementSibling,D=!1;if(v){var T=C.offsetTop,S=e.offsetTop;D=T===S?e.previousElementSibling===C&&!m||b&&m:e.previousElementSibling===C||C.previousElementSibling===e?.5<(t.clientY-o.top)/g:T