From c6501f0cdf4ee83bb724378118f39b0d6abface6 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Tue, 2 Jul 2024 15:01:39 +0200 Subject: [PATCH 01/31] Initailize sortable JS --- assets/js/sortable.min.js | 2 ++ assets/toolbox.scss | 6 ++++++ controllers/toolbox.php | 17 +++++++++++++++++ views/toolbox/index.php | 18 +++++++++++++++--- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 assets/js/sortable.min.js diff --git a/assets/js/sortable.min.js b/assets/js/sortable.min.js new file mode 100644 index 0000000..39091f8 --- /dev/null +++ b/assets/js/sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.15.2 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function I(o){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?e(Object(i),!0).forEach(function(t){var e,n;e=o,t=i[n=t],n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t}):Object.getOwnPropertyDescriptors?Object.defineProperties(o,Object.getOwnPropertyDescriptors(i)):e(Object(i)).forEach(function(t){Object.defineProperty(o,t,Object.getOwnPropertyDescriptor(i,t))})}return o}function o(t){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(){return(a=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,o=arguments[e];for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(t[n]=o[n])}return t}).apply(this,arguments)}function i(t,e){if(null==t)return{};var n,o=function(t,e){if(null==t)return{};for(var n,o={},i=Object.keys(t),r=0;r<i.length;r++)n=i[r],0<=e.indexOf(n)||(o[n]=t[n]);return o}(t,e);if(Object.getOwnPropertySymbols)for(var i=Object.getOwnPropertySymbols(t),r=0;r<i.length;r++)n=i[r],0<=e.indexOf(n)||Object.prototype.propertyIsEnumerable.call(t,n)&&(o[n]=t[n]);return o}function r(t){return function(t){if(Array.isArray(t))return l(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return l(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Map"===(n="Object"===n&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?l(t,e):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=new Array(e);n<e;n++)o[n]=t[n];return o}function t(t){if("undefined"!=typeof window&&window.navigator)return!!navigator.userAgent.match(t)}var y=t(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),w=t(/Edge/i),s=t(/firefox/i),u=t(/safari/i)&&!t(/chrome/i)&&!t(/android/i),n=t(/iP(ad|od|hone)/i),c=t(/chrome/i)&&t(/android/i),d={capture:!1,passive:!1};function h(t,e,n){t.addEventListener(e,n,!y&&d)}function f(t,e,n){t.removeEventListener(e,n,!y&&d)}function p(t,e){if(e&&(">"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode))}var i;return null}var g,m=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(m," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(m," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function b(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i<r;i++)n(o[i],i);return o}return[]}function O(){var t=document.scrollingElement;return t||document.documentElement}function X(t,e,n,o,i){if(t.getBoundingClientRect||t===window){var r,a,l,s,c,u,d=t!==window&&t.parentNode&&t!==O()?(a=(r=t.getBoundingClientRect()).top,l=r.left,s=r.bottom,c=r.right,u=r.height,r.width):(l=a=0,s=window.innerHeight,c=window.innerWidth,u=window.innerHeight,window.innerWidth);if((e||n)&&t!==window&&(i=i||t.parentNode,!y))do{if(i&&i.getBoundingClientRect&&("none"!==R(i,"transform")||n&&"static"!==R(i,"position"))){var h=i.getBoundingClientRect();a-=h.top+parseInt(R(i,"border-top-width")),l-=h.left+parseInt(R(i,"border-left-width")),s=a+r.height,c=l+r.width;break}}while(i=i.parentNode);return o&&t!==window&&(o=(e=v(i||t))&&e.a,t=e&&e.d,e&&(s=(a/=t)+(u/=t),c=(l/=o)+(d/=o))),{top:a,left:l,bottom:s,right:c,width:d,height:u}}}function Y(t,e,n){for(var o=M(t,!0),i=X(t)[e];o;){var r=X(o)[n];if(!("top"===n||"left"===n?r<=i:i<=r))return o;if(o===O())break;o=M(o,!1)}return!1}function B(t,e,n,o){for(var i=0,r=0,a=t.children;r<a.length;){if("none"!==a[r].style.display&&a[r]!==Ft.ghost&&(o||a[r]!==Ft.dragged)&&P(a[r],n.draggable,t,!1)){if(i===e)return a[r];i++}r++}return null}function F(t,e){for(var n=t.lastElementChild;n&&(n===Ft.ghost||"none"===R(n,"display")||e&&!p(n,e));)n=n.previousElementSibling;return n||null}function j(t,e){var n=0;if(!t||!t.parentNode)return-1;for(;t=t.previousElementSibling;)"TEMPLATE"===t.nodeName.toUpperCase()||t===Ft.clone||e&&!p(t,e)||n++;return n}function E(t){var e=0,n=0,o=O();if(t)do{var i=v(t),r=i.a,i=i.d}while(e+=t.scrollLeft*r,n+=t.scrollTop*i,t!==o&&(t=t.parentNode));return[e,n]}function M(t,e){if(!t||!t.getBoundingClientRect)return O();var n=t,o=!1;do{if(n.clientWidth<n.scrollWidth||n.clientHeight<n.scrollHeight){var i=R(n);if(n.clientWidth<n.scrollWidth&&("auto"==i.overflowX||"scroll"==i.overflowX)||n.clientHeight<n.scrollHeight&&("auto"==i.overflowY||"scroll"==i.overflowY)){if(!n.getBoundingClientRect||n===document.body)return O();if(o||e)return n;o=!0}}}while(n=n.parentNode);return O()}function D(t,e){return Math.round(t.top)===Math.round(e.top)&&Math.round(t.left)===Math.round(e.left)&&Math.round(t.height)===Math.round(e.height)&&Math.round(t.width)===Math.round(e.width)}function S(e,n){return function(){var t;g||(1===(t=arguments).length?e.call(this,t[0]):e.apply(this,t),g=setTimeout(function(){g=void 0},n))}}function H(t,e,n){t.scrollLeft+=e,t.scrollTop+=n}function _(t){var e=window.Polymer,n=window.jQuery||window.Zepto;return e&&e.dom?e.dom(t).cloneNode(!0):n?n(t).clone(!0)[0]:t.cloneNode(!0)}function C(t,e){R(t,"position","absolute"),R(t,"top",e.top),R(t,"left",e.left),R(t,"width",e.width),R(t,"height",e.height)}function T(t){R(t,"position",""),R(t,"top",""),R(t,"left",""),R(t,"width",""),R(t,"height","")}function L(n,o,i){var r={};return Array.from(n.children).forEach(function(t){var e;P(t,o.draggable,n,!1)&&!t.animated&&t!==i&&(e=X(t),r.left=Math.min(null!==(t=r.left)&&void 0!==t?t:1/0,e.left),r.top=Math.min(null!==(t=r.top)&&void 0!==t?t:1/0,e.top),r.right=Math.max(null!==(t=r.right)&&void 0!==t?t:-1/0,e.right),r.bottom=Math.max(null!==(t=r.bottom)&&void 0!==t?t:-1/0,e.bottom))}),r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}var K="Sortable"+(new Date).getTime();function x(){var e,o=[];return{captureAnimationState:function(){o=[],this.options.animation&&[].slice.call(this.el.children).forEach(function(t){var e,n;"none"!==R(t,"display")&&t!==Ft.ghost&&(o.push({target:t,rect:X(t)}),e=I({},o[o.length-1].rect),!t.thisAnimationDuration||(n=v(t,!0))&&(e.top-=n.f,e.left-=n.e),t.fromRect=e)})},addAnimationState:function(t){o.push(t)},removeAnimationState:function(t){o.splice(function(t,e){for(var n in t)if(t.hasOwnProperty(n))for(var o in e)if(e.hasOwnProperty(o)&&e[o]===t[n][o])return Number(n);return-1}(o,{target:t}),1)},animateAll:function(t){var c=this;if(!this.options.animation)return clearTimeout(e),void("function"==typeof t&&t());var u=!1,d=0;o.forEach(function(t){var e=0,n=t.target,o=n.fromRect,i=X(n),r=n.prevFromRect,a=n.prevToRect,l=t.rect,s=v(n,!0);s&&(i.top-=s.f,i.left-=s.e),n.toRect=i,n.thisAnimationDuration&&D(r,i)&&!D(o,i)&&(l.top-i.top)/(l.left-i.left)==(o.top-i.top)/(o.left-i.left)&&(t=l,s=r,r=a,a=c.options,e=Math.sqrt(Math.pow(s.top-t.top,2)+Math.pow(s.left-t.left,2))/Math.sqrt(Math.pow(s.top-r.top,2)+Math.pow(s.left-r.left,2))*a.animation),D(i,o)||(n.prevFromRect=o,n.prevToRect=i,e=e||c.options.animation,c.animate(n,l,i,e)),e&&(u=!0,d=Math.max(d,e),clearTimeout(n.animationResetTimer),n.animationResetTimer=setTimeout(function(){n.animationTime=0,n.prevFromRect=null,n.fromRect=null,n.prevToRect=null,n.thisAnimationDuration=null},e),n.thisAnimationDuration=e)}),clearTimeout(e),u?e=setTimeout(function(){"function"==typeof t&&t()},d):"function"==typeof t&&t(),o=[]},animate:function(t,e,n,o){var i,r;o&&(R(t,"transition",""),R(t,"transform",""),i=(r=v(this.el))&&r.a,r=r&&r.d,i=(e.left-n.left)/(i||1),r=(e.top-n.top)/(r||1),t.animatingX=!!i,t.animatingY=!!r,R(t,"transform","translate3d("+i+"px,"+r+"px,0)"),this.forRepaintDummy=t.offsetWidth,R(t,"transition","transform "+o+"ms"+(this.options.easing?" "+this.options.easing:"")),R(t,"transform","translate3d(0,0,0)"),"number"==typeof t.animated&&clearTimeout(t.animated),t.animated=setTimeout(function(){R(t,"transition",""),R(t,"transform",""),t.animated=!1,t.animatingX=!1,t.animatingY=!1},o))}}}var A=[],N={initializeByDefault:!0},W={mount:function(e){for(var t in N)!N.hasOwnProperty(t)||t in e||(e[t]=N[t]);A.forEach(function(t){if(t.pluginName===e.pluginName)throw"Sortable: Cannot mount plugin ".concat(e.pluginName," more than once")}),A.push(e)},pluginEvent:function(e,n,o){var t=this;this.eventCanceled=!1,o.cancel=function(){t.eventCanceled=!0};var i=e+"Global";A.forEach(function(t){n[t.pluginName]&&(n[t.pluginName][i]&&n[t.pluginName][i](I({sortable:n},o)),n.options[t.pluginName]&&n[t.pluginName][e]&&n[t.pluginName][e](I({sortable:n},o)))})},initializePlugins:function(n,o,i,t){for(var e in A.forEach(function(t){var e=t.pluginName;(n.options[e]||t.initializeByDefault)&&((t=new t(n,o,n.options)).sortable=n,t.options=n.options,n[e]=t,a(i,t.defaults))}),n.options){var r;n.options.hasOwnProperty(e)&&(void 0!==(r=this.modifyOption(n,e,n.options[e]))&&(n.options[e]=r))}},getEventProperties:function(e,n){var o={};return A.forEach(function(t){"function"==typeof t.eventProperties&&a(o,t.eventProperties.call(n[t.pluginName],e))}),o},modifyOption:function(e,n,o){var i;return A.forEach(function(t){e[t.pluginName]&&t.optionListeners&&"function"==typeof t.optionListeners[n]&&(i=t.optionListeners[n].call(e[t.pluginName],o))}),i}};function z(t){var e=t.sortable,n=t.rootEl,o=t.name,i=t.targetEl,r=t.cloneEl,a=t.toEl,l=t.fromEl,s=t.oldIndex,c=t.newIndex,u=t.oldDraggableIndex,d=t.newDraggableIndex,h=t.originalEvent,f=t.putSortable,p=t.extraEventProperties;if(e=e||n&&n[K]){var g,m=e.options,t="on"+o.charAt(0).toUpperCase()+o.substr(1);!window.CustomEvent||y||w?(g=document.createEvent("Event")).initEvent(o,!0,!0):g=new CustomEvent(o,{bubbles:!0,cancelable:!0}),g.to=a||n,g.from=l||n,g.item=i||n,g.clone=r,g.oldIndex=s,g.newIndex=c,g.oldDraggableIndex=u,g.newDraggableIndex=d,g.originalEvent=h,g.pullMode=f?f.lastPutMode:void 0;var v,b=I(I({},p),W.getEventProperties(o,e));for(v in b)g[v]=b[v];n&&n.dispatchEvent(g),m[t]&&m[t].call(e,g)}}function G(t,e){var n=(o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{}).evt,o=i(o,U);W.pluginEvent.bind(Ft)(t,e,I({dragEl:V,parentEl:Z,ghostEl:$,rootEl:Q,nextEl:J,lastDownEl:tt,cloneEl:et,cloneHidden:nt,dragStarted:gt,putSortable:st,activeSortable:Ft.active,originalEvent:n,oldIndex:ot,oldDraggableIndex:rt,newIndex:it,newDraggableIndex:at,hideGhostForTarget:Rt,unhideGhostForTarget:Xt,cloneNowHidden:function(){nt=!0},cloneNowShown:function(){nt=!1},dispatchSortableEvent:function(t){q({sortable:e,name:t,originalEvent:n})}},o))}var U=["evt"];function q(t){z(I({putSortable:st,cloneEl:et,targetEl:V,rootEl:Q,oldIndex:ot,oldDraggableIndex:rt,newIndex:it,newDraggableIndex:at},t))}var V,Z,$,Q,J,tt,et,nt,ot,it,rt,at,lt,st,ct,ut,dt,ht,ft,pt,gt,mt,vt,bt,yt,wt=!1,Et=!1,Dt=[],St=!1,_t=!1,Ct=[],Tt=!1,xt=[],Ot="undefined"!=typeof document,Mt=n,At=w||y?"cssFloat":"float",Nt=Ot&&!c&&!n&&"draggable"in document.createElement("div"),It=function(){if(Ot){if(y)return!1;var t=document.createElement("x");return t.style.cssText="pointer-events:auto","auto"===t.style.pointerEvents}}(),Pt=function(t,e){var n=R(t),o=parseInt(n.width)-parseInt(n.paddingLeft)-parseInt(n.paddingRight)-parseInt(n.borderLeftWidth)-parseInt(n.borderRightWidth),i=B(t,0,e),r=B(t,1,e),a=i&&R(i),l=r&&R(r),s=a&&parseInt(a.marginLeft)+parseInt(a.marginRight)+X(i).width,t=l&&parseInt(l.marginLeft)+parseInt(l.marginRight)+X(r).width;if("flex"===n.display)return"column"===n.flexDirection||"column-reverse"===n.flexDirection?"vertical":"horizontal";if("grid"===n.display)return n.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(i&&a.float&&"none"!==a.float){e="left"===a.float?"left":"right";return!r||"both"!==l.clear&&l.clear!==e?"horizontal":"vertical"}return i&&("block"===a.display||"flex"===a.display||"table"===a.display||"grid"===a.display||o<=s&&"none"===n[At]||r&&"none"===n[At]&&o<s+t)?"vertical":"horizontal"},kt=function(t){function l(r,a){return function(t,e,n,o){var i=t.options.group.name&&e.options.group.name&&t.options.group.name===e.options.group.name;if(null==r&&(a||i))return!0;if(null==r||!1===r)return!1;if(a&&"clone"===r)return r;if("function"==typeof r)return l(r(t,e,n,o),a)(t,e,n,o);e=(a?t:e).options.group.name;return!0===r||"string"==typeof r&&r===e||r.join&&-1<r.indexOf(e)}}var e={},n=t.group;n&&"object"==o(n)||(n={name:n}),e.name=n.name,e.checkPull=l(n.pull,!0),e.checkPut=l(n.put),e.revertClone=n.revertClone,t.group=e},Rt=function(){!It&&$&&R($,"display","none")},Xt=function(){!It&&$&&R($,"display","")};Ot&&!c&&document.addEventListener("click",function(t){if(Et)return t.preventDefault(),t.stopPropagation&&t.stopPropagation(),t.stopImmediatePropagation&&t.stopImmediatePropagation(),Et=!1},!0);function Yt(t){if(V){t=t.touches?t.touches[0]:t;var e=(i=t.clientX,r=t.clientY,Dt.some(function(t){var e=t[K].options.emptyInsertThreshold;if(e&&!F(t)){var n=X(t),o=i>=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function Bt(t){V&&V.parentNode[K]._isOutsideThisEl(t.target)}function Ft(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Pt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Ft.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(n in W.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in kt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&Nt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),Dt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,x())}function jt(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Ht(t){t.draggable=!1}function Lt(){Tt=!1}function Kt(t){return setTimeout(t,0)}function Wt(t){return clearTimeout(t)}Ft.prototype={constructor:Ft,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(mt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,V):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){xt.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&xt.push(o)}}(o),!V&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||tt===l)){if(ot=j(l),rt=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return q({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),G("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return q({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),G("filter",n,{evt:e}),!0}))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!V&&n.parentNode===r&&(o=X(n),Q=r,Z=(V=n).parentNode,J=V.nextSibling,tt=n,lt=a.group,ct={target:Ft.dragged=V,clientX:(e||t).clientX,clientY:(e||t).clientY},ft=ct.clientX-o.left,pt=ct.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,V.style["will-change"]="all",o=function(){G("delayEnded",i,{evt:t}),Ft.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(V.draggable=!0),i._triggerDragStart(t,e),q({sortable:i,name:"choose",originalEvent:t}),k(V,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){b(V,t.trim(),Ht)}),h(l,"dragover",Yt),h(l,"mousemove",Yt),h(l,"touchmove",Yt),h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,V.draggable=!0),G("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():Ft.eventCanceled?this._onDrop():(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){V&&Ht(V),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._disableDelayedDrag),f(t,"touchend",this._disableDelayedDrag),f(t,"touchcancel",this._disableDelayedDrag),f(t,"mousemove",this._delayedDragTouchMoveHandler),f(t,"touchmove",this._delayedDragTouchMoveHandler),f(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(V,"dragend",this),h(Q,"dragstart",this._onDragStart));try{document.selection?Kt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;wt=!1,Q&&V?(G("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Bt),n=this.options,t||k(V,n.dragClass,!1),k(V,n.ghostClass,!0),Ft.active=this,t&&this._appendGhost(),q({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(ut){this._lastX=ut.clientX,this._lastY=ut.clientY,Rt();for(var t=document.elementFromPoint(ut.clientX,ut.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(ut.clientX,ut.clientY))!==e;)e=t;if(V.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:ut.clientX,clientY:ut.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=(t=e).parentNode);Xt()}},_onTouchMove:function(t){if(ct){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=$&&v($,!0),a=$&&r&&r.a,l=$&&r&&r.d,e=Mt&&yt&&E(yt),a=(i.clientX-ct.clientX+o.x)/(a||1)+(e?e[0]-Ct[0]:0)/(a||1),l=(i.clientY-ct.clientY+o.y)/(l||1)+(e?e[1]-Ct[1]:0)/(l||1);if(!Ft.active&&!wt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))<n)return;this._onDragStart(t,!0)}$&&(r?(r.e+=a-(dt||0),r.f+=l-(ht||0)):r={a:1,b:0,c:0,d:1,e:a,f:l},r="matrix(".concat(r.a,",").concat(r.b,",").concat(r.c,",").concat(r.d,",").concat(r.e,",").concat(r.f,")"),R($,"webkitTransform",r),R($,"mozTransform",r),R($,"msTransform",r),R($,"transform",r),dt=a,ht=l,ut=i),t.cancelable&&t.preventDefault()}},_appendGhost:function(){if(!$){var t=this.options.fallbackOnBody?document.body:Q,e=X(V,!0,Mt,!0,t),n=this.options;if(Mt){for(yt=t;"static"===R(yt,"position")&&"none"===R(yt,"transform")&&yt!==document;)yt=yt.parentNode;yt!==document.body&&yt!==document.documentElement?(yt===document&&(yt=O()),e.top+=yt.scrollTop,e.left+=yt.scrollLeft):yt=O(),Ct=E(yt)}k($=V.cloneNode(!0),n.ghostClass,!1),k($,n.fallbackClass,!0),k($,n.dragClass,!0),R($,"transition",""),R($,"transform",""),R($,"box-sizing","border-box"),R($,"margin",0),R($,"top",e.top),R($,"left",e.left),R($,"width",e.width),R($,"height",e.height),R($,"opacity","0.8"),R($,"position",Mt?"absolute":"fixed"),R($,"zIndex","100000"),R($,"pointerEvents","none"),Ft.ghost=$,t.appendChild($),R($,"transform-origin",ft/parseInt($.style.width)*100+"% "+pt/parseInt($.style.height)*100+"%")}},_onDragStart:function(t,e){var n=this,o=t.dataTransfer,i=n.options;G("dragStart",this,{evt:t}),Ft.eventCanceled?this._onDrop():(G("setupClone",this),Ft.eventCanceled||((et=_(V)).removeAttribute("id"),et.draggable=!1,et.style["will-change"]="",this._hideClone(),k(et,this.options.chosenClass,!1),Ft.clone=et),n.cloneId=Kt(function(){G("clone",n),Ft.eventCanceled||(n.options.removeCloneOnHide||Q.insertBefore(et,V),n._hideClone(),q({sortable:n,name:"clone"}))}),e||k(V,i.dragClass,!0),e?(Et=!0,n._loopId=setInterval(n._emulateDragOver,50)):(f(document,"mouseup",n._onDrop),f(document,"touchend",n._onDrop),f(document,"touchcancel",n._onDrop),o&&(o.effectAllowed="move",i.setData&&i.setData.call(n,o,V)),h(document,"drop",n),R(V,"transform","translateZ(0)")),wt=!0,n._dragStartId=Kt(n._dragStarted.bind(n,e,t)),h(document,"selectstart",n),gt=!0,u&&R(document.body,"user-select","none"))},_onDragOver:function(n){var o,i,r,t,e,a=this.el,l=n.target,s=this.options,c=s.group,u=Ft.active,d=lt===c,h=s.sort,f=st||u,p=this,g=!1;if(!Tt){if(void 0!==n.preventDefault&&n.cancelable&&n.preventDefault(),l=P(l,s.draggable,a,!0),O("dragOver"),Ft.eventCanceled)return g;if(V.contains(n.target)||l.animated&&l.animatingX&&l.animatingY||p._ignoreWhileAnimating===l)return A(!1);if(Et=!1,u&&!s.disabled&&(d?h||(i=Z!==Q):st===this||(this.lastPutMode=lt.checkPull(this,u,V,n))&&c.checkPut(this,u,V,n))){if(r="vertical"===this._getDirection(n,l),o=X(V),O("dragOverValid"),Ft.eventCanceled)return g;if(i)return Z=Q,M(),this._hideClone(),O("revert"),Ft.eventCanceled||(J?Q.insertBefore(V,J):Q.appendChild(V)),A(!0);var m=F(a,s.draggable);if(m&&(S=n,c=r,x=X(F((D=this).el,D.options.draggable)),D=L(D.el,D.options,$),!(c?S.clientX>D.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>D.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,$),e?t.clientX<_.left-10||t.clientY<C.top&&t.clientX<C.right:t.clientY<_.top-10||t.clientY<C.bottom&&t.clientX<C.left)){var v=B(a,0,s,!0);if(v===V)return A(!1);if(E=X(l=v),!1!==jt(Q,a,V,o,l,E,n,!1))return M(),a.insertBefore(V,v),Z=a,N(),A(!0)}else if(l.parentNode===a){var b,y,w,E=X(l),D=V.parentNode!==a,S=(S=V.animated&&V.toRect||o,x=l.animated&&l.toRect||E,_=(e=r)?S.left:S.top,t=e?S.right:S.bottom,C=e?S.width:S.height,v=e?x.left:x.top,S=e?x.right:x.bottom,x=e?x.width:x.height,!(_===v||t===S||_+C/2===v+x/2)),_=r?"top":"left",C=Y(l,"top","top")||Y(V,"top","top"),v=C?C.scrollTop:void 0;if(mt!==l&&(y=E[_],St=!1,_t=!S&&s.invertSwap||D),0!==(b=function(t,e,n,o,i,r,a,l){var s=o?t.clientY:t.clientX,c=o?n.height:n.width,t=o?n.top:n.left,o=o?n.bottom:n.right,n=!1;if(!a)if(l&&bt<c*i){if(St=!St&&(1===vt?t+c*r/2<s:s<o-c*r/2)?!0:St)n=!0;else if(1===vt?s<t+bt:o-bt<s)return-vt}else if(t+c*(1-i)/2<s&&s<o-c*(1-i)/2)return function(t){return j(V)<j(t)?1:-1}(e);if((n=n||a)&&(s<t+c*r/2||o-c*r/2<s))return t+c/2<s?1:-1;return 0}(n,l,E,r,S?1:s.swapThreshold,null==s.invertedSwapThreshold?s.swapThreshold:s.invertedSwapThreshold,_t,mt===l)))for(var T=j(V);(w=Z.children[T-=b])&&("none"===R(w,"display")||w===$););if(0===b||w===l)return A(!1);vt=b;var x=(mt=l).nextElementSibling,D=!1,S=jt(Q,a,V,o,l,E,n,D=1===b);if(!1!==S)return 1!==S&&-1!==S||(D=1===S),Tt=!0,setTimeout(Lt,30),M(),D&&!x?a.appendChild(V):l.parentNode.insertBefore(V,D?x:l),C&&H(C,0,v-C.scrollTop),Z=V.parentNode,void 0===y||_t||(bt=Math.abs(y-X(l)[_])),N(),A(!0)}}else{if(m===V)return A(!1);if((l=m&&a===n.target?m:l)&&(E=X(l)),!1!==jt(Q,a,V,o,l,E,n,!!l))return M(),m&&m.nextSibling?a.insertBefore(V,m.nextSibling):a.appendChild(V),Z=a,N(),A(!0)}if(a.contains(V))return A(!1)}return!1}function O(t,e){G(t,p,I({evt:n,isOwner:d,axis:r?"vertical":"horizontal",revert:i,dragRect:o,targetRect:E,canSort:h,fromSortable:f,target:l,completed:A,onMove:function(t,e){return jt(Q,a,V,o,t,X(t),n,e)},changed:N},e))}function M(){O("dragOverAnimationCapture"),p.captureAnimationState(),p!==f&&f.captureAnimationState()}function A(t){return O("dragOverCompleted",{insertion:t}),t&&(d?u._hideClone():u._showClone(p),p!==f&&(k(V,(st||u).options.ghostClass,!1),k(V,s.ghostClass,!0)),st!==p&&p!==Ft.active?st=p:p===Ft.active&&st&&(st=null),f===p&&(p._ignoreWhileAnimating=l),p.animateAll(function(){O("dragOverAnimationComplete"),p._ignoreWhileAnimating=null}),p!==f&&(f.animateAll(),f._ignoreWhileAnimating=null)),(l===V&&!V.animated||l===a&&!l.animated)&&(mt=null),s.dragoverBubble||n.rootEl||l===document||(V.parentNode[K]._isOutsideThisEl(n.target),t||Yt(n)),!s.dragoverBubble&&n.stopPropagation&&n.stopPropagation(),g=!0}function N(){it=j(V),at=j(V,s.draggable),q({sortable:p,name:"change",toEl:a,newIndex:it,newDraggableIndex:at,originalEvent:n})}},_ignoreWhileAnimating:null,_offMoveEvents:function(){f(document,"mousemove",this._onTouchMove),f(document,"touchmove",this._onTouchMove),f(document,"pointermove",this._onTouchMove),f(document,"dragover",Yt),f(document,"mousemove",Yt),f(document,"touchmove",Yt)},_offUpEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._onDrop),f(t,"touchend",this._onDrop),f(t,"pointerup",this._onDrop),f(t,"touchcancel",this._onDrop),f(document,"selectstart",this)},_onDrop:function(t){var e=this.el,n=this.options;it=j(V),at=j(V,n.draggable),G("drop",this,{evt:t}),Z=V&&V.parentNode,it=j(V),at=j(V,n.draggable),Ft.eventCanceled||(St=_t=wt=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),Wt(this.cloneId),Wt(this._dragStartId),this.nativeDraggable&&(f(document,"drop",this),f(e,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),u&&R(document.body,"user-select",""),R(V,"transform",""),t&&(gt&&(t.cancelable&&t.preventDefault(),n.dropBubble||t.stopPropagation()),$&&$.parentNode&&$.parentNode.removeChild($),(Q===Z||st&&"clone"!==st.lastPutMode)&&et&&et.parentNode&&et.parentNode.removeChild(et),V&&(this.nativeDraggable&&f(V,"dragend",this),Ht(V),V.style["will-change"]="",gt&&!wt&&k(V,(st||this).options.ghostClass,!1),k(V,this.options.chosenClass,!1),q({sortable:this,name:"unchoose",toEl:Z,newIndex:null,newDraggableIndex:null,originalEvent:t}),Q!==Z?(0<=it&&(q({rootEl:Z,name:"add",toEl:Z,fromEl:Q,originalEvent:t}),q({sortable:this,name:"remove",toEl:Z,originalEvent:t}),q({rootEl:Z,name:"sort",toEl:Z,fromEl:Q,originalEvent:t}),q({sortable:this,name:"sort",toEl:Z,originalEvent:t})),st&&st.save()):it!==ot&&0<=it&&(q({sortable:this,name:"update",toEl:Z,originalEvent:t}),q({sortable:this,name:"sort",toEl:Z,originalEvent:t})),Ft.active&&(null!=it&&-1!==it||(it=ot,at=rt),q({sortable:this,name:"end",toEl:Z,originalEvent:t}),this.save())))),this._nulling()},_nulling:function(){G("nulling",this),Q=V=Z=$=J=et=tt=nt=ct=ut=gt=it=at=ot=rt=mt=vt=st=lt=Ft.dragged=Ft.ghost=Ft.clone=Ft.active=null,xt.forEach(function(t){t.checked=!0}),xt.length=dt=ht=0},handleEvent:function(t){switch(t.type){case"drop":case"dragend":this._onDrop(t);break;case"dragenter":case"dragover":V&&(this._onDragOver(t),function(t){t.dataTransfer&&(t.dataTransfer.dropEffect="move");t.cancelable&&t.preventDefault()}(t));break;case"selectstart":t.preventDefault()}},toArray:function(){for(var t,e=[],n=this.el.children,o=0,i=n.length,r=this.options;o<i;o++)P(t=n[o],r.draggable,this.el,!1)&&e.push(t.getAttribute(r.dataIdAttr)||function(t){var e=t.tagName+t.className+t.src+t.href+t.textContent,n=e.length,o=0;for(;n--;)o+=e.charCodeAt(n);return o.toString(36)}(t));return e},sort:function(t,e){var n={},o=this.el;this.toArray().forEach(function(t,e){e=o.children[e];P(e,this.options.draggable,o,!1)&&(n[t]=e)},this),e&&this.captureAnimationState(),t.forEach(function(t){n[t]&&(o.removeChild(n[t]),o.appendChild(n[t]))}),e&&this.animateAll()},save:function(){var t=this.options.store;t&&t.set&&t.set(this)},closest:function(t,e){return P(t,e||this.options.draggable,this.el,!1)},option:function(t,e){var n=this.options;if(void 0===e)return n[t];var o=W.modifyOption(this,t,e);n[t]=void 0!==o?o:e,"group"===t&&kt(n)},destroy:function(){G("destroy",this);var t=this.el;t[K]=null,f(t,"mousedown",this._onTapStart),f(t,"touchstart",this._onTapStart),f(t,"pointerdown",this._onTapStart),this.nativeDraggable&&(f(t,"dragover",this),f(t,"dragenter",this)),Array.prototype.forEach.call(t.querySelectorAll("[draggable]"),function(t){t.removeAttribute("draggable")}),this._onDrop(),this._disableDelayedDragEvents(),Dt.splice(Dt.indexOf(this.el),1),this.el=t=null},_hideClone:function(){nt||(G("hideClone",this),Ft.eventCanceled||(R(et,"display","none"),this.options.removeCloneOnHide&&et.parentNode&&et.parentNode.removeChild(et),nt=!0))},_showClone:function(t){"clone"===t.lastPutMode?nt&&(G("showClone",this),Ft.eventCanceled||(V.parentNode!=Q||this.options.group.revertClone?J?Q.insertBefore(et,J):Q.appendChild(et):Q.insertBefore(et,V),this.options.group.revertClone&&this.animate(V,et),R(et,"display",""),nt=!1)):this._hideClone()}},Ot&&h(document,"touchmove",function(t){(Ft.active||wt)&&t.cancelable&&t.preventDefault()}),Ft.utils={on:h,off:f,css:R,find:b,is:function(t,e){return!!P(t,e,t,!1)},extend:function(t,e){if(t&&e)for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t},throttle:S,closest:P,toggleClass:k,clone:_,index:j,nextTick:Kt,cancelNextTick:Wt,detectDirection:Pt,getChild:B},Ft.get=function(t){return t[K]},Ft.mount=function(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];(e=e[0].constructor===Array?e[0]:e).forEach(function(t){if(!t.prototype||!t.prototype.constructor)throw"Sortable: Mounted plugin must be a constructor function, not ".concat({}.toString.call(t));t.utils&&(Ft.utils=I(I({},Ft.utils),t.utils)),W.mount(t)})},Ft.create=function(t,e){return new Ft(t,e)};var zt,Gt,Ut,qt,Vt,Zt,$t=[],Qt=!(Ft.version="1.15.2");function Jt(){$t.forEach(function(t){clearInterval(t.pid)}),$t=[]}function te(){clearInterval(Zt)}var ee,ne=S(function(n,t,e,o){if(t.scroll){var i,r=(n.touches?n.touches[0]:n).clientX,a=(n.touches?n.touches[0]:n).clientY,l=t.scrollSensitivity,s=t.scrollSpeed,c=O(),u=!1;Gt!==e&&(Gt=e,Jt(),zt=t.scroll,i=t.scrollFn,!0===zt&&(zt=M(e,!0)));var d=0,h=zt;do{var f=h,p=X(f),g=p.top,m=p.bottom,v=p.left,b=p.right,y=p.width,w=p.height,E=void 0,D=void 0,S=f.scrollWidth,_=f.scrollHeight,C=R(f),T=f.scrollLeft,p=f.scrollTop,D=f===c?(E=y<S&&("auto"===C.overflowX||"scroll"===C.overflowX||"visible"===C.overflowX),w<_&&("auto"===C.overflowY||"scroll"===C.overflowY||"visible"===C.overflowY)):(E=y<S&&("auto"===C.overflowX||"scroll"===C.overflowX),w<_&&("auto"===C.overflowY||"scroll"===C.overflowY)),T=E&&(Math.abs(b-r)<=l&&T+y<S)-(Math.abs(v-r)<=l&&!!T),p=D&&(Math.abs(m-a)<=l&&p+w<_)-(Math.abs(g-a)<=l&&!!p);if(!$t[d])for(var x=0;x<=d;x++)$t[x]||($t[x]={});$t[d].vx==T&&$t[d].vy==p&&$t[d].el===f||($t[d].el=f,$t[d].vx=T,$t[d].vy=p,clearInterval($t[d].pid),0==T&&0==p||(u=!0,$t[d].pid=setInterval(function(){o&&0===this.layer&&Ft.active._onTouchMove(Vt);var t=$t[this.layer].vy?$t[this.layer].vy*s:0,e=$t[this.layer].vx?$t[this.layer].vx*s:0;"function"==typeof i&&"continue"!==i.call(Ft.dragged.parentNode[K],e,t,n,Vt,$t[this.layer].el)||H($t[this.layer].el,e,t)}.bind({layer:d}),24))),d++}while(t.bubbleScroll&&h!==c&&(h=M(h,!1)));Qt=u}},30),c=function(t){var e=t.originalEvent,n=t.putSortable,o=t.dragEl,i=t.activeSortable,r=t.dispatchSortableEvent,a=t.hideGhostForTarget,t=t.unhideGhostForTarget;e&&(i=n||i,a(),e=e.changedTouches&&e.changedTouches.length?e.changedTouches[0]:e,e=document.elementFromPoint(e.clientX,e.clientY),t(),i&&!i.el.contains(e)&&(r("spill"),this.onSpill({dragEl:o,putSortable:n})))};function oe(){}function ie(){}oe.prototype={startIndex:null,dragStart:function(t){t=t.oldDraggableIndex;this.startIndex=t},onSpill:function(t){var e=t.dragEl,n=t.putSortable;this.sortable.captureAnimationState(),n&&n.captureAnimationState();t=B(this.sortable.el,this.startIndex,this.options);t?this.sortable.el.insertBefore(e,t):this.sortable.el.appendChild(e),this.sortable.animateAll(),n&&n.animateAll()},drop:c},a(oe,{pluginName:"revertOnSpill"}),ie.prototype={onSpill:function(t){var e=t.dragEl,t=t.putSortable||this.sortable;t.captureAnimationState(),e.parentNode&&e.parentNode.removeChild(e),t.animateAll()},drop:c},a(ie,{pluginName:"removeOnSpill"});var re,ae,le,se,ce,ue=[],de=[],he=!1,fe=!1,pe=!1;function ge(n,o){de.forEach(function(t,e){e=o.children[t.sortableIndex+(n?Number(e):0)];e?o.insertBefore(t,e):o.appendChild(t)})}function me(){ue.forEach(function(t){t!==le&&t.parentNode&&t.parentNode.removeChild(t)})}return Ft.mount(new function(){function t(){for(var t in this.defaults={scroll:!0,forceAutoScrollFallback:!1,scrollSensitivity:30,scrollSpeed:10,bubbleScroll:!0},this)"_"===t.charAt(0)&&"function"==typeof this[t]&&(this[t]=this[t].bind(this))}return t.prototype={dragStarted:function(t){t=t.originalEvent;this.sortable.nativeDraggable?h(document,"dragover",this._handleAutoScroll):this.options.supportPointer?h(document,"pointermove",this._handleFallbackAutoScroll):t.touches?h(document,"touchmove",this._handleFallbackAutoScroll):h(document,"mousemove",this._handleFallbackAutoScroll)},dragOverCompleted:function(t){t=t.originalEvent;this.options.dragOverBubble||t.rootEl||this._handleAutoScroll(t)},drop:function(){this.sortable.nativeDraggable?f(document,"dragover",this._handleAutoScroll):(f(document,"pointermove",this._handleFallbackAutoScroll),f(document,"touchmove",this._handleFallbackAutoScroll),f(document,"mousemove",this._handleFallbackAutoScroll)),te(),Jt(),clearTimeout(g),g=void 0},nulling:function(){Vt=Gt=zt=Qt=Zt=Ut=qt=null,$t.length=0},_handleFallbackAutoScroll:function(t){this._handleAutoScroll(t,!0)},_handleAutoScroll:function(e,n){var o,i=this,r=(e.touches?e.touches[0]:e).clientX,a=(e.touches?e.touches[0]:e).clientY,t=document.elementFromPoint(r,a);Vt=e,n||this.options.forceAutoScrollFallback||w||y||u?(ne(e,this.options,t,n),o=M(t,!0),!Qt||Zt&&r===Ut&&a===qt||(Zt&&te(),Zt=setInterval(function(){var t=M(document.elementFromPoint(r,a),!0);t!==o&&(o=t,Jt()),ne(e,i.options,t,n)},10),Ut=r,qt=a)):this.options.bubbleScroll&&M(t,!0)!==O()?ne(e,this.options,M(t,!1),!1):Jt()}},a(t,{pluginName:"scroll",initializeByDefault:!0})}),Ft.mount(ie,oe),Ft.mount(new function(){function t(){this.defaults={swapClass:"sortable-swap-highlight"}}return t.prototype={dragStart:function(t){t=t.dragEl;ee=t},dragOverValid:function(t){var e=t.completed,n=t.target,o=t.onMove,i=t.activeSortable,r=t.changed,a=t.cancel;i.options.swap&&(t=this.sortable.el,i=this.options,n&&n!==t&&(t=ee,ee=!1!==o(n)?(k(n,i.swapClass,!0),n):null,t&&t!==ee&&k(t,i.swapClass,!1)),r(),e(!0),a())},drop:function(t){var e,n,o=t.activeSortable,i=t.putSortable,r=t.dragEl,a=i||this.sortable,l=this.options;ee&&k(ee,l.swapClass,!1),ee&&(l.swap||i&&i.options.swap)&&r!==ee&&(a.captureAnimationState(),a!==o&&o.captureAnimationState(),n=ee,t=(e=r).parentNode,l=n.parentNode,t&&l&&!t.isEqualNode(n)&&!l.isEqualNode(e)&&(i=j(e),r=j(n),t.isEqualNode(l)&&i<r&&r++,t.insertBefore(n,t.children[i]),l.insertBefore(e,l.children[r])),a.animateAll(),a!==o&&o.animateAll())},nulling:function(){ee=null}},a(t,{pluginName:"swap",eventProperties:function(){return{swapItem:ee}}})}),Ft.mount(new function(){function t(o){for(var t in this)"_"===t.charAt(0)&&"function"==typeof this[t]&&(this[t]=this[t].bind(this));o.options.avoidImplicitDeselect||(o.options.supportPointer?h(document,"pointerup",this._deselectMultiDrag):(h(document,"mouseup",this._deselectMultiDrag),h(document,"touchend",this._deselectMultiDrag))),h(document,"keydown",this._checkKeyDown),h(document,"keyup",this._checkKeyUp),this.defaults={selectedClass:"sortable-selected",multiDragKey:null,avoidImplicitDeselect:!1,setData:function(t,e){var n="";ue.length&&ae===o?ue.forEach(function(t,e){n+=(e?", ":"")+t.textContent}):n=e.textContent,t.setData("Text",n)}}}return t.prototype={multiDragKeyDown:!1,isMultiDrag:!1,delayStartGlobal:function(t){t=t.dragEl;le=t},delayEnded:function(){this.isMultiDrag=~ue.indexOf(le)},setupClone:function(t){var e=t.sortable,t=t.cancel;if(this.isMultiDrag){for(var n=0;n<ue.length;n++)de.push(_(ue[n])),de[n].sortableIndex=ue[n].sortableIndex,de[n].draggable=!1,de[n].style["will-change"]="",k(de[n],this.options.selectedClass,!1),ue[n]===le&&k(de[n],this.options.chosenClass,!1);e._hideClone(),t()}},clone:function(t){var e=t.sortable,n=t.rootEl,o=t.dispatchSortableEvent,t=t.cancel;this.isMultiDrag&&(this.options.removeCloneOnHide||ue.length&&ae===e&&(ge(!0,n),o("clone"),t()))},showClone:function(t){var e=t.cloneNowShown,n=t.rootEl,t=t.cancel;this.isMultiDrag&&(ge(!1,n),de.forEach(function(t){R(t,"display","")}),e(),ce=!1,t())},hideClone:function(t){var e=this,n=(t.sortable,t.cloneNowHidden),t=t.cancel;this.isMultiDrag&&(de.forEach(function(t){R(t,"display","none"),e.options.removeCloneOnHide&&t.parentNode&&t.parentNode.removeChild(t)}),n(),ce=!0,t())},dragStartGlobal:function(t){t.sortable;!this.isMultiDrag&&ae&&ae.multiDrag._deselectMultiDrag(),ue.forEach(function(t){t.sortableIndex=j(t)}),ue=ue.sort(function(t,e){return t.sortableIndex-e.sortableIndex}),pe=!0},dragStarted:function(t){var e,n=this,t=t.sortable;this.isMultiDrag&&(this.options.sort&&(t.captureAnimationState(),this.options.animation&&(ue.forEach(function(t){t!==le&&R(t,"position","absolute")}),e=X(le,!1,!0,!0),ue.forEach(function(t){t!==le&&C(t,e)}),he=fe=!0)),t.animateAll(function(){he=fe=!1,n.options.animation&&ue.forEach(function(t){T(t)}),n.options.sort&&me()}))},dragOver:function(t){var e=t.target,n=t.completed,t=t.cancel;fe&&~ue.indexOf(e)&&(n(!1),t())},revert:function(t){var n,o,e=t.fromSortable,i=t.rootEl,r=t.sortable,a=t.dragRect;1<ue.length&&(ue.forEach(function(t){r.addAnimationState({target:t,rect:fe?X(t):a}),T(t),t.fromRect=a,e.removeAnimationState(t)}),fe=!1,n=!this.options.removeCloneOnHide,o=i,ue.forEach(function(t,e){e=o.children[t.sortableIndex+(n?Number(e):0)];e?o.insertBefore(t,e):o.appendChild(t)}))},dragOverCompleted:function(t){var e,n=t.sortable,o=t.isOwner,i=t.insertion,r=t.activeSortable,a=t.parentEl,l=t.putSortable,t=this.options;i&&(o&&r._hideClone(),he=!1,t.animation&&1<ue.length&&(fe||!o&&!r.options.sort&&!l)&&(e=X(le,!1,!0,!0),ue.forEach(function(t){t!==le&&(C(t,e),a.appendChild(t))}),fe=!0),o||(fe||me(),1<ue.length?(o=ce,r._showClone(n),r.options.animation&&!ce&&o&&de.forEach(function(t){r.addAnimationState({target:t,rect:se}),t.fromRect=se,t.thisAnimationDuration=null})):r._showClone(n)))},dragOverAnimationCapture:function(t){var e=t.dragRect,n=t.isOwner,t=t.activeSortable;ue.forEach(function(t){t.thisAnimationDuration=null}),t.options.animation&&!n&&t.multiDrag.isMultiDrag&&(se=a({},e),e=v(le,!0),se.top-=e.f,se.left-=e.e)},dragOverAnimationComplete:function(){fe&&(fe=!1,me())},drop:function(t){var e=t.originalEvent,n=t.rootEl,o=t.parentEl,i=t.sortable,r=t.dispatchSortableEvent,a=t.oldIndex,l=t.putSortable,s=l||this.sortable;if(e){var c,u,d,h=this.options,f=o.children;if(!pe)if(h.multiDragKey&&!this.multiDragKeyDown&&this._deselectMultiDrag(),k(le,h.selectedClass,!~ue.indexOf(le)),~ue.indexOf(le))ue.splice(ue.indexOf(le),1),re=null,z({sortable:i,rootEl:n,name:"deselect",targetEl:le,originalEvent:e});else{if(ue.push(le),z({sortable:i,rootEl:n,name:"select",targetEl:le,originalEvent:e}),e.shiftKey&&re&&i.el.contains(re)){var p=j(re),t=j(le);if(~p&&~t&&p!==t)for(var g,m=p<t?(g=p,t):(g=t,p+1);g<m;g++)~ue.indexOf(f[g])||(k(f[g],h.selectedClass,!0),ue.push(f[g]),z({sortable:i,rootEl:n,name:"select",targetEl:f[g],originalEvent:e}))}else re=le;ae=s}pe&&this.isMultiDrag&&(fe=!1,(o[K].options.sort||o!==n)&&1<ue.length&&(c=X(le),u=j(le,":not(."+this.options.selectedClass+")"),!he&&h.animation&&(le.thisAnimationDuration=null),s.captureAnimationState(),he||(h.animation&&(le.fromRect=c,ue.forEach(function(t){var e;t.thisAnimationDuration=null,t!==le&&(e=fe?X(t):c,t.fromRect=e,s.addAnimationState({target:t,rect:e}))})),me(),ue.forEach(function(t){f[u]?o.insertBefore(t,f[u]):o.appendChild(t),u++}),a===j(le)&&(d=!1,ue.forEach(function(t){t.sortableIndex!==j(t)&&(d=!0)}),d&&(r("update"),r("sort")))),ue.forEach(function(t){T(t)}),s.animateAll()),ae=s),(n===o||l&&"clone"!==l.lastPutMode)&&de.forEach(function(t){t.parentNode&&t.parentNode.removeChild(t)})}},nullingGlobal:function(){this.isMultiDrag=pe=!1,de.length=0},destroyGlobal:function(){this._deselectMultiDrag(),f(document,"pointerup",this._deselectMultiDrag),f(document,"mouseup",this._deselectMultiDrag),f(document,"touchend",this._deselectMultiDrag),f(document,"keydown",this._checkKeyDown),f(document,"keyup",this._checkKeyUp)},_deselectMultiDrag:function(t){if(!(void 0!==pe&&pe||ae!==this.sortable||t&&P(t.target,this.options.draggable,this.sortable.el,!1)||t&&0!==t.button))for(;ue.length;){var e=ue[0];k(e,this.options.selectedClass,!1),ue.shift(),z({sortable:this.sortable,rootEl:this.sortable.el,name:"deselect",targetEl:e,originalEvent:t})}},_checkKeyDown:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!0)},_checkKeyUp:function(t){t.key===this.options.multiDragKey&&(this.multiDragKeyDown=!1)}},a(t,{pluginName:"multiDrag",utils:{select:function(t){var e=t.parentNode[K];e&&e.options.multiDrag&&!~ue.indexOf(t)&&(ae&&ae!==e&&(ae.multiDrag._deselectMultiDrag(),ae=e),k(t,e.options.selectedClass,!0),ue.push(t))},deselect:function(t){var e=t.parentNode[K],n=ue.indexOf(t);e&&e.options.multiDrag&&~n&&(k(t,e.options.selectedClass,!1),ue.splice(n,1))}},eventProperties:function(){var n=this,o=[],i=[];return ue.forEach(function(t){var e;o.push({multiDragElement:t,index:t.sortableIndex}),e=fe&&t!==le?-1:fe?j(t,":not(."+n.options.selectedClass+")"):j(t),i.push({multiDragElement:t,index:e})}),{items:r(ue),clones:[].concat(de),oldIndicies:o,newIndicies:i}},optionListeners:{multiDragKey:function(t){return"ctrl"===(t=t.toLowerCase())?t="Control":1<t.length&&(t=t.charAt(0).toUpperCase()+t.substr(1)),t}}})}),Ft}); diff --git a/assets/toolbox.scss b/assets/toolbox.scss index f374797..beab708 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -37,3 +37,9 @@ #plugin-toolbox-toolbox-index .messagebox .messagebox_buttons .close { display: none; } + +.dropable-placeholder { + background-color: white !important; + border: #d3d3d3 2px dashed !important; + content-visibility: hidden !important; +} diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 81dd9bb..d7c4dac 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -2,6 +2,12 @@ class ToolboxController extends PluginController { + public function before_filter(&$action, &$args) + { + PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/sortable.min.js'); + parent::before_filter($action, $args); + } + public function index_action() { Navigation::activateItem('/contents/toolbox'); @@ -45,4 +51,15 @@ class ToolboxController extends PluginController readfile($icon); exit; } + + public function vue_action() + { + + } + + public function vue_cdn_action() + { + $this->message = 'Nein, danke'; + + } } diff --git a/views/toolbox/index.php b/views/toolbox/index.php index b4a28b7..a50761f 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,6 +1,6 @@ -<ul class="content-items toolbox"> - <? foreach ($tools as $tool) : ?> - <li class="content-item"> +<ul id="simpleList" class="content-items toolbox"> + <? foreach ($tools as $key => $tool) : ?> + <li data-id="<?= $tool->id ?>" class="content-item"> <div class="top_icons"> <? if ($tool['long_description']) : ?> <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" @@ -30,6 +30,16 @@ <? endforeach ?> </ul> +<script> + // Simple list + Sortable.create(simpleList, { + animation: 150, + ghostClass: 'dropable-placeholder', + dataIdAttr: 'data-id', + + }); +</script> + <? $actions = new ActionsWidget(); @@ -40,3 +50,5 @@ $actions->addLink( ['data-dialog' => 1] ); Sidebar::Get()->addWidget($actions); + + -- GitLab From c23c1a93c2b6efb1a6fefca6df0d8b9f72f65f4e Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 10:34:15 +0200 Subject: [PATCH 02/31] Add Toolbox user model --- lib/ToolboxUserTool.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/ToolboxUserTool.php diff --git a/lib/ToolboxUserTool.php b/lib/ToolboxUserTool.php new file mode 100644 index 0000000..b92fd06 --- /dev/null +++ b/lib/ToolboxUserTool.php @@ -0,0 +1,29 @@ +<?php + +class ToolboxUserTool extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'toolbox_user_tools'; + $config['belongs_to']['toolbox_tools'] = [ + 'class_name' => ToolboxTool::class, + 'foreign_key' => 'tool_id', + ]; + $config['belongs_to']['user'] = [ + 'class_name' => 'User', + 'foreign_key' => 'user_id', + 'assoc_foreign_key' => 'user_id' + ]; + parent::configure($config); + } + + + /** + * @param $toolId + * @return ToolboxUserTool|null + */ + public static function getUserTool($toolId) + { + return self::findOneBySQL("tool_id = ? AND user_id = ?", [$toolId, $GLOBALS['user']->id]); + } +} -- GitLab From d5e0f210b93834729d1a4f8a6b8409c4fc1cd3e9 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 10:36:08 +0200 Subject: [PATCH 03/31] Make tools dragable --- assets/toolbox.scss | 8 ++++++++ controllers/toolbox.php | 23 +++++++++++++++++------ views/admin/edit_tool.php | 2 +- views/toolbox/index.php | 33 ++++++++++++++++++++++++++------- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/assets/toolbox.scss b/assets/toolbox.scss index beab708..31c63a5 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -43,3 +43,11 @@ border: #d3d3d3 2px dashed !important; content-visibility: hidden !important; } + +.drag-handle { + display: inline-block; + width: 6px; + height: 20px; + margin-top: 5px; + background-size: auto 20px; +} diff --git a/controllers/toolbox.php b/controllers/toolbox.php index d7c4dac..918aad8 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -16,7 +16,7 @@ class ToolboxController extends PluginController } $this->tools = ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) WHERE `toolbox_tools`.`active` = 1 - ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) DESC, `toolbox_tools`.`name` ASC', + ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', ['user_id' => User::findCurrent()->id] ); } @@ -52,14 +52,25 @@ class ToolboxController extends PluginController exit; } - public function vue_action() + public function save_order_action() { + $toolIds = explode(',', Request::get('order')); - } + foreach ($toolIds as $index => $id) { + $toolboxUserTool = ToolboxUserTool::getUserTool($id); - public function vue_cdn_action() - { - $this->message = 'Nein, danke'; + if(! $toolboxUserTool) { + $toolboxUserTool = new ToolboxUserTool(); + $toolboxUserTool->user_id = User::findCurrent()->id; + $toolboxUserTool->tool_id = $id; + } + + $toolboxUserTool->priority = $index; + $toolboxUserTool->store(); + } + $this->render_json([ + 'success' => true + ]); } } diff --git a/views/admin/edit_tool.php b/views/admin/edit_tool.php index 084dd4e..6b1304a 100644 --- a/views/admin/edit_tool.php +++ b/views/admin/edit_tool.php @@ -107,7 +107,7 @@ </div> </form> -<script src="//unpkg.com/alpinejs" defer></script> +<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> <script> var iconType = <?= $tool->isNew() ? json_encode('studip') : json_encode($tool['icon_type']) ?>; diff --git a/views/toolbox/index.php b/views/toolbox/index.php index a50761f..1e4c4ad 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,6 +1,6 @@ -<ul id="simpleList" class="content-items toolbox"> +<ul id="simpleList" class="content-items toolbox" x-data="initApp()"> <? foreach ($tools as $key => $tool) : ?> - <li data-id="<?= $tool->id ?>" class="content-item"> + <li data-id="<?= $tool->id ?>" class="content-item dragarea"> <div class="top_icons"> <? if ($tool['long_description']) : ?> <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" @@ -26,17 +26,36 @@ </div> <div class="info"><?= htmlReady($tool['info']) ?></div> </a> + <a class="dragarea" tabindex="0"> + <span class="drag-handle"></span> + </a> </li> <? endforeach ?> </ul> + <script> - // Simple list - Sortable.create(simpleList, { - animation: 150, - ghostClass: 'dropable-placeholder', - dataIdAttr: 'data-id', + STUDIP.ready(() => { + var sortable = Sortable.create(simpleList, { + animation: 150, + ghostClass: 'dropable-placeholder', + dataIdAttr: 'data-id', + handle: ".dragarea", + + onSort: (event) => { + storeToolOrder() + }, + }); + + const storeToolOrder = async () => { + let formData = new FormData(); + formData.append('order', sortable.toArray()); + await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/save_order`), { + method: 'POST', + body: formData + }); + }; }); </script> -- GitLab From d7b4d4845a9fb5c5619a9eb158b38ec0a3829163 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 10:38:24 +0200 Subject: [PATCH 04/31] Add alpine js min file --- assets/js/alpine.min.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 assets/js/alpine.min.js diff --git a/assets/js/alpine.min.js b/assets/js/alpine.min.js new file mode 100644 index 0000000..2ca4827 --- /dev/null +++ b/assets/js/alpine.min.js @@ -0,0 +1,5 @@ +(()=>{var rt=!1,nt=!1,U=[],it=-1;function qt(e){Cn(e)}function Cn(e){U.includes(e)||U.push(e),Tn()}function Ee(e){let t=U.indexOf(e);t!==-1&&t>it&&U.splice(t,1)}function Tn(){!nt&&!rt&&(rt=!0,queueMicrotask(Rn))}function Rn(){rt=!1,nt=!0;for(let e=0;e<U.length;e++)U[e](),it=e;U.length=0,it=-1,nt=!1}var R,D,L,st,ot=!0;function Ut(e){ot=!1,e(),ot=!0}function Wt(e){R=e.reactive,L=e.release,D=t=>e.effect(t,{scheduler:r=>{ot?qt(r):r()}}),st=e.raw}function at(e){D=e}function Gt(e){let t=()=>{};return[n=>{let i=D(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),L(i))},i},()=>{t()}]}function ve(e,t){let r=!0,n,i=D(()=>{let o=e();JSON.stringify(o),r?n=o:queueMicrotask(()=>{t(o,n),n=o}),r=!1});return()=>L(i)}var Jt=[],Yt=[],Xt=[];function Zt(e){Xt.push(e)}function ee(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Yt.push(t))}function Ae(e){Jt.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function ct(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function Qt(e){if(e._x_cleanups)for(;e._x_cleanups.length;)e._x_cleanups.pop()()}var lt=new MutationObserver(pt),ut=!1;function le(){lt.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ut=!0}function ft(){Mn(),lt.disconnect(),ut=!1}var ce=[];function Mn(){let e=lt.takeRecords();ce.push(()=>e.length>0&&pt(e));let t=ce.length;queueMicrotask(()=>{if(ce.length===t)for(;ce.length>0;)ce.shift()()})}function _(e){if(!ut)return e();ft();let t=e();return le(),t}var dt=!1,Se=[];function er(){dt=!0}function tr(){dt=!1,pt(Se),Se=[]}function pt(e){if(dt){Se=Se.concat(e);return}let t=new Set,r=new Set,n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].addedNodes.forEach(s=>s.nodeType===1&&t.add(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.add(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{ct(s,o)}),n.forEach((o,s)=>{Jt.forEach(a=>a(s,o))});for(let o of r)t.has(o)||Yt.forEach(s=>s(o));t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.has(o)||o.isConnected&&(delete o._x_ignoreSelf,delete o._x_ignore,Xt.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function Ce(e){return F(j(e))}function P(e,t,r){return e._x_dataStack=[t,...j(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function j(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?j(e.host):e.parentNode?j(e.parentNode):[]}function F(e){return new Proxy({objects:e},Nn)}var Nn={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(r=>Object.prototype.hasOwnProperty.call(r,t)||Reflect.has(r,t))},get({objects:e},t,r){return t=="toJSON"?Dn:Reflect.get(e.find(n=>Reflect.has(n,t))||{},t,r)},set({objects:e},t,r,n){let i=e.find(s=>Object.prototype.hasOwnProperty.call(s,t))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,t,r)}};function Dn(){return Reflect.ownKeys(this).reduce((t,r)=>(t[r]=Reflect.get(this,r),t),{})}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>Pn(n,i),s=>mt(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function Pn(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function mt(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),mt(e[t[0]],t.slice(1),r)}}var rr={};function y(e,t){rr[e]=t}function ue(e,t){return Object.entries(rr).forEach(([r,n])=>{let i=null;function o(){if(i)return i;{let[s,a]=_t(t);return i={interceptor:Re,...s},ee(t,a),i}}Object.defineProperty(e,`$${r}`,{get(){return n(t,o())},enumerable:!1})}),e}function nr(e,t,r,...n){try{return r(...n)}catch(i){te(i,e,t)}}function te(e,t,r=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function De(e){let t=Me;Me=!1;let r=e();return Me=t,r}function M(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return ir(...e)}var ir=gt;function or(e){ir=e}function gt(e,t){let r={};ue(r,e);let n=[r,...j(e)],i=typeof t=="function"?In(n,t):Ln(n,t,e);return nr.bind(null,e,t,i)}function In(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(F([n,...e]),i);Ne(r,o)}}var ht={};function kn(e,t){if(ht[e])return ht[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{let s=new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`);return Object.defineProperty(s,"name",{value:`[Alpine] ${e}`}),s}catch(s){return te(s,t,e),Promise.resolve()}})();return ht[e]=o,o}function Ln(e,t,r){let n=kn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=F([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>te(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>te(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>te(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var bt="x-";function C(e=""){return bt+e}function sr(e){bt=e}var Pe={};function d(e,t){return Pe[e]=t,{before(r){if(!Pe[r]){console.warn(String.raw`Cannot find directive \`${r}\`. \`${e}\` will use the default order of execution`);return}let n=W.indexOf(r);W.splice(n>=0?n:W.indexOf("DEFAULT"),0,e)}}}function ar(e){return Object.keys(Pe).includes(e)}function de(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=wt(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(ur((o,s)=>n[o]=s)).filter(dr).map(jn(n,r)).sort(Fn).map(o=>$n(e,o))}function wt(e){return Array.from(e).map(ur()).filter(t=>!dr(t))}var xt=!1,fe=new Map,cr=Symbol();function lr(e){xt=!0;let t=Symbol();cr=t,fe.set(t,[]);let r=()=>{for(;fe.get(t).length;)fe.get(t).shift()();fe.delete(t)},n=()=>{xt=!1,r()};e(r),n()}function _t(e){let t=[],r=a=>t.push(a),[n,i]=Gt(e);return t.push(i),[{Alpine:B,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:M.bind(M,e)},()=>t.forEach(a=>a())]}function $n(e,t){let r=()=>{},n=Pe[t.type]||r,[i,o]=_t(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),xt?fe.get(cr).push(n):n())};return s.runCleanups=o,s}var Ie=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),ke=e=>e;function ur(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=fr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var fr=[];function re(e){fr.push(e)}function dr({name:e}){return pr().test(e)}var pr=()=>new RegExp(`^${bt}([^:^.]+)\\b`);function jn(e,t){return({name:r,value:n})=>{let i=r.match(pr()),o=r.match(/:([a-zA-Z0-9\-_:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var yt="DEFAULT",W=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",yt,"teleport"];function Fn(e,t){let r=W.indexOf(e.type)===-1?yt:e.type,n=W.indexOf(t.type)===-1?yt:t.type;return W.indexOf(r)-W.indexOf(n)}function G(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function T(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>T(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)T(n,t,!1),n=n.nextElementSibling}function E(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var mr=!1;function _r(){mr&&E("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),mr=!0,document.body||E("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),G(document,"alpine:init"),G(document,"alpine:initializing"),le(),Zt(t=>S(t,T)),ee(t=>vt(t)),Ae((t,r)=>{de(t,r).forEach(n=>n())});let e=t=>!J(t.parentElement,!0);Array.from(document.querySelectorAll(xr().join(","))).filter(e).forEach(t=>{S(t)}),G(document,"alpine:initialized"),setTimeout(()=>{Bn()})}var Et=[],hr=[];function gr(){return Et.map(e=>e())}function xr(){return Et.concat(hr).map(e=>e())}function Le(e){Et.push(e)}function $e(e){hr.push(e)}function J(e,t=!1){return z(e,r=>{if((t?xr():gr()).some(i=>r.matches(i)))return!0})}function z(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),!!e.parentElement)return z(e.parentElement,t)}}function yr(e){return gr().some(t=>e.matches(t))}var br=[];function wr(e){br.push(e)}function S(e,t=T,r=()=>{}){lr(()=>{t(e,(n,i)=>{r(n,i),br.forEach(o=>o(n,i)),de(n,n.attributes).forEach(o=>o()),n._x_ignore&&i()})})}function vt(e,t=T){t(e,r=>{ct(r),Qt(r)})}function Bn(){[["ui","dialog",["[x-dialog], [x-popover]"]],["anchor","anchor",["[x-anchor]"]],["sort","sort",["[x-sort]"]]].forEach(([t,r,n])=>{ar(r)||n.some(i=>{if(document.querySelector(i))return E(`found "${i}", but missing ${t} plugin`),!0})})}var St=[],At=!1;function ne(e=()=>{}){return queueMicrotask(()=>{At||setTimeout(()=>{je()})}),new Promise(t=>{St.push(()=>{e(),t()})})}function je(){for(At=!1;St.length;)St.shift()()}function Er(){At=!0}function pe(e,t){return Array.isArray(t)?vr(e,t.join(" ")):typeof t=="object"&&t!==null?zn(e,t):typeof t=="function"?pe(e,t()):vr(e,t)}function vr(e,t){let r=o=>o.split(" ").filter(Boolean),n=o=>o.split(" ").filter(s=>!e.classList.contains(s)).filter(Boolean),i=o=>(e.classList.add(...o),()=>{e.classList.remove(...o)});return t=t===!0?t="":t||"",i(n(t))}function zn(e,t){let r=a=>a.split(" ").filter(Boolean),n=Object.entries(t).flatMap(([a,c])=>c?r(a):!1).filter(Boolean),i=Object.entries(t).flatMap(([a,c])=>c?!1:r(a)).filter(Boolean),o=[],s=[];return i.forEach(a=>{e.classList.contains(a)&&(e.classList.remove(a),s.push(a))}),n.forEach(a=>{e.classList.contains(a)||(e.classList.add(a),o.push(a))}),()=>{s.forEach(a=>e.classList.add(a)),o.forEach(a=>e.classList.remove(a))}}function Y(e,t){return typeof t=="object"&&t!==null?Kn(e,t):Hn(e,t)}function Kn(e,t){let r={};return Object.entries(t).forEach(([n,i])=>{r[n]=e.style[n],n.startsWith("--")||(n=Vn(n)),e.style.setProperty(n,i)}),setTimeout(()=>{e.style.length===0&&e.removeAttribute("style")}),()=>{Y(e,r)}}function Hn(e,t){let r=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",r||"")}}function Vn(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function me(e,t=()=>{}){let r=!1;return function(){r?t.apply(this,arguments):(r=!0,e.apply(this,arguments))}}d("transition",(e,{value:t,modifiers:r,expression:n},{evaluate:i})=>{typeof n=="function"&&(n=i(n)),n!==!1&&(!n||typeof n=="boolean"?Un(e,r,t):qn(e,n,t))});function qn(e,t,r){Sr(e,pe,""),{enter:i=>{e._x_transition.enter.during=i},"enter-start":i=>{e._x_transition.enter.start=i},"enter-end":i=>{e._x_transition.enter.end=i},leave:i=>{e._x_transition.leave.during=i},"leave-start":i=>{e._x_transition.leave.start=i},"leave-end":i=>{e._x_transition.leave.end=i}}[r](t)}function Un(e,t,r){Sr(e,Y);let n=!t.includes("in")&&!t.includes("out")&&!r,i=n||t.includes("in")||["enter"].includes(r),o=n||t.includes("out")||["leave"].includes(r);t.includes("in")&&!n&&(t=t.filter((g,b)=>b<t.indexOf("out"))),t.includes("out")&&!n&&(t=t.filter((g,b)=>b>t.indexOf("out")));let s=!t.includes("opacity")&&!t.includes("scale"),a=s||t.includes("opacity"),c=s||t.includes("scale"),l=a?0:1,u=c?_e(t,"scale",95)/100:1,p=_e(t,"delay",0)/1e3,m=_e(t,"origin","center"),w="opacity, transform",$=_e(t,"duration",150)/1e3,we=_e(t,"duration",75)/1e3,f="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:m,transitionDelay:`${p}s`,transitionProperty:w,transitionDuration:`${$}s`,transitionTimingFunction:f},e._x_transition.enter.start={opacity:l,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"}),o&&(e._x_transition.leave.during={transformOrigin:m,transitionDelay:`${p}s`,transitionProperty:w,transitionDuration:`${we}s`,transitionTimingFunction:f},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:l,transform:`scale(${u})`})}function Sr(e,t,r={}){e._x_transition||(e._x_transition={enter:{during:r,start:r,end:r},leave:{during:r,start:r,end:r},in(n=()=>{},i=()=>{}){Fe(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,i)},out(n=()=>{},i=()=>{}){Fe(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,i)}})}window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,r,n){let i=document.visibilityState==="visible"?requestAnimationFrame:setTimeout,o=()=>i(r);if(t){e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(r):o():e._x_transition?e._x_transition.in(r):o();return}e._x_hidePromise=e._x_transition?new Promise((s,a)=>{e._x_transition.out(()=>{},()=>s(n)),e._x_transitioning&&e._x_transitioning.beforeCancel(()=>a({isFromCancelledTransition:!0}))}):Promise.resolve(n),queueMicrotask(()=>{let s=Ar(e);s?(s._x_hideChildren||(s._x_hideChildren=[]),s._x_hideChildren.push(e)):i(()=>{let a=c=>{let l=Promise.all([c._x_hidePromise,...(c._x_hideChildren||[]).map(a)]).then(([u])=>u?.());return delete c._x_hidePromise,delete c._x_hideChildren,l};a(e).catch(c=>{if(!c.isFromCancelledTransition)throw c})})})};function Ar(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:Ar(t)}function Fe(e,t,{during:r,start:n,end:i}={},o=()=>{},s=()=>{}){if(e._x_transitioning&&e._x_transitioning.cancel(),Object.keys(r).length===0&&Object.keys(n).length===0&&Object.keys(i).length===0){o(),s();return}let a,c,l;Wn(e,{start(){a=t(e,n)},during(){c=t(e,r)},before:o,end(){a(),l=t(e,i)},after:s,cleanup(){c(),l()}})}function Wn(e,t){let r,n,i,o=me(()=>{_(()=>{r=!0,n||t.before(),i||(t.end(),je()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning})});e._x_transitioning={beforeCancels:[],beforeCancel(s){this.beforeCancels.push(s)},cancel:me(function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()}),finish:o},_(()=>{t.start(),t.during()}),Er(),requestAnimationFrame(()=>{if(r)return;let s=Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s",""))*1e3,a=Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""))*1e3;s===0&&(s=Number(getComputedStyle(e).animationDuration.replace("s",""))*1e3),_(()=>{t.before()}),n=!0,requestAnimationFrame(()=>{r||(_(()=>{t.end()}),je(),setTimeout(e._x_transitioning.finish,s+a),i=!0)})})}function _e(e,t,r){if(e.indexOf(t)===-1)return r;let n=e[e.indexOf(t)+1];if(!n||t==="scale"&&isNaN(n))return r;if(t==="duration"||t==="delay"){let i=n.match(/([0-9]+)ms/);if(i)return i[1]}return t==="origin"&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[n,e[e.indexOf(t)+2]].join(" "):n}var I=!1;function A(e,t=()=>{}){return(...r)=>I?t(...r):e(...r)}function Or(e){return(...t)=>I&&e(...t)}var Cr=[];function K(e){Cr.push(e)}function Tr(e,t){Cr.forEach(r=>r(e,t)),I=!0,Mr(()=>{S(t,(r,n)=>{n(r,()=>{})})}),I=!1}var Be=!1;function Rr(e,t){t._x_dataStack||(t._x_dataStack=e._x_dataStack),I=!0,Be=!0,Mr(()=>{Gn(t)}),I=!1,Be=!1}function Gn(e){let t=!1;S(e,(n,i)=>{T(n,(o,s)=>{if(t&&yr(o))return s();t=!0,i(o,s)})})}function Mr(e){let t=D;at((r,n)=>{let i=t(r);return L(i),()=>{}}),e(),at(t)}function he(e,t,r,n=[]){switch(e._x_bindings||(e._x_bindings=R({})),e._x_bindings[t]=r,t=n.includes("camel")?ri(t):t,t){case"value":Jn(e,r);break;case"style":Xn(e,r);break;case"class":Yn(e,r);break;case"selected":case"checked":Zn(e,t,r);break;default:Dr(e,t,r);break}}function Jn(e,t){if(e.type==="radio")e.attributes.value===void 0&&(e.value=t),window.fromModel&&(typeof t=="boolean"?e.checked=ge(e.value)===t:e.checked=Nr(e.value,t));else if(e.type==="checkbox")Number.isInteger(t)?e.value=t:!Array.isArray(t)&&typeof t!="boolean"&&![null,void 0].includes(t)?e.value=String(t):Array.isArray(t)?e.checked=t.some(r=>Nr(r,e.value)):e.checked=!!t;else if(e.tagName==="SELECT")ti(e,t);else{if(e.value===t)return;e.value=t===void 0?"":t}}function Yn(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedClasses=pe(e,t)}function Xn(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles(),e._x_undoAddedStyles=Y(e,t)}function Zn(e,t,r){Dr(e,t,r),ei(e,t,r)}function Dr(e,t,r){[null,void 0,!1].includes(r)&&ni(t)?e.removeAttribute(t):(Pr(t)&&(r=t),Qn(e,t,r))}function Qn(e,t,r){e.getAttribute(t)!=r&&e.setAttribute(t,r)}function ei(e,t,r){e[t]!==r&&(e[t]=r)}function ti(e,t){let r=[].concat(t).map(n=>n+"");Array.from(e.options).forEach(n=>{n.selected=r.includes(n.value)})}function ri(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function Nr(e,t){return e==t}function ge(e){return[1,"1","true","on","yes",!0].includes(e)?!0:[0,"0","false","off","no",!1].includes(e)?!1:e?Boolean(e):null}function Pr(e){return["disabled","checked","required","readonly","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function ni(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}function Ir(e,t,r){return e._x_bindings&&e._x_bindings[t]!==void 0?e._x_bindings[t]:Lr(e,t,r)}function kr(e,t,r,n=!0){if(e._x_bindings&&e._x_bindings[t]!==void 0)return e._x_bindings[t];if(e._x_inlineBindings&&e._x_inlineBindings[t]!==void 0){let i=e._x_inlineBindings[t];return i.extract=n,De(()=>M(e,i.expression))}return Lr(e,t,r)}function Lr(e,t,r){let n=e.getAttribute(t);return n===null?typeof r=="function"?r():r:n===""?!0:Pr(t)?!![t,"true"].includes(n):n}function ze(e,t){var r;return function(){var n=this,i=arguments,o=function(){r=null,e.apply(n,i)};clearTimeout(r),r=setTimeout(o,t)}}function Ke(e,t){let r;return function(){let n=this,i=arguments;r||(e.apply(n,i),r=!0,setTimeout(()=>r=!1,t))}}function He({get:e,set:t},{get:r,set:n}){let i=!0,o,s,a=D(()=>{let c=e(),l=r();if(i)n(Ot(c)),i=!1;else{let u=JSON.stringify(c),p=JSON.stringify(l);u!==o?n(Ot(c)):u!==p&&t(Ot(l))}o=JSON.stringify(e()),s=JSON.stringify(r())});return()=>{L(a)}}function Ot(e){return typeof e=="object"?JSON.parse(JSON.stringify(e)):e}function $r(e){(Array.isArray(e)?e:[e]).forEach(r=>r(B))}var X={},jr=!1;function Fr(e,t){if(jr||(X=R(X),jr=!0),t===void 0)return X[e];X[e]=t,typeof t=="object"&&t!==null&&t.hasOwnProperty("init")&&typeof t.init=="function"&&X[e].init(),Te(X[e])}function Br(){return X}var zr={};function Kr(e,t){let r=typeof t!="function"?()=>t:t;return e instanceof Element?Ct(e,r()):(zr[e]=r,()=>{})}function Hr(e){return Object.entries(zr).forEach(([t,r])=>{Object.defineProperty(e,t,{get(){return(...n)=>r(...n)}})}),e}function Ct(e,t,r){let n=[];for(;n.length;)n.pop()();let i=Object.entries(t).map(([s,a])=>({name:s,value:a})),o=wt(i);return i=i.map(s=>o.find(a=>a.name===s.name)?{name:`x-bind:${s.name}`,value:`"${s.value}"`}:s),de(e,i,r).map(s=>{n.push(s.runCleanups),s()}),()=>{for(;n.length;)n.pop()()}}var Vr={};function qr(e,t){Vr[e]=t}function Ur(e,t){return Object.entries(Vr).forEach(([r,n])=>{Object.defineProperty(e,r,{get(){return(...i)=>n.bind(t)(...i)},enumerable:!1})}),e}var ii={get reactive(){return R},get release(){return L},get effect(){return D},get raw(){return st},version:"3.14.1",flushAndStopDeferringMutations:tr,dontAutoEvaluateFunctions:De,disableEffectScheduling:Ut,startObservingMutations:le,stopObservingMutations:ft,setReactivityEngine:Wt,onAttributeRemoved:Oe,onAttributesAdded:Ae,closestDataStack:j,skipDuringClone:A,onlyDuringClone:Or,addRootSelector:Le,addInitSelector:$e,interceptClone:K,addScopeToNode:P,deferMutations:er,mapAttributes:re,evaluateLater:x,interceptInit:wr,setEvaluator:or,mergeProxies:F,extractProp:kr,findClosest:z,onElRemoved:ee,closestRoot:J,destroyTree:vt,interceptor:Re,transition:Fe,setStyles:Y,mutateDom:_,directive:d,entangle:He,throttle:Ke,debounce:ze,evaluate:M,initTree:S,nextTick:ne,prefixed:C,prefix:sr,plugin:$r,magic:y,store:Fr,start:_r,clone:Rr,cloneNode:Tr,bound:Ir,$data:Ce,watch:ve,walk:T,data:qr,bind:Kr},B=ii;function Tt(e,t){let r=Object.create(null),n=e.split(",");for(let i=0;i<n.length;i++)r[n[i]]=!0;return t?i=>!!r[i.toLowerCase()]:i=>!!r[i]}var oi="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly";var Ms=Tt(oi+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected");var Wr=Object.freeze({}),Ns=Object.freeze([]);var si=Object.prototype.hasOwnProperty,xe=(e,t)=>si.call(e,t),H=Array.isArray,ie=e=>Gr(e)==="[object Map]";var ai=e=>typeof e=="string",Ve=e=>typeof e=="symbol",ye=e=>e!==null&&typeof e=="object";var ci=Object.prototype.toString,Gr=e=>ci.call(e),Rt=e=>Gr(e).slice(8,-1);var qe=e=>ai(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e;var Ue=e=>{let t=Object.create(null);return r=>t[r]||(t[r]=e(r))},li=/-(\w)/g,Ds=Ue(e=>e.replace(li,(t,r)=>r?r.toUpperCase():"")),ui=/\B([A-Z])/g,Ps=Ue(e=>e.replace(ui,"-$1").toLowerCase()),Mt=Ue(e=>e.charAt(0).toUpperCase()+e.slice(1)),Is=Ue(e=>e?`on${Mt(e)}`:""),Nt=(e,t)=>e!==t&&(e===e||t===t);var Dt=new WeakMap,be=[],k,Z=Symbol("iterate"),Pt=Symbol("Map key iterate");function fi(e){return e&&e._isEffect===!0}function en(e,t=Wr){fi(e)&&(e=e.raw);let r=pi(e,t);return t.lazy||r(),r}function tn(e){e.active&&(rn(e),e.options.onStop&&e.options.onStop(),e.active=!1)}var di=0;function pi(e,t){let r=function(){if(!r.active)return e();if(!be.includes(r)){rn(r);try{return _i(),be.push(r),k=r,e()}finally{be.pop(),nn(),k=be[be.length-1]}}};return r.id=di++,r.allowRecurse=!!t.allowRecurse,r._isEffect=!0,r.active=!0,r.raw=e,r.deps=[],r.options=t,r}function rn(e){let{deps:t}=e;if(t.length){for(let r=0;r<t.length;r++)t[r].delete(e);t.length=0}}var oe=!0,kt=[];function mi(){kt.push(oe),oe=!1}function _i(){kt.push(oe),oe=!0}function nn(){let e=kt.pop();oe=e===void 0?!0:e}function N(e,t,r){if(!oe||k===void 0)return;let n=Dt.get(e);n||Dt.set(e,n=new Map);let i=n.get(r);i||n.set(r,i=new Set),i.has(k)||(i.add(k),k.deps.push(i),k.options.onTrack&&k.options.onTrack({effect:k,target:e,type:t,key:r}))}function q(e,t,r,n,i,o){let s=Dt.get(e);if(!s)return;let a=new Set,c=u=>{u&&u.forEach(p=>{(p!==k||p.allowRecurse)&&a.add(p)})};if(t==="clear")s.forEach(c);else if(r==="length"&&H(e))s.forEach((u,p)=>{(p==="length"||p>=n)&&c(u)});else switch(r!==void 0&&c(s.get(r)),t){case"add":H(e)?qe(r)&&c(s.get("length")):(c(s.get(Z)),ie(e)&&c(s.get(Pt)));break;case"delete":H(e)||(c(s.get(Z)),ie(e)&&c(s.get(Pt)));break;case"set":ie(e)&&c(s.get(Z));break}let l=u=>{u.options.onTrigger&&u.options.onTrigger({effect:u,target:e,key:r,type:t,newValue:n,oldValue:i,oldTarget:o}),u.options.scheduler?u.options.scheduler(u):u()};a.forEach(l)}var hi=Tt("__proto__,__v_isRef,__isVue"),on=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(Ve)),gi=sn();var xi=sn(!0);var Jr=yi();function yi(){let e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...r){let n=h(this);for(let o=0,s=this.length;o<s;o++)N(n,"get",o+"");let i=n[t](...r);return i===-1||i===!1?n[t](...r.map(h)):i}}),["push","pop","shift","unshift","splice"].forEach(t=>{e[t]=function(...r){mi();let n=h(this)[t].apply(this,r);return nn(),n}}),e}function sn(e=!1,t=!1){return function(n,i,o){if(i==="__v_isReactive")return!e;if(i==="__v_isReadonly")return e;if(i==="__v_raw"&&o===(e?t?ki:un:t?Ii:ln).get(n))return n;let s=H(n);if(!e&&s&&xe(Jr,i))return Reflect.get(Jr,i,o);let a=Reflect.get(n,i,o);return(Ve(i)?on.has(i):hi(i))||(e||N(n,"get",i),t)?a:It(a)?!s||!qe(i)?a.value:a:ye(a)?e?fn(a):Qe(a):a}}var bi=wi();function wi(e=!1){return function(r,n,i,o){let s=r[n];if(!e&&(i=h(i),s=h(s),!H(r)&&It(s)&&!It(i)))return s.value=i,!0;let a=H(r)&&qe(n)?Number(n)<r.length:xe(r,n),c=Reflect.set(r,n,i,o);return r===h(o)&&(a?Nt(i,s)&&q(r,"set",n,i,s):q(r,"add",n,i)),c}}function Ei(e,t){let r=xe(e,t),n=e[t],i=Reflect.deleteProperty(e,t);return i&&r&&q(e,"delete",t,void 0,n),i}function vi(e,t){let r=Reflect.has(e,t);return(!Ve(t)||!on.has(t))&&N(e,"has",t),r}function Si(e){return N(e,"iterate",H(e)?"length":Z),Reflect.ownKeys(e)}var Ai={get:gi,set:bi,deleteProperty:Ei,has:vi,ownKeys:Si},Oi={get:xi,set(e,t){return console.warn(`Set operation on key "${String(t)}" failed: target is readonly.`,e),!0},deleteProperty(e,t){return console.warn(`Delete operation on key "${String(t)}" failed: target is readonly.`,e),!0}};var Lt=e=>ye(e)?Qe(e):e,$t=e=>ye(e)?fn(e):e,jt=e=>e,Ze=e=>Reflect.getPrototypeOf(e);function We(e,t,r=!1,n=!1){e=e.__v_raw;let i=h(e),o=h(t);t!==o&&!r&&N(i,"get",t),!r&&N(i,"get",o);let{has:s}=Ze(i),a=n?jt:r?$t:Lt;if(s.call(i,t))return a(e.get(t));if(s.call(i,o))return a(e.get(o));e!==i&&e.get(t)}function Ge(e,t=!1){let r=this.__v_raw,n=h(r),i=h(e);return e!==i&&!t&&N(n,"has",e),!t&&N(n,"has",i),e===i?r.has(e):r.has(e)||r.has(i)}function Je(e,t=!1){return e=e.__v_raw,!t&&N(h(e),"iterate",Z),Reflect.get(e,"size",e)}function Yr(e){e=h(e);let t=h(this);return Ze(t).has.call(t,e)||(t.add(e),q(t,"add",e,e)),this}function Xr(e,t){t=h(t);let r=h(this),{has:n,get:i}=Ze(r),o=n.call(r,e);o?cn(r,n,e):(e=h(e),o=n.call(r,e));let s=i.call(r,e);return r.set(e,t),o?Nt(t,s)&&q(r,"set",e,t,s):q(r,"add",e,t),this}function Zr(e){let t=h(this),{has:r,get:n}=Ze(t),i=r.call(t,e);i?cn(t,r,e):(e=h(e),i=r.call(t,e));let o=n?n.call(t,e):void 0,s=t.delete(e);return i&&q(t,"delete",e,void 0,o),s}function Qr(){let e=h(this),t=e.size!==0,r=ie(e)?new Map(e):new Set(e),n=e.clear();return t&&q(e,"clear",void 0,void 0,r),n}function Ye(e,t){return function(n,i){let o=this,s=o.__v_raw,a=h(s),c=t?jt:e?$t:Lt;return!e&&N(a,"iterate",Z),s.forEach((l,u)=>n.call(i,c(l),c(u),o))}}function Xe(e,t,r){return function(...n){let i=this.__v_raw,o=h(i),s=ie(o),a=e==="entries"||e===Symbol.iterator&&s,c=e==="keys"&&s,l=i[e](...n),u=r?jt:t?$t:Lt;return!t&&N(o,"iterate",c?Pt:Z),{next(){let{value:p,done:m}=l.next();return m?{value:p,done:m}:{value:a?[u(p[0]),u(p[1])]:u(p),done:m}},[Symbol.iterator](){return this}}}}function V(e){return function(...t){{let r=t[0]?`on key "${t[0]}" `:"";console.warn(`${Mt(e)} operation ${r}failed: target is readonly.`,h(this))}return e==="delete"?!1:this}}function Ci(){let e={get(o){return We(this,o)},get size(){return Je(this)},has:Ge,add:Yr,set:Xr,delete:Zr,clear:Qr,forEach:Ye(!1,!1)},t={get(o){return We(this,o,!1,!0)},get size(){return Je(this)},has:Ge,add:Yr,set:Xr,delete:Zr,clear:Qr,forEach:Ye(!1,!0)},r={get(o){return We(this,o,!0)},get size(){return Je(this,!0)},has(o){return Ge.call(this,o,!0)},add:V("add"),set:V("set"),delete:V("delete"),clear:V("clear"),forEach:Ye(!0,!1)},n={get(o){return We(this,o,!0,!0)},get size(){return Je(this,!0)},has(o){return Ge.call(this,o,!0)},add:V("add"),set:V("set"),delete:V("delete"),clear:V("clear"),forEach:Ye(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(o=>{e[o]=Xe(o,!1,!1),r[o]=Xe(o,!0,!1),t[o]=Xe(o,!1,!0),n[o]=Xe(o,!0,!0)}),[e,r,t,n]}var[Ti,Ri,Mi,Ni]=Ci();function an(e,t){let r=t?e?Ni:Mi:e?Ri:Ti;return(n,i,o)=>i==="__v_isReactive"?!e:i==="__v_isReadonly"?e:i==="__v_raw"?n:Reflect.get(xe(r,i)&&i in n?r:n,i,o)}var Di={get:an(!1,!1)};var Pi={get:an(!0,!1)};function cn(e,t,r){let n=h(r);if(n!==r&&t.call(e,n)){let i=Rt(e);console.warn(`Reactive ${i} contains both the raw and reactive versions of the same object${i==="Map"?" as keys":""}, which can lead to inconsistencies. Avoid differentiating between the raw and reactive versions of an object and only use the reactive version if possible.`)}}var ln=new WeakMap,Ii=new WeakMap,un=new WeakMap,ki=new WeakMap;function Li(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function $i(e){return e.__v_skip||!Object.isExtensible(e)?0:Li(Rt(e))}function Qe(e){return e&&e.__v_isReadonly?e:dn(e,!1,Ai,Di,ln)}function fn(e){return dn(e,!0,Oi,Pi,un)}function dn(e,t,r,n,i){if(!ye(e))return console.warn(`value cannot be made reactive: ${String(e)}`),e;if(e.__v_raw&&!(t&&e.__v_isReactive))return e;let o=i.get(e);if(o)return o;let s=$i(e);if(s===0)return e;let a=new Proxy(e,s===2?n:r);return i.set(e,a),a}function h(e){return e&&h(e.__v_raw)||e}function It(e){return Boolean(e&&e.__v_isRef===!0)}y("nextTick",()=>ne);y("dispatch",e=>G.bind(G,e));y("watch",(e,{evaluateLater:t,cleanup:r})=>(n,i)=>{let o=t(n),a=ve(()=>{let c;return o(l=>c=l),c},i);r(a)});y("store",Br);y("data",e=>Ce(e));y("root",e=>J(e));y("refs",e=>(e._x_refs_proxy||(e._x_refs_proxy=F(ji(e))),e._x_refs_proxy));function ji(e){let t=[];return z(e,r=>{r._x_refs&&t.push(r._x_refs)}),t}var Ft={};function Bt(e){return Ft[e]||(Ft[e]=0),++Ft[e]}function pn(e,t){return z(e,r=>{if(r._x_ids&&r._x_ids[t])return!0})}function mn(e,t){e._x_ids||(e._x_ids={}),e._x_ids[t]||(e._x_ids[t]=Bt(t))}y("id",(e,{cleanup:t})=>(r,n=null)=>{let i=`${r}${n?`-${n}`:""}`;return Fi(e,i,t,()=>{let o=pn(e,r),s=o?o._x_ids[r]:Bt(r);return n?`${r}-${s}-${n}`:`${r}-${s}`})});K((e,t)=>{e._x_id&&(t._x_id=e._x_id)});function Fi(e,t,r,n){if(e._x_id||(e._x_id={}),e._x_id[t])return e._x_id[t];let i=n();return e._x_id[t]=i,r(()=>{delete e._x_id[t]}),i}y("el",e=>e);_n("Focus","focus","focus");_n("Persist","persist","persist");function _n(e,t,r){y(t,n=>E(`You can't use [$${t}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}d("modelable",(e,{expression:t},{effect:r,evaluateLater:n,cleanup:i})=>{let o=n(t),s=()=>{let u;return o(p=>u=p),u},a=n(`${t} = __placeholder`),c=u=>a(()=>{},{scope:{__placeholder:u}}),l=s();c(l),queueMicrotask(()=>{if(!e._x_model)return;e._x_removeModelListeners.default();let u=e._x_model.get,p=e._x_model.set,m=He({get(){return u()},set(w){p(w)}},{get(){return s()},set(w){c(w)}});i(m)})});d("teleport",(e,{modifiers:t,expression:r},{cleanup:n})=>{e.tagName.toLowerCase()!=="template"&&E("x-teleport can only be used on a <template> tag",e);let i=hn(r),o=e.content.cloneNode(!0).firstElementChild;e._x_teleport=o,o._x_teleportBack=e,e.setAttribute("data-teleport-template",!0),o.setAttribute("data-teleport-target",!0),e._x_forwardEvents&&e._x_forwardEvents.forEach(a=>{o.addEventListener(a,c=>{c.stopPropagation(),e.dispatchEvent(new c.constructor(c.type,c))})}),P(o,{},e);let s=(a,c,l)=>{l.includes("prepend")?c.parentNode.insertBefore(a,c):l.includes("append")?c.parentNode.insertBefore(a,c.nextSibling):c.appendChild(a)};_(()=>{s(o,i,t),A(()=>{S(o),o._x_ignore=!0})()}),e._x_teleportPutBack=()=>{let a=hn(r);_(()=>{s(e._x_teleport,a,t)})},n(()=>o.remove())});var Bi=document.createElement("div");function hn(e){let t=A(()=>document.querySelector(e),()=>Bi)();return t||E(`Cannot find x-teleport element for selector: "${e}"`),t}var gn=()=>{};gn.inline=(e,{modifiers:t},{cleanup:r})=>{t.includes("self")?e._x_ignoreSelf=!0:e._x_ignore=!0,r(()=>{t.includes("self")?delete e._x_ignoreSelf:delete e._x_ignore})};d("ignore",gn);d("effect",A((e,{expression:t},{effect:r})=>{r(x(e,t))}));function se(e,t,r,n){let i=e,o=c=>n(c),s={},a=(c,l)=>u=>l(c,u);if(r.includes("dot")&&(t=zi(t)),r.includes("camel")&&(t=Ki(t)),r.includes("passive")&&(s.passive=!0),r.includes("capture")&&(s.capture=!0),r.includes("window")&&(i=window),r.includes("document")&&(i=document),r.includes("debounce")){let c=r[r.indexOf("debounce")+1]||"invalid-wait",l=et(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=ze(o,l)}if(r.includes("throttle")){let c=r[r.indexOf("throttle")+1]||"invalid-wait",l=et(c.split("ms")[0])?Number(c.split("ms")[0]):250;o=Ke(o,l)}return r.includes("prevent")&&(o=a(o,(c,l)=>{l.preventDefault(),c(l)})),r.includes("stop")&&(o=a(o,(c,l)=>{l.stopPropagation(),c(l)})),r.includes("once")&&(o=a(o,(c,l)=>{c(l),i.removeEventListener(t,o,s)})),(r.includes("away")||r.includes("outside"))&&(i=document,o=a(o,(c,l)=>{e.contains(l.target)||l.target.isConnected!==!1&&(e.offsetWidth<1&&e.offsetHeight<1||e._x_isShown!==!1&&c(l))})),r.includes("self")&&(o=a(o,(c,l)=>{l.target===e&&c(l)})),(Vi(t)||yn(t))&&(o=a(o,(c,l)=>{qi(l,r)||c(l)})),i.addEventListener(t,o,s),()=>{i.removeEventListener(t,o,s)}}function zi(e){return e.replace(/-/g,".")}function Ki(e){return e.toLowerCase().replace(/-(\w)/g,(t,r)=>r.toUpperCase())}function et(e){return!Array.isArray(e)&&!isNaN(e)}function Hi(e){return[" ","_"].includes(e)?e:e.replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[_\s]/,"-").toLowerCase()}function Vi(e){return["keydown","keyup"].includes(e)}function yn(e){return["contextmenu","click","mouse"].some(t=>e.includes(t))}function qi(e,t){let r=t.filter(o=>!["window","document","prevent","stop","once","capture","self","away","outside","passive"].includes(o));if(r.includes("debounce")){let o=r.indexOf("debounce");r.splice(o,et((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.includes("throttle")){let o=r.indexOf("throttle");r.splice(o,et((r[o+1]||"invalid-wait").split("ms")[0])?2:1)}if(r.length===0||r.length===1&&xn(e.key).includes(r[0]))return!1;let i=["ctrl","shift","alt","meta","cmd","super"].filter(o=>r.includes(o));return r=r.filter(o=>!i.includes(o)),!(i.length>0&&i.filter(s=>((s==="cmd"||s==="super")&&(s="meta"),e[`${s}Key`])).length===i.length&&(yn(e.type)||xn(e.key).includes(r[0])))}function xn(e){if(!e)return[];e=Hi(e);let t={ctrl:"control",slash:"/",space:" ",spacebar:" ",cmd:"meta",esc:"escape",up:"arrow-up",down:"arrow-down",left:"arrow-left",right:"arrow-right",period:".",comma:",",equal:"=",minus:"-",underscore:"_"};return t[e]=e,Object.keys(t).map(r=>{if(t[r]===e)return r}).filter(r=>r)}d("model",(e,{modifiers:t,expression:r},{effect:n,cleanup:i})=>{let o=e;t.includes("parent")&&(o=e.parentNode);let s=x(o,r),a;typeof r=="string"?a=x(o,`${r} = __placeholder`):typeof r=="function"&&typeof r()=="string"?a=x(o,`${r()} = __placeholder`):a=()=>{};let c=()=>{let m;return s(w=>m=w),bn(m)?m.get():m},l=m=>{let w;s($=>w=$),bn(w)?w.set(m):a(()=>{},{scope:{__placeholder:m}})};typeof r=="string"&&e.type==="radio"&&_(()=>{e.hasAttribute("name")||e.setAttribute("name",r)});var u=e.tagName.toLowerCase()==="select"||["checkbox","radio"].includes(e.type)||t.includes("lazy")?"change":"input";let p=I?()=>{}:se(e,u,t,m=>{l(zt(e,t,m,c()))});if(t.includes("fill")&&([void 0,null,""].includes(c())||e.type==="checkbox"&&Array.isArray(c())||e.tagName.toLowerCase()==="select"&&e.multiple)&&l(zt(e,t,{target:e},c())),e._x_removeModelListeners||(e._x_removeModelListeners={}),e._x_removeModelListeners.default=p,i(()=>e._x_removeModelListeners.default()),e.form){let m=se(e.form,"reset",[],w=>{ne(()=>e._x_model&&e._x_model.set(zt(e,t,{target:e},c())))});i(()=>m())}e._x_model={get(){return c()},set(m){l(m)}},e._x_forceModelUpdate=m=>{m===void 0&&typeof r=="string"&&r.match(/\./)&&(m=""),window.fromModel=!0,_(()=>he(e,"value",m)),delete window.fromModel},n(()=>{let m=c();t.includes("unintrusive")&&document.activeElement.isSameNode(e)||e._x_forceModelUpdate(m)})});function zt(e,t,r,n){return _(()=>{if(r instanceof CustomEvent&&r.detail!==void 0)return r.detail!==null&&r.detail!==void 0?r.detail:r.target.value;if(e.type==="checkbox")if(Array.isArray(n)){let i=null;return t.includes("number")?i=Kt(r.target.value):t.includes("boolean")?i=ge(r.target.value):i=r.target.value,r.target.checked?n.includes(i)?n:n.concat([i]):n.filter(o=>!Ui(o,i))}else return r.target.checked;else{if(e.tagName.toLowerCase()==="select"&&e.multiple)return t.includes("number")?Array.from(r.target.selectedOptions).map(i=>{let o=i.value||i.text;return Kt(o)}):t.includes("boolean")?Array.from(r.target.selectedOptions).map(i=>{let o=i.value||i.text;return ge(o)}):Array.from(r.target.selectedOptions).map(i=>i.value||i.text);{let i;return e.type==="radio"?r.target.checked?i=r.target.value:i=n:i=r.target.value,t.includes("number")?Kt(i):t.includes("boolean")?ge(i):t.includes("trim")?i.trim():i}}})}function Kt(e){let t=e?parseFloat(e):null;return Wi(t)?t:e}function Ui(e,t){return e==t}function Wi(e){return!Array.isArray(e)&&!isNaN(e)}function bn(e){return e!==null&&typeof e=="object"&&typeof e.get=="function"&&typeof e.set=="function"}d("cloak",e=>queueMicrotask(()=>_(()=>e.removeAttribute(C("cloak")))));$e(()=>`[${C("init")}]`);d("init",A((e,{expression:t},{evaluate:r})=>typeof t=="string"?!!t.trim()&&r(t,{},!1):r(t,{},!1)));d("text",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{_(()=>{e.textContent=o})})})});d("html",(e,{expression:t},{effect:r,evaluateLater:n})=>{let i=n(t);r(()=>{i(o=>{_(()=>{e.innerHTML=o,e._x_ignoreSelf=!0,S(e),delete e._x_ignoreSelf})})})});re(Ie(":",ke(C("bind:"))));var wn=(e,{value:t,modifiers:r,expression:n,original:i},{effect:o,cleanup:s})=>{if(!t){let c={};Hr(c),x(e,n)(u=>{Ct(e,u,i)},{scope:c});return}if(t==="key")return Gi(e,n);if(e._x_inlineBindings&&e._x_inlineBindings[t]&&e._x_inlineBindings[t].extract)return;let a=x(e,n);o(()=>a(c=>{c===void 0&&typeof n=="string"&&n.match(/\./)&&(c=""),_(()=>he(e,t,c,r))})),s(()=>{e._x_undoAddedClasses&&e._x_undoAddedClasses(),e._x_undoAddedStyles&&e._x_undoAddedStyles()})};wn.inline=(e,{value:t,modifiers:r,expression:n})=>{t&&(e._x_inlineBindings||(e._x_inlineBindings={}),e._x_inlineBindings[t]={expression:n,extract:!1})};d("bind",wn);function Gi(e,t){e._x_keyExpression=t}Le(()=>`[${C("data")}]`);d("data",(e,{expression:t},{cleanup:r})=>{if(Ji(e))return;t=t===""?"{}":t;let n={};ue(n,e);let i={};Ur(i,n);let o=M(e,t,{scope:i});(o===void 0||o===!0)&&(o={}),ue(o,e);let s=R(o);Te(s);let a=P(e,s);s.init&&M(e,s.init),r(()=>{s.destroy&&M(e,s.destroy),a()})});K((e,t)=>{e._x_dataStack&&(t._x_dataStack=e._x_dataStack,t.setAttribute("data-has-alpine-state",!0))});function Ji(e){return I?Be?!0:e.hasAttribute("data-has-alpine-state"):!1}d("show",(e,{modifiers:t,expression:r},{effect:n})=>{let i=x(e,r);e._x_doHide||(e._x_doHide=()=>{_(()=>{e.style.setProperty("display","none",t.includes("important")?"important":void 0)})}),e._x_doShow||(e._x_doShow=()=>{_(()=>{e.style.length===1&&e.style.display==="none"?e.removeAttribute("style"):e.style.removeProperty("display")})});let o=()=>{e._x_doHide(),e._x_isShown=!1},s=()=>{e._x_doShow(),e._x_isShown=!0},a=()=>setTimeout(s),c=me(p=>p?s():o(),p=>{typeof e._x_toggleAndCascadeWithTransitions=="function"?e._x_toggleAndCascadeWithTransitions(e,p,s,o):p?a():o()}),l,u=!0;n(()=>i(p=>{!u&&p===l||(t.includes("immediate")&&(p?a():o()),c(p),l=p,u=!1)}))});d("for",(e,{expression:t},{effect:r,cleanup:n})=>{let i=Xi(t),o=x(e,i.items),s=x(e,e._x_keyExpression||"index");e._x_prevKeys=[],e._x_lookup={},r(()=>Yi(e,i,o,s)),n(()=>{Object.values(e._x_lookup).forEach(a=>a.remove()),delete e._x_prevKeys,delete e._x_lookup})});function Yi(e,t,r,n){let i=s=>typeof s=="object"&&!Array.isArray(s),o=e;r(s=>{Zi(s)&&s>=0&&(s=Array.from(Array(s).keys(),f=>f+1)),s===void 0&&(s=[]);let a=e._x_lookup,c=e._x_prevKeys,l=[],u=[];if(i(s))s=Object.entries(s).map(([f,g])=>{let b=En(t,g,f,s);n(v=>{u.includes(v)&&E("Duplicate key on x-for",e),u.push(v)},{scope:{index:f,...b}}),l.push(b)});else for(let f=0;f<s.length;f++){let g=En(t,s[f],f,s);n(b=>{u.includes(b)&&E("Duplicate key on x-for",e),u.push(b)},{scope:{index:f,...g}}),l.push(g)}let p=[],m=[],w=[],$=[];for(let f=0;f<c.length;f++){let g=c[f];u.indexOf(g)===-1&&w.push(g)}c=c.filter(f=>!w.includes(f));let we="template";for(let f=0;f<u.length;f++){let g=u[f],b=c.indexOf(g);if(b===-1)c.splice(f,0,g),p.push([we,f]);else if(b!==f){let v=c.splice(f,1)[0],O=c.splice(b-1,1)[0];c.splice(f,0,O),c.splice(b,0,v),m.push([v,O])}else $.push(g);we=g}for(let f=0;f<w.length;f++){let g=w[f];a[g]._x_effects&&a[g]._x_effects.forEach(Ee),a[g].remove(),a[g]=null,delete a[g]}for(let f=0;f<m.length;f++){let[g,b]=m[f],v=a[g],O=a[b],Q=document.createElement("div");_(()=>{O||E('x-for ":key" is undefined or invalid',o,b,a),O.after(Q),v.after(O),O._x_currentIfEl&&O.after(O._x_currentIfEl),Q.before(v),v._x_currentIfEl&&v.after(v._x_currentIfEl),Q.remove()}),O._x_refreshXForScope(l[u.indexOf(b)])}for(let f=0;f<p.length;f++){let[g,b]=p[f],v=g==="template"?o:a[g];v._x_currentIfEl&&(v=v._x_currentIfEl);let O=l[b],Q=u[b],ae=document.importNode(o.content,!0).firstElementChild,Vt=R(O);P(ae,Vt,o),ae._x_refreshXForScope=Sn=>{Object.entries(Sn).forEach(([An,On])=>{Vt[An]=On})},_(()=>{v.after(ae),A(()=>S(ae))()}),typeof Q=="object"&&E("x-for key cannot be an object, it must be a string or an integer",o),a[Q]=ae}for(let f=0;f<$.length;f++)a[$[f]]._x_refreshXForScope(l[u.indexOf($[f])]);o._x_prevKeys=u})}function Xi(e){let t=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,r=/^\s*\(|\)\s*$/g,n=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,i=e.match(n);if(!i)return;let o={};o.items=i[2].trim();let s=i[1].replace(r,"").trim(),a=s.match(t);return a?(o.item=s.replace(t,"").trim(),o.index=a[1].trim(),a[2]&&(o.collection=a[2].trim())):o.item=s,o}function En(e,t,r,n){let i={};return/^\[.*\]$/.test(e.item)&&Array.isArray(t)?e.item.replace("[","").replace("]","").split(",").map(s=>s.trim()).forEach((s,a)=>{i[s]=t[a]}):/^\{.*\}$/.test(e.item)&&!Array.isArray(t)&&typeof t=="object"?e.item.replace("{","").replace("}","").split(",").map(s=>s.trim()).forEach(s=>{i[s]=t[s]}):i[e.item]=t,e.index&&(i[e.index]=r),e.collection&&(i[e.collection]=n),i}function Zi(e){return!Array.isArray(e)&&!isNaN(e)}function vn(){}vn.inline=(e,{expression:t},{cleanup:r})=>{let n=J(e);n._x_refs||(n._x_refs={}),n._x_refs[t]=e,r(()=>delete n._x_refs[t])};d("ref",vn);d("if",(e,{expression:t},{effect:r,cleanup:n})=>{e.tagName.toLowerCase()!=="template"&&E("x-if can only be used on a <template> tag",e);let i=x(e,t),o=()=>{if(e._x_currentIfEl)return e._x_currentIfEl;let a=e.content.cloneNode(!0).firstElementChild;return P(a,{},e),_(()=>{e.after(a),A(()=>S(a))()}),e._x_currentIfEl=a,e._x_undoIf=()=>{T(a,c=>{c._x_effects&&c._x_effects.forEach(Ee)}),a.remove(),delete e._x_currentIfEl},a},s=()=>{e._x_undoIf&&(e._x_undoIf(),delete e._x_undoIf)};r(()=>i(a=>{a?o():s()})),n(()=>e._x_undoIf&&e._x_undoIf())});d("id",(e,{expression:t},{evaluate:r})=>{r(t).forEach(i=>mn(e,i))});K((e,t)=>{e._x_ids&&(t._x_ids=e._x_ids)});re(Ie("@",ke(C("on:"))));d("on",A((e,{value:t,modifiers:r,expression:n},{cleanup:i})=>{let o=n?x(e,n):()=>{};e.tagName.toLowerCase()==="template"&&(e._x_forwardEvents||(e._x_forwardEvents=[]),e._x_forwardEvents.includes(t)||e._x_forwardEvents.push(t));let s=se(e,t,r,a=>{o(()=>{},{scope:{$event:a},params:[a]})});i(()=>s())}));tt("Collapse","collapse","collapse");tt("Intersect","intersect","intersect");tt("Focus","trap","focus");tt("Mask","mask","mask");function tt(e,t,r){d(t,n=>E(`You can't use [x-${t}] without first installing the "${e}" plugin here: https://alpinejs.dev/plugins/${r}`,n))}B.setEvaluator(gt);B.setReactivityEngine({reactive:Qe,effect:en,release:tn,raw:h});var Ht=B;window.Alpine=Ht;queueMicrotask(()=>{Ht.start()});})(); -- GitLab From bd194f7ebe5f0eb82a7ccf7ea051e3cbe7e4ea7d Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 10:39:27 +0200 Subject: [PATCH 05/31] Update container id --- views/toolbox/index.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 1e4c4ad..ae1a018 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,4 +1,4 @@ -<ul id="simpleList" class="content-items toolbox" x-data="initApp()"> +<ul id="userTools" class="content-items toolbox" x-data="initApp()"> <? foreach ($tools as $key => $tool) : ?> <li data-id="<?= $tool->id ?>" class="content-item dragarea"> <div class="top_icons"> @@ -36,7 +36,7 @@ <script> STUDIP.ready(() => { - var sortable = Sortable.create(simpleList, { + var sortable = Sortable.create(userTools, { animation: 150, ghostClass: 'dropable-placeholder', dataIdAttr: 'data-id', -- GitLab From 92529803d92e4f0e3b1d81a4b2b74db96aa1f432 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 11:55:16 +0200 Subject: [PATCH 06/31] Make dragable through arrow keys --- views/toolbox/index.php | 76 +++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index ae1a018..369449f 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,6 +1,6 @@ <ul id="userTools" class="content-items toolbox" x-data="initApp()"> <? foreach ($tools as $key => $tool) : ?> - <li data-id="<?= $tool->id ?>" class="content-item dragarea"> + <li toolId="<?= $tool->id ?>" class="content-item dragarea"> <div class="top_icons"> <? if ($tool['long_description']) : ?> <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" @@ -26,37 +26,69 @@ </div> <div class="info"><?= htmlReady($tool['info']) ?></div> </a> - <a class="dragarea" tabindex="0"> - <span class="drag-handle"></span> - </a> + <div> + <a class="dragarea" tabindex="0" toolId="<?= $tool->id ?>" @keydown="sortTool"> + <span class="drag-handle"></span> + </a> + </div> </li> <? endforeach ?> </ul> +<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> <script> - STUDIP.ready(() => { - var sortable = Sortable.create(userTools, { - animation: 150, - ghostClass: 'dropable-placeholder', - dataIdAttr: 'data-id', - handle: ".dragarea", - - onSort: (event) => { - storeToolOrder() - }, + var sortable = Sortable.create(userTools, { + animation: 150, + ghostClass: 'dropable-placeholder', + dataIdAttr: 'toolId', + handle: ".dragarea", + + onSort: (event) => { + storeTool(); + }, + }); + + const storeTool = async () => { + let formData = new FormData(); + formData.append('order', sortable.toArray()); + + await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/save_order`), { + method: 'POST', + body: formData }); + }; - const storeToolOrder = async () => { - let formData = new FormData(); - formData.append('order', sortable.toArray()); + function initApp() { + return { + sortTool($event) { + if ($event.keyCode >= 37 && $event.keyCode <= 40) { + let toolId = $event.target.getAttribute('toolId'); + let step = 1; - await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/save_order`), { - method: 'POST', - body: formData - }); + + if ($event.keyCode == 37 || $event.keyCode == 38) { + step = -1; + } + + sortable.sort(this.swap(toolId, step), true); + storeTool() + $event.target.focus(); + } + }, + + swap(toolId, step) { + let toolIds = sortable.toArray(); + let index = toolIds.findIndex(tool => tool == toolId); + + let temp = toolIds[index + step]; + toolIds[index + step] = toolIds[index]; + toolIds[index] = temp; + + return toolIds + } }; - }); + } </script> <? -- GitLab From 6fb08dd5a72b8709a8646ab34a3ae3e7f1f9a6cb Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 12:35:12 +0200 Subject: [PATCH 07/31] Update tool state once after a delay --- views/toolbox/index.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 369449f..ab8ea1c 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -59,6 +59,18 @@ }); }; + function debounce(callback, timeout = 1000){ + let timer; + return () => { + clearTimeout(timer); + timer = setTimeout(() => { + callback(); + }, timeout); + }; + } + + const storeToolWithDelay = debounce(() => storeTool(), 3000); + function initApp() { return { sortTool($event) { @@ -72,7 +84,7 @@ } sortable.sort(this.swap(toolId, step), true); - storeTool() + storeToolWithDelay(); $event.target.focus(); } }, -- GitLab From f441f02254448ca4911609cb78a1fa372c733a15 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 12:37:51 +0200 Subject: [PATCH 08/31] Reduce the debounce timer --- views/toolbox/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index ab8ea1c..8229826 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -69,7 +69,7 @@ }; } - const storeToolWithDelay = debounce(() => storeTool(), 3000); + const storeToolWithDelay = debounce(() => storeTool(), 2500); function initApp() { return { -- GitLab From fecfb62bc71983d915588c91352ba6d4e5629808 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 12:40:50 +0200 Subject: [PATCH 09/31] Refactor method name --- views/toolbox/index.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 8229826..0b015c9 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -45,11 +45,11 @@ handle: ".dragarea", onSort: (event) => { - storeTool(); + storeToolOrder(); }, }); - const storeTool = async () => { + const storeToolOrder = async () => { let formData = new FormData(); formData.append('order', sortable.toArray()); @@ -69,7 +69,7 @@ }; } - const storeToolWithDelay = debounce(() => storeTool(), 2500); + const storeToolOrderWithDelay = debounce(() => storeToolOrder(), 2500); function initApp() { return { @@ -84,7 +84,7 @@ } sortable.sort(this.swap(toolId, step), true); - storeToolWithDelay(); + storeToolOrderWithDelay(); $event.target.focus(); } }, -- GitLab From cae7e79be3c6de85ed20c5419020c077c3e026df Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 12:41:41 +0200 Subject: [PATCH 10/31] Refactor method name --- views/toolbox/index.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 0b015c9..4ea673a 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -83,13 +83,13 @@ step = -1; } - sortable.sort(this.swap(toolId, step), true); + sortable.sort(this.swapTool(toolId, step), true); storeToolOrderWithDelay(); $event.target.focus(); } }, - swap(toolId, step) { + swapTool(toolId, step) { let toolIds = sortable.toArray(); let index = toolIds.findIndex(tool => tool == toolId); @@ -97,7 +97,7 @@ toolIds[index + step] = toolIds[index]; toolIds[index] = temp; - return toolIds + return toolIds: } }; } -- GitLab From 1bf7a41d657fb9dce7707f43223ad7a8a5147214 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 12:43:00 +0200 Subject: [PATCH 11/31] Refactor method name --- views/toolbox/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 4ea673a..03a73b2 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -97,7 +97,7 @@ toolIds[index + step] = toolIds[index]; toolIds[index] = temp; - return toolIds: + return toolIds; } }; } -- GitLab From 971a5085130bd2db2b493065a424b00bb7042fbf Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 12:49:37 +0200 Subject: [PATCH 12/31] Add arialebel content --- views/toolbox/index.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 03a73b2..a9458c4 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -27,7 +27,12 @@ <div class="info"><?= htmlReady($tool['info']) ?></div> </a> <div> - <a class="dragarea" tabindex="0" toolId="<?= $tool->id ?>" @keydown="sortTool"> + <a + class="dragarea" + tabindex="0" + toolId="<?= $tool->id ?>" + aria-label="<?= _('Sortierelement für Werkzeug Toolbox. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.')?>" + @keydown="sortTool"> <span class="drag-handle"></span> </a> </div> -- GitLab From 164adf889bab8335ecf49e57ae2d621b2da04301 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 14:47:52 +0200 Subject: [PATCH 13/31] Make tools favorable --- assets/toolbox.scss | 16 ++++++++++++++++ controllers/toolbox.php | 18 ++++++++++++++++++ lib/ToolboxTool.php | 7 +++++++ views/toolbox/index.php | 13 ++++++++++++- 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/assets/toolbox.scss b/assets/toolbox.scss index 31c63a5..d07e5db 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -11,6 +11,13 @@ padding-top: 5px; padding-right: 5px; } + .bottom_icons { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1em; + padding: 0 5px; + } .content-item-link { padding-top: 0px; &.--no-icon { @@ -51,3 +58,12 @@ margin-top: 5px; background-size: auto 20px; } + +.favorite-icon-container { + &.opacity-50 { + opacity: 0.5; + } + &.opacity-50:hover { + opacity: 0.8; + } +} diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 918aad8..3650c9d 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -73,4 +73,22 @@ class ToolboxController extends PluginController 'success' => true ]); } + + public function toggle_favorite_action($toolId) + { + $toolboxUserTool = ToolboxUserTool::getUserTool($toolId); + + if(! $toolboxUserTool) { + $toolboxUserTool = new ToolboxUserTool(); + $toolboxUserTool->user_id = User::findCurrent()->id; + $toolboxUserTool->tool_id = $toolId; + $toolboxUserTool->favorite = 1; + } else { + $toolboxUserTool->favorite = ! $toolboxUserTool->favorite; + } + + $toolboxUserTool->store(); + + $this->relocate('toolbox/index'); + } } diff --git a/lib/ToolboxTool.php b/lib/ToolboxTool.php index 6c87c63..72bc457 100644 --- a/lib/ToolboxTool.php +++ b/lib/ToolboxTool.php @@ -5,6 +5,13 @@ class ToolboxTool extends SimpleORMap protected static function configure($config = []) { $config['db_table'] = 'toolbox_tools'; + $config['has_one']['toolbox_user_tools'] = [ + 'class_name' => ToolboxUserTool::class, + 'assoc_func' => 'getUserTool', + 'foreign_key' => 'tool_id', + 'assoc_foreign_key' => 'tool_id', + 'on_delete' => 'delete', + ]; parent::configure($config); } } diff --git a/views/toolbox/index.php b/views/toolbox/index.php index a9458c4..1962164 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -26,7 +26,7 @@ </div> <div class="info"><?= htmlReady($tool['info']) ?></div> </a> - <div> + <div class="bottom_icons"> <a class="dragarea" tabindex="0" @@ -35,6 +35,17 @@ @keydown="sortTool"> <span class="drag-handle"></span> </a> + + <div> + <a + title="<?= _('Mark/Unmark it as favorite') ?>" + aria-label="<?= _('TODO: need to write something here.')?>" + class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? '' : 'opacity-50' ?>" + href="<?= PluginEngine::getLink($plugin, [], 'toolbox/toggle_favorite/'.$tool->id) ?>" + > + <?= Icon::create($tool->toolbox_user_tools?->favorite ? 'star' : 'star-empty')->asImg(20) ?> + </a> + </div> </div> </li> <? endforeach ?> -- GitLab From 197e86977d7a6716644ba46a71db213ae1fbdab4 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 15:26:47 +0200 Subject: [PATCH 14/31] Add two sidebar link --- assets/toolbox.scss | 1 + controllers/toolbox.php | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/assets/toolbox.scss b/assets/toolbox.scss index d07e5db..2f8a619 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -57,6 +57,7 @@ height: 20px; margin-top: 5px; background-size: auto 20px; + cursor: move; } .favorite-icon-container { diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 3650c9d..551260b 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -5,6 +5,22 @@ class ToolboxController extends PluginController public function before_filter(&$action, &$args) { PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/sortable.min.js'); + + $viewWidget = new ViewsWidget(); + + $viewWidget->addLink( + _('Alle'), + PluginEngine::getURL($this->plugin, [], 'toolbox/index') + )->setActive(str_ends_with(Request::path(), '/toolbox/index')); + + $viewWidget->addLink( + _('Nur Favorites'), + PluginEngine::getURL($this->plugin, [ + 'onlyFavorites' => 1, + ], 'toolbox') + )->setActive(str_ends_with(Request::path(), '/toolbox?onlyFavorites=1')); + + Sidebar::Get()->addWidget($viewWidget); parent::before_filter($action, $args); } @@ -15,9 +31,12 @@ class ToolboxController extends PluginController PageLayout::postInfo(nl2br(htmlReady(Config::get()->TOOLBOX_INFO))); } $this->tools = ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) - WHERE `toolbox_tools`.`active` = 1 + WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', - ['user_id' => User::findCurrent()->id] + [ + 'user_id' => User::findCurrent()->id, + 'favorites' => Request::option('onlyFavorites') ? [1] : [0, 1], + ] ); } -- GitLab From 30e791120cca2a02cc38475d43160900ec678252 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Wed, 3 Jul 2024 15:44:53 +0200 Subject: [PATCH 15/31] Load Alpine script through controller --- controllers/admin.php | 1 + controllers/toolbox.php | 2 ++ views/admin/edit_tool.php | 2 -- views/toolbox/index.php | 3 --- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/controllers/admin.php b/controllers/admin.php index df482b3..d336c3c 100644 --- a/controllers/admin.php +++ b/controllers/admin.php @@ -4,6 +4,7 @@ class AdminController extends PluginController { public function before_filter(&$action, &$args) { + PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/alpinejs.min.js', ['defer' => '1']); parent::before_filter($action, $args); if (!Toolbox::isAdmin()) { throw new AccessDeniedException(); diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 551260b..160eeb9 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -21,6 +21,8 @@ class ToolboxController extends PluginController )->setActive(str_ends_with(Request::path(), '/toolbox?onlyFavorites=1')); Sidebar::Get()->addWidget($viewWidget); + + PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/alpinejs.min.js', ['defer' => '1']); parent::before_filter($action, $args); } diff --git a/views/admin/edit_tool.php b/views/admin/edit_tool.php index 6b1304a..73cb7e1 100644 --- a/views/admin/edit_tool.php +++ b/views/admin/edit_tool.php @@ -107,8 +107,6 @@ </div> </form> -<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> - <script> var iconType = <?= $tool->isNew() ? json_encode('studip') : json_encode($tool['icon_type']) ?>; diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 1962164..b9b9585 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -51,8 +51,6 @@ <? endforeach ?> </ul> -<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> - <script> var sortable = Sortable.create(userTools, { animation: 150, @@ -94,7 +92,6 @@ let toolId = $event.target.getAttribute('toolId'); let step = 1; - if ($event.keyCode == 37 || $event.keyCode == 38) { step = -1; } -- GitLab From 184126d08f8355f3751e0c5aba70385863eaff9f Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 10:17:39 +0200 Subject: [PATCH 16/31] Add Favorites Toolbox to start page --- Toolbox.class.php | 24 ++++++++++++++++++++++-- assets/toolbox.scss | 2 +- controllers/admin.php | 1 - controllers/toolbox.php | 26 -------------------------- views/admin/edit_tool.php | 2 ++ views/toolbox/index.php | 14 +++----------- views/toolbox/sort.php | 19 ------------------- 7 files changed, 28 insertions(+), 60 deletions(-) delete mode 100644 views/toolbox/sort.php diff --git a/Toolbox.class.php b/Toolbox.class.php index 54e1233..1bb3969 100644 --- a/Toolbox.class.php +++ b/Toolbox.class.php @@ -1,6 +1,6 @@ <?php -class Toolbox extends StudIPPlugin implements SystemPlugin +class Toolbox extends StudIPPlugin implements SystemPlugin, PortalPlugin { static public function isAdmin() { @@ -27,6 +27,7 @@ class Toolbox extends StudIPPlugin implements SystemPlugin { parent::__construct(); StudipAutoloader::addAutoloadPath(__DIR__ . "/lib"); + $this->addStylesheet('assets/toolbox.scss'); if (ToolboxTool::countBySql('`active` = 1') > 0) { $nav = new Navigation(_('Toolbox'), PluginEngine::getURL($this, [], 'toolbox/index')); @@ -46,7 +47,6 @@ class Toolbox extends StudIPPlugin implements SystemPlugin public function perform($unconsumed_path) { - $this->addStylesheet('assets/toolbox.scss'); parent::perform($unconsumed_path); // TODO: Change the autogenerated stub } @@ -88,4 +88,24 @@ class Toolbox extends StudIPPlugin implements SystemPlugin return ""; } + function getPortalTemplate() + { + $tools = ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) + WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 + ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', + [ + 'user_id' => User::findCurrent()->id, + 'favorites' => [1], + ] + ); + $tf = new Flexi_TemplateFactory(__DIR__.'/views'); + $template = $tf->open('toolbox/index.php'); + $template->plugin = $this; + $template->title = _('Toolbox'); + $template->tools = $tools; + $nav = new Navigation(_('Toolbox'), PluginEngine::getURL($this, [], 'toolbox/index')); + $nav->setImage(Icon::create('add')); + $template->icons = [$nav]; + return $template; + } } diff --git a/assets/toolbox.scss b/assets/toolbox.scss index 2f8a619..462e448 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -47,7 +47,7 @@ .dropable-placeholder { background-color: white !important; - border: #d3d3d3 2px dashed !important; + border: $base-color-20 2px dashed !important; content-visibility: hidden !important; } diff --git a/controllers/admin.php b/controllers/admin.php index d336c3c..df482b3 100644 --- a/controllers/admin.php +++ b/controllers/admin.php @@ -4,7 +4,6 @@ class AdminController extends PluginController { public function before_filter(&$action, &$args) { - PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/alpinejs.min.js', ['defer' => '1']); parent::before_filter($action, $args); if (!Toolbox::isAdmin()) { throw new AccessDeniedException(); diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 160eeb9..bdb2987 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -6,23 +6,6 @@ class ToolboxController extends PluginController { PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/sortable.min.js'); - $viewWidget = new ViewsWidget(); - - $viewWidget->addLink( - _('Alle'), - PluginEngine::getURL($this->plugin, [], 'toolbox/index') - )->setActive(str_ends_with(Request::path(), '/toolbox/index')); - - $viewWidget->addLink( - _('Nur Favorites'), - PluginEngine::getURL($this->plugin, [ - 'onlyFavorites' => 1, - ], 'toolbox') - )->setActive(str_ends_with(Request::path(), '/toolbox?onlyFavorites=1')); - - Sidebar::Get()->addWidget($viewWidget); - - PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/alpinejs.min.js', ['defer' => '1']); parent::before_filter($action, $args); } @@ -48,15 +31,6 @@ class ToolboxController extends PluginController $this->tool = $tool; } - public function sort_action() - { - $this->tools = ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) - WHERE `toolbox_tools`.`active` = 1 - ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) DESC, `toolbox_tools`.`name` ASC', - ['user_id' => User::findCurrent()->id] - ); - } - public function display_icon_action() { $icon = Toolbox::getIconsDir() . Request::option('icon_name'); diff --git a/views/admin/edit_tool.php b/views/admin/edit_tool.php index 73cb7e1..6b1304a 100644 --- a/views/admin/edit_tool.php +++ b/views/admin/edit_tool.php @@ -107,6 +107,8 @@ </div> </form> +<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> + <script> var iconType = <?= $tool->isNew() ? json_encode('studip') : json_encode($tool['icon_type']) ?>; diff --git a/views/toolbox/index.php b/views/toolbox/index.php index b9b9585..bd42bfd 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -51,8 +51,11 @@ <? endforeach ?> </ul> +<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> + <script> var sortable = Sortable.create(userTools, { + group: 'toolbox', animation: 150, ghostClass: 'dropable-placeholder', dataIdAttr: 'toolId', @@ -116,15 +119,4 @@ } </script> -<? - -$actions = new ActionsWidget(); -$actions->addLink( - _('Reihenfolge ändern'), - PluginEngine::getURL($plugin, [], 'toolbox/sort'), - Icon::create('settings'), - ['data-dialog' => 1] -); -Sidebar::Get()->addWidget($actions); - diff --git a/views/toolbox/sort.php b/views/toolbox/sort.php deleted file mode 100644 index d74aa8a..0000000 --- a/views/toolbox/sort.php +++ /dev/null @@ -1,19 +0,0 @@ -<? -$tools_object = array_map(function ($t) { - return [ - 'tool_id' => $t->id, - 'name' => $t['name'], - 'icon' => $t['icon'], - 'priority' => 1 - ]; -}, $tools); -?> -<ol class="clean" - id="toolbox_sorter" data-tools="<?= htmlReady(json_encode($tools_object)) ?>"> - <li v-for="tool in sortedTools" :key="tool.tool_id"> - <span class="drag-handle"></span> - <input type="hidden" name="order[]" :value="tool.tool_id"> - <studip-icon :shape="tool.icon" v-if="tool.icon" size="20"></studip-icon> - {{ tool.name }} - </li> -</ol> -- GitLab From 6d2527a915678d4d2255c9846a30c7059f4cc7d8 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 10:31:04 +0200 Subject: [PATCH 17/31] Do some refactoring and fix some style issues --- Toolbox.class.php | 13 ++++--------- assets/toolbox.scss | 3 +++ controllers/toolbox.php | 11 +++-------- lib/ToolboxTool.php | 15 +++++++++++++++ views/toolbox/index.php | 14 +++++++++++++- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/Toolbox.class.php b/Toolbox.class.php index 1bb3969..0aa6eea 100644 --- a/Toolbox.class.php +++ b/Toolbox.class.php @@ -90,22 +90,17 @@ class Toolbox extends StudIPPlugin implements SystemPlugin, PortalPlugin function getPortalTemplate() { - $tools = ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) - WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 - ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', - [ - 'user_id' => User::findCurrent()->id, - 'favorites' => [1], - ] - ); + $tools = ToolboxTool::getToolbox([1]); + $tf = new Flexi_TemplateFactory(__DIR__.'/views'); $template = $tf->open('toolbox/index.php'); $template->plugin = $this; - $template->title = _('Toolbox'); + $template->title = _('Favorites Toolbox'); $template->tools = $tools; $nav = new Navigation(_('Toolbox'), PluginEngine::getURL($this, [], 'toolbox/index')); $nav->setImage(Icon::create('add')); $template->icons = [$nav]; + $template->isHomePageActive = true; return $template; } } diff --git a/assets/toolbox.scss b/assets/toolbox.scss index 462e448..d72f41e 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -1,5 +1,8 @@ .toolbox.content-items { display: grid; + &.home-page { + padding: 10px; + } .content-item { min-height: 130px; height: auto; diff --git a/controllers/toolbox.php b/controllers/toolbox.php index bdb2987..835b3cb 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -15,14 +15,9 @@ class ToolboxController extends PluginController if (Config::get()->TOOLBOX_INFO) { PageLayout::postInfo(nl2br(htmlReady(Config::get()->TOOLBOX_INFO))); } - $this->tools = ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) - WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 - ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', - [ - 'user_id' => User::findCurrent()->id, - 'favorites' => Request::option('onlyFavorites') ? [1] : [0, 1], - ] - ); + + $this->isHomePageActive = false; + $this->tools = ToolboxTool::getToolbox(); } public function info_action(ToolboxTool $tool) diff --git a/lib/ToolboxTool.php b/lib/ToolboxTool.php index 72bc457..2716e0d 100644 --- a/lib/ToolboxTool.php +++ b/lib/ToolboxTool.php @@ -14,4 +14,19 @@ class ToolboxTool extends SimpleORMap ]; parent::configure($config); } + + /** + * @param $favorites[] + * @return ToolboxTool[] + */ + public static function getToolbox($favorites = [0,1]) { + return ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) + WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 + ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', + [ + 'user_id' => User::findCurrent()->id, + 'favorites' => $favorites, + ] + ); + } } diff --git a/views/toolbox/index.php b/views/toolbox/index.php index bd42bfd..6532886 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,4 +1,7 @@ -<ul id="userTools" class="content-items toolbox" x-data="initApp()"> +<ul + id="userTools" + class="content-items toolbox <?= $isHomePageActive ? 'home-page' : '' ?>" + x-data="initApp()"> <? foreach ($tools as $key => $tool) : ?> <li toolId="<?= $tool->id ?>" class="content-item dragarea"> <div class="top_icons"> @@ -27,6 +30,7 @@ <div class="info"><?= htmlReady($tool['info']) ?></div> </a> <div class="bottom_icons"> + <? if (! $isHomePageActive) : ?> <a class="dragarea" tabindex="0" @@ -46,6 +50,14 @@ <?= Icon::create($tool->toolbox_user_tools?->favorite ? 'star' : 'star-empty')->asImg(20) ?> </a> </div> + <? else: ?> + <div></div> + <div> + <span class="favorite-icon-container"> + <?= Icon::create('star')->asImg(20) ?> + </span> + </div> + <? endif ?> </div> </li> <? endforeach ?> -- GitLab From 0823aec394230ae108381421ad8338ef3f6a0982 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 11:56:16 +0200 Subject: [PATCH 18/31] Replace class name with self keyword --- lib/ToolboxTool.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ToolboxTool.php b/lib/ToolboxTool.php index 2716e0d..47dfc9a 100644 --- a/lib/ToolboxTool.php +++ b/lib/ToolboxTool.php @@ -20,7 +20,7 @@ class ToolboxTool extends SimpleORMap * @return ToolboxTool[] */ public static function getToolbox($favorites = [0,1]) { - return ToolboxTool::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) + return self::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', [ -- GitLab From cf60f8e869be910053c65c164c4d0823ba6f5749 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 12:09:13 +0200 Subject: [PATCH 19/31] Add info message when there is not toolbox avaiable --- Toolbox.class.php | 13 ++--- assets/toolbox.scss | 6 +- views/toolbox/index.php | 123 +++++++++++++++++++++------------------- 3 files changed, 74 insertions(+), 68 deletions(-) diff --git a/Toolbox.class.php b/Toolbox.class.php index 0aa6eea..4b83393 100644 --- a/Toolbox.class.php +++ b/Toolbox.class.php @@ -29,12 +29,11 @@ class Toolbox extends StudIPPlugin implements SystemPlugin, PortalPlugin StudipAutoloader::addAutoloadPath(__DIR__ . "/lib"); $this->addStylesheet('assets/toolbox.scss'); - if (ToolboxTool::countBySql('`active` = 1') > 0) { - $nav = new Navigation(_('Toolbox'), PluginEngine::getURL($this, [], 'toolbox/index')); - $nav->setImage(Icon::create('knife', Icon::ROLE_CLICKABLE)); - $nav->setDescription(_('Externe Toolsammlung für kollaboratives Zusammenarbeiten')); - Navigation::addItem('/contents/toolbox', $nav); - } + $nav = new Navigation(_('Toolbox'), PluginEngine::getURL($this, [], 'toolbox/index')); + $nav->setImage(Icon::create('knife', Icon::ROLE_CLICKABLE)); + $nav->setDescription(_('Externe Toolsammlung für kollaboratives Zusammenarbeiten')); + Navigation::addItem('/contents/toolbox', $nav); + if (Toolbox::isAdmin()) { $nav = new Navigation(_('Toolbox'), PluginEngine::getURL($this, [], 'admin/toolbox')); if (Navigation::hasItem('/admin/locations')) { @@ -92,7 +91,7 @@ class Toolbox extends StudIPPlugin implements SystemPlugin, PortalPlugin { $tools = ToolboxTool::getToolbox([1]); - $tf = new Flexi_TemplateFactory(__DIR__.'/views'); + $tf = new Flexi_TemplateFactory(__DIR__ . '/views'); $template = $tf->open('toolbox/index.php'); $template->plugin = $this; $template->title = _('Favorites Toolbox'); diff --git a/assets/toolbox.scss b/assets/toolbox.scss index d72f41e..b562ae5 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -1,8 +1,8 @@ +.home-page { + padding: 10px; +} .toolbox.content-items { display: grid; - &.home-page { - padding: 10px; - } .content-item { min-height: 130px; height: auto; diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 6532886..74f2d06 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,67 +1,74 @@ -<ul - id="userTools" - class="content-items toolbox <?= $isHomePageActive ? 'home-page' : '' ?>" - x-data="initApp()"> - <? foreach ($tools as $key => $tool) : ?> - <li toolId="<?= $tool->id ?>" class="content-item dragarea"> - <div class="top_icons"> - <? if ($tool['long_description']) : ?> - <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" - data-dialog - title="<?= _('Detailierte Informationen zu dem Werkzeug') ?>"> - <?= Icon::create('info-circle')->asImg(20) ?> - </a> - <? endif ?> - </div> - <a href="<?= htmlReady($tool['url']) ?>" target="_blank" class="content-item-link <?= $tool['icon_type'] == 'no-icon' ? '--no-icon' : '' ?>"> - <? if ($tool['icon_type'] != 'no-icon') : ?> - <div class="content-item-img-wrapper"> - <?= $plugin->echoIcon($tool, $tool['icon_type'], 64) ?> - </div> - <? endif ?> - <div class="content-item-text"> - <p class="content-item-title"> - <?= htmlReady($tool['name']) ?> - </p> - <p class="content-item-description"> - <?= htmlReady(mila($tool['description'], 70)) ?> - </p> +<div class="<?= $isHomePageActive ? 'home-page' : '' ?>"> + <ul + id="userTools" + class="content-items toolbox" + x-data="initApp()"> + <? foreach ($tools as $key => $tool) : ?> + <li toolId="<?= $tool->id ?>" class="content-item dragarea"> + <div class="top_icons"> + <? if ($tool['long_description']) : ?> + <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" + data-dialog + title="<?= _('Detailierte Informationen zu dem Werkzeug') ?>"> + <?= Icon::create('info-circle')->asImg(20) ?> + </a> + <? endif ?> </div> - <div class="info"><?= htmlReady($tool['info']) ?></div> - </a> - <div class="bottom_icons"> - <? if (! $isHomePageActive) : ?> - <a - class="dragarea" - tabindex="0" - toolId="<?= $tool->id ?>" - aria-label="<?= _('Sortierelement für Werkzeug Toolbox. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.')?>" - @keydown="sortTool"> - <span class="drag-handle"></span> + <a href="<?= htmlReady($tool['url']) ?>" target="_blank" class="content-item-link <?= $tool['icon_type'] == 'no-icon' ? '--no-icon' : '' ?>"> + <? if ($tool['icon_type'] != 'no-icon') : ?> + <div class="content-item-img-wrapper"> + <?= $plugin->echoIcon($tool, $tool['icon_type'], 64) ?> + </div> + <? endif ?> + <div class="content-item-text"> + <p class="content-item-title"> + <?= htmlReady($tool['name']) ?> + </p> + <p class="content-item-description"> + <?= htmlReady(mila($tool['description'], 70)) ?> + </p> + </div> + <div class="info"><?= htmlReady($tool['info']) ?></div> </a> - - <div> + <div class="bottom_icons"> + <? if (! $isHomePageActive) : ?> <a - title="<?= _('Mark/Unmark it as favorite') ?>" - aria-label="<?= _('TODO: need to write something here.')?>" - class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? '' : 'opacity-50' ?>" - href="<?= PluginEngine::getLink($plugin, [], 'toolbox/toggle_favorite/'.$tool->id) ?>" - > - <?= Icon::create($tool->toolbox_user_tools?->favorite ? 'star' : 'star-empty')->asImg(20) ?> + class="dragarea" + tabindex="0" + toolId="<?= $tool->id ?>" + aria-label="<?= _('Sortierelement für Werkzeug Toolbox. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.')?>" + @keydown="sortTool"> + <span class="drag-handle"></span> </a> - </div> - <? else: ?> - <div></div> + <div> - <span class="favorite-icon-container"> - <?= Icon::create('star')->asImg(20) ?> - </span> + <a + title="<?= _('Mark/Unmark it as favorite') ?>" + aria-label="<?= _('TODO: need to write something here.')?>" + class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? '' : 'opacity-50' ?>" + href="<?= PluginEngine::getLink($plugin, [], 'toolbox/toggle_favorite/'.$tool->id) ?>" + > + <?= Icon::create($tool->toolbox_user_tools?->favorite ? 'star' : 'star-empty')->asImg(20) ?> + </a> </div> - <? endif ?> - </div> - </li> - <? endforeach ?> -</ul> + <? else: ?> + <div></div> + <div> + <span class="favorite-icon-container"> + <?= Icon::create('star')->asImg(20) ?> + </span> + </div> + <? endif ?> + </div> + </li> + <? endforeach ?> + </ul> + <?php if (count($tools) === 0) : ?> + <p> + <?= _('Es sind noch keine Toolbox vorhanden.') ?> + </p> + <?php endif ?> +</div> <script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> -- GitLab From e299c077ac60cb055592031fb6e31139a6bcf958 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 12:14:02 +0200 Subject: [PATCH 20/31] Add info message when there is not toolbox avaiable --- views/toolbox/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 74f2d06..3336f36 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -65,7 +65,7 @@ </ul> <?php if (count($tools) === 0) : ?> <p> - <?= _('Es sind noch keine Toolbox vorhanden.') ?> + <?= sprintf(_("Es sind noch keine %s Toolbox vorhanden."), $isHomePageActive ? 'Favorite' : '') ?> </p> <?php endif ?> </div> -- GitLab From 9d191cb77ac1b16e54792b052ebe7183749ba46a Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 12:17:25 +0200 Subject: [PATCH 21/31] Reduce the delay time of storeToolOrderWithDelay --- views/toolbox/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 3336f36..e4571e3 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -105,7 +105,7 @@ }; } - const storeToolOrderWithDelay = debounce(() => storeToolOrder(), 2500); + const storeToolOrderWithDelay = debounce(() => storeToolOrder(), 1500); function initApp() { return { -- GitLab From 2994e83b000b6ca7c212df49f3747ba45279a8fb Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 14:12:39 +0200 Subject: [PATCH 22/31] Update getToolbox query and info text message --- controllers/toolbox.php | 2 ++ lib/ToolboxTool.php | 15 ++++++++------- views/toolbox/index.php | 15 +++++++++------ 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 835b3cb..0af2602 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -16,6 +16,8 @@ class ToolboxController extends PluginController PageLayout::postInfo(nl2br(htmlReady(Config::get()->TOOLBOX_INFO))); } + MessageBox::info('sd'); + $this->isHomePageActive = false; $this->tools = ToolboxTool::getToolbox(); } diff --git a/lib/ToolboxTool.php b/lib/ToolboxTool.php index 47dfc9a..954d893 100644 --- a/lib/ToolboxTool.php +++ b/lib/ToolboxTool.php @@ -16,16 +16,17 @@ class ToolboxTool extends SimpleORMap } /** - * @param $favorites[] + * @param bool $onlyFavorite * @return ToolboxTool[] */ - public static function getToolbox($favorites = [0,1]) { - return self::findBySQL('LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) - WHERE `toolbox_user_tools`.`favorite` IN (:favorites) AND `toolbox_tools`.`active` = 1 - ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC', + public static function getToolbox($onlyFavorite = false) { + $withFavorites = $onlyFavorite ? '`toolbox_user_tools`.`favorite` = 1 AND' : null; + + return self::findBySQL("LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) + WHERE $withFavorites `toolbox_tools`.`active` = 1 + ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC", [ - 'user_id' => User::findCurrent()->id, - 'favorites' => $favorites, + 'user_id' => User::findCurrent()->id ] ); } diff --git a/views/toolbox/index.php b/views/toolbox/index.php index e4571e3..9c562b6 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -1,4 +1,11 @@ <div class="<?= $isHomePageActive ? 'home-page' : '' ?>"> + <?php if (count($tools) === 0) : ?> + <?php if ($isHomePageActive ): ?> + <?= MessageBox::info('Um hier Tools anzuzeigen, gehen Sie zur Toolbox in Ihren Arbeitsplatz und markieren Sie die für Sie wichtigen Tools mit einem Sternchen.') ?> + <?php else: ?> + <?= MessageBox::info('Es gibt noch keine Tools.') ?> + <?php endif ?> + <?php endif ?> <ul id="userTools" class="content-items toolbox" @@ -63,11 +70,7 @@ </li> <? endforeach ?> </ul> - <?php if (count($tools) === 0) : ?> - <p> - <?= sprintf(_("Es sind noch keine %s Toolbox vorhanden."), $isHomePageActive ? 'Favorite' : '') ?> - </p> - <?php endif ?> + </div> <script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> @@ -105,7 +108,7 @@ }; } - const storeToolOrderWithDelay = debounce(() => storeToolOrder(), 1500); + const storeToolOrderWithDelay = debounce(() => storeToolOrder(), 1000); function initApp() { return { -- GitLab From b4531d6e09aae863587768611cabae3636070926 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 14:15:11 +0200 Subject: [PATCH 23/31] Update a varialbe name --- lib/ToolboxTool.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ToolboxTool.php b/lib/ToolboxTool.php index 954d893..d6a0b01 100644 --- a/lib/ToolboxTool.php +++ b/lib/ToolboxTool.php @@ -20,10 +20,10 @@ class ToolboxTool extends SimpleORMap * @return ToolboxTool[] */ public static function getToolbox($onlyFavorite = false) { - $withFavorites = $onlyFavorite ? '`toolbox_user_tools`.`favorite` = 1 AND' : null; + $whereFavorite = $onlyFavorite ? '`toolbox_user_tools`.`favorite` = 1 AND' : null; return self::findBySQL("LEFT JOIN `toolbox_user_tools` ON (`toolbox_user_tools`.`tool_id` = `toolbox_tools`.`tool_id` AND `toolbox_user_tools`.`user_id` = :user_id) - WHERE $withFavorites `toolbox_tools`.`active` = 1 + WHERE $whereFavorite `toolbox_tools`.`active` = 1 ORDER BY IFNULL(`toolbox_user_tools`.`priority`, 0) ASC, `toolbox_tools`.`name` ASC", [ 'user_id' => User::findCurrent()->id -- GitLab From df0353f9e0019102d87ac419a847756f8d8b7a64 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 14:15:52 +0200 Subject: [PATCH 24/31] Pass the right param --- Toolbox.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Toolbox.class.php b/Toolbox.class.php index 4b83393..2c90987 100644 --- a/Toolbox.class.php +++ b/Toolbox.class.php @@ -89,7 +89,7 @@ class Toolbox extends StudIPPlugin implements SystemPlugin, PortalPlugin function getPortalTemplate() { - $tools = ToolboxTool::getToolbox([1]); + $tools = ToolboxTool::getToolbox(true); $tf = new Flexi_TemplateFactory(__DIR__ . '/views'); $template = $tf->open('toolbox/index.php'); -- GitLab From ff0b47f3d1dd0f2d8392a358a36e218008c1b887 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 14:30:58 +0200 Subject: [PATCH 25/31] Remove a dumyline code --- controllers/toolbox.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 0af2602..835b3cb 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -16,8 +16,6 @@ class ToolboxController extends PluginController PageLayout::postInfo(nl2br(htmlReady(Config::get()->TOOLBOX_INFO))); } - MessageBox::info('sd'); - $this->isHomePageActive = false; $this->tools = ToolboxTool::getToolbox(); } -- GitLab From de2b91a4f8e4a1192b74a32f1af2f66a610d1995 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 16:04:22 +0200 Subject: [PATCH 26/31] Toggle toolbox favorite per AJAX --- assets/toolbox.scss | 13 +++++++++++-- controllers/toolbox.php | 5 ++++- views/toolbox/index.php | 32 +++++++++++++++++++++++++++----- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/assets/toolbox.scss b/assets/toolbox.scss index b562ae5..293a747 100644 --- a/assets/toolbox.scss +++ b/assets/toolbox.scss @@ -64,10 +64,19 @@ } .favorite-icon-container { - &.opacity-50 { + &.not-favorite { opacity: 0.5; + .favorite-icon { + display: none; + } } - &.opacity-50:hover { + &.not-favorite:hover { opacity: 0.8; } + + &.favorite { + .un-favorite-icon { + display: none; + } + } } diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 835b3cb..3b25007 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -79,6 +79,9 @@ class ToolboxController extends PluginController $toolboxUserTool->store(); - $this->relocate('toolbox/index'); + $this->render_json([ + 'success' => true, + 'favorite' => $toolboxUserTool->favorite + ]); } } diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 9c562b6..decda49 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -50,12 +50,15 @@ <div> <a - title="<?= _('Mark/Unmark it as favorite') ?>" - aria-label="<?= _('TODO: need to write something here.')?>" - class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? '' : 'opacity-50' ?>" - href="<?= PluginEngine::getLink($plugin, [], 'toolbox/toggle_favorite/'.$tool->id) ?>" + id="<?= $tool->id ?>" + title="<?= _('Markieren Sie dieses Tool mit einem Stern, um es im Startseitenwidget anzuzeigen.') ?>" + role="checkbox" + aria-checked="true/false" + class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? 'favorite' : 'not-favorite' ?>" + @click="toggleFavorite('<?= $tool->id ?>')" > - <?= Icon::create($tool->toolbox_user_tools?->favorite ? 'star' : 'star-empty')->asImg(20) ?> + <?= Icon::create('star', Icon::DEFAULT_ROLE, ['class' => 'favorite-icon'] )->asImg(20) ?> + <?= Icon::create('star-empty', Icon::DEFAULT_ROLE, ['class' => 'un-favorite-icon'] )->asImg(20) ?> </a> </div> <? else: ?> @@ -136,6 +139,25 @@ toolIds[index] = temp; return toolIds; + }, + + async toggleFavorite(toolId) { + const response = await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/toggle_favorite/${toolId}`), { + method: 'POST' + }); + + const data = await response.json(); + + if(data.success) { + if (data.favorite) { + document.getElementById(toolId).classList.remove('not-favorite') + document.getElementById(toolId).classList.add('favorite') + } else { + document.getElementById(toolId).classList.remove('favorite') + document.getElementById(toolId).classList.add('not-favorite') + } + } + } }; } -- GitLab From 2a877f24ba1f03cdfa829985612ffe094fb5b7fe Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 16:51:07 +0200 Subject: [PATCH 27/31] Pack scripts to a JS file --- assets/js/toolbox-index.js | 71 +++++++++++++ views/toolbox/index.php | 199 +++++++++++-------------------------- 2 files changed, 128 insertions(+), 142 deletions(-) create mode 100644 assets/js/toolbox-index.js diff --git a/assets/js/toolbox-index.js b/assets/js/toolbox-index.js new file mode 100644 index 0000000..25b5434 --- /dev/null +++ b/assets/js/toolbox-index.js @@ -0,0 +1,71 @@ +STUDIP.Toolbox = { + sortable: Sortable.create(userTools, { + group: 'toolbox', + animation: 150, + ghostClass: 'dropable-placeholder', + dataIdAttr: 'toolId', + handle: ".dragarea", + + onSort: (event) => { + STUDIP.Toolbox.storeToolOrder(); + }, + }), + storeToolOrder: async () => { + let formData = new FormData(); + formData.append('order', STUDIP.Toolbox.sortable.toArray()); + + await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/save_order`), { + method: 'POST', + body: formData + }); + }, + + initApp: () => { + return { + sortTool($event) { + if ($event.keyCode >= 37 && $event.keyCode <= 40) { + let toolId = $event.target.getAttribute('toolId'); + let step = 1; + + if ($event.keyCode == 37 || $event.keyCode == 38) { + step = -1; + } + + STUDIP.Toolbox.sortable.sort(this.swapTool(toolId, step), true); + STUDIP.Toolbox.storeToolOrder(); + $event.target.focus(); + } + }, + + swapTool(toolId, step) { + let toolIds = STUDIP.Toolbox.sortable.toArray(); + let index = toolIds.findIndex(tool => tool == toolId); + + let temp = toolIds[index + step]; + toolIds[index + step] = toolIds[index]; + toolIds[index] = temp; + + return toolIds; + }, + + async toggleFavorite(toolId) { + const response = await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/toggle_favorite/${toolId}`), { + method: 'POST' + }); + + const data = await response.json(); + + if(data.success) { + if (data.favorite) { + document.getElementById(toolId).classList.remove('not-favorite') + document.getElementById(toolId).classList.add('favorite') + } else { + document.getElementById(toolId).classList.remove('favorite') + document.getElementById(toolId).classList.add('not-favorite') + } + } + + } + }; + } +} diff --git a/views/toolbox/index.php b/views/toolbox/index.php index decda49..e5029ad 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -9,158 +9,73 @@ <ul id="userTools" class="content-items toolbox" - x-data="initApp()"> + x-data="STUDIP.Toolbox.initApp()"> <? foreach ($tools as $key => $tool) : ?> - <li toolId="<?= $tool->id ?>" class="content-item dragarea"> - <div class="top_icons"> - <? if ($tool['long_description']) : ?> - <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" - data-dialog - title="<?= _('Detailierte Informationen zu dem Werkzeug') ?>"> - <?= Icon::create('info-circle')->asImg(20) ?> - </a> - <? endif ?> - </div> - <a href="<?= htmlReady($tool['url']) ?>" target="_blank" class="content-item-link <?= $tool['icon_type'] == 'no-icon' ? '--no-icon' : '' ?>"> - <? if ($tool['icon_type'] != 'no-icon') : ?> - <div class="content-item-img-wrapper"> - <?= $plugin->echoIcon($tool, $tool['icon_type'], 64) ?> - </div> - <? endif ?> - <div class="content-item-text"> - <p class="content-item-title"> - <?= htmlReady($tool['name']) ?> - </p> - <p class="content-item-description"> - <?= htmlReady(mila($tool['description'], 70)) ?> - </p> + <li toolId="<?= $tool->id ?>" class="content-item dragarea"> + <div class="top_icons"> + <? if ($tool['long_description']) : ?> + <a href="<?= PluginEngine::getLink($plugin, [], 'toolbox/info/'.$tool->id) ?>" + data-dialog + title="<?= _('Detailierte Informationen zu dem Werkzeug') ?>"> + <?= Icon::create('info-circle')->asImg(20) ?> + </a> + <? endif ?> </div> - <div class="info"><?= htmlReady($tool['info']) ?></div> - </a> - <div class="bottom_icons"> - <? if (! $isHomePageActive) : ?> - <a - class="dragarea" - tabindex="0" - toolId="<?= $tool->id ?>" - aria-label="<?= _('Sortierelement für Werkzeug Toolbox. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.')?>" - @keydown="sortTool"> - <span class="drag-handle"></span> + <a href="<?= htmlReady($tool['url']) ?>" target="_blank" class="content-item-link <?= $tool['icon_type'] == 'no-icon' ? '--no-icon' : '' ?>"> + <? if ($tool['icon_type'] != 'no-icon') : ?> + <div class="content-item-img-wrapper"> + <?= $plugin->echoIcon($tool, $tool['icon_type'], 64) ?> + </div> + <? endif ?> + <div class="content-item-text"> + <p class="content-item-title"> + <?= htmlReady($tool['name']) ?> + </p> + <p class="content-item-description"> + <?= htmlReady(mila($tool['description'], 70)) ?> + </p> + </div> + <div class="info"><?= htmlReady($tool['info']) ?></div> </a> - - <div> - <a - id="<?= $tool->id ?>" - title="<?= _('Markieren Sie dieses Tool mit einem Stern, um es im Startseitenwidget anzuzeigen.') ?>" - role="checkbox" - aria-checked="true/false" - class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? 'favorite' : 'not-favorite' ?>" - @click="toggleFavorite('<?= $tool->id ?>')" - > - <?= Icon::create('star', Icon::DEFAULT_ROLE, ['class' => 'favorite-icon'] )->asImg(20) ?> - <?= Icon::create('star-empty', Icon::DEFAULT_ROLE, ['class' => 'un-favorite-icon'] )->asImg(20) ?> - </a> - </div> - <? else: ?> - <div></div> - <div> + <div class="bottom_icons"> + <? if (! $isHomePageActive) : ?> + <a + class="dragarea" + tabindex="0" + toolId="<?= $tool->id ?>" + aria-label="<?= _('Sortierelement für Werkzeug Toolbox. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.')?>" + @keydown="sortTool"> + <span class="drag-handle"></span> + </a> + + <div> + <a + id="<?= $tool->id ?>" + title="<?= _('Markieren Sie dieses Tool mit einem Stern, um es im Startseitenwidget anzuzeigen.') ?>" + role="checkbox" + aria-checked="true/false" + class="favorite-icon-container <?= $tool->toolbox_user_tools?->favorite ? 'favorite' : 'not-favorite' ?>" + @click="toggleFavorite('<?= $tool->id ?>')" + > + <?= Icon::create('star', Icon::DEFAULT_ROLE, ['class' => 'favorite-icon'] )->asImg(20) ?> + <?= Icon::create('star-empty', Icon::DEFAULT_ROLE, ['class' => 'un-favorite-icon'] )->asImg(20) ?> + </a> + </div> + <? else: ?> + <div></div> + <div> <span class="favorite-icon-container"> <?= Icon::create('star')->asImg(20) ?> </span> - </div> - <? endif ?> - </div> - </li> + </div> + <? endif ?> + </div> + </li> <? endforeach ?> </ul> </div> <script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> - -<script> - var sortable = Sortable.create(userTools, { - group: 'toolbox', - animation: 150, - ghostClass: 'dropable-placeholder', - dataIdAttr: 'toolId', - handle: ".dragarea", - - onSort: (event) => { - storeToolOrder(); - }, - }); - - const storeToolOrder = async () => { - let formData = new FormData(); - formData.append('order', sortable.toArray()); - - await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/save_order`), { - method: 'POST', - body: formData - }); - }; - - function debounce(callback, timeout = 1000){ - let timer; - return () => { - clearTimeout(timer); - timer = setTimeout(() => { - callback(); - }, timeout); - }; - } - - const storeToolOrderWithDelay = debounce(() => storeToolOrder(), 1000); - - function initApp() { - return { - sortTool($event) { - if ($event.keyCode >= 37 && $event.keyCode <= 40) { - let toolId = $event.target.getAttribute('toolId'); - let step = 1; - - if ($event.keyCode == 37 || $event.keyCode == 38) { - step = -1; - } - - sortable.sort(this.swapTool(toolId, step), true); - storeToolOrderWithDelay(); - $event.target.focus(); - } - }, - - swapTool(toolId, step) { - let toolIds = sortable.toArray(); - let index = toolIds.findIndex(tool => tool == toolId); - - let temp = toolIds[index + step]; - toolIds[index + step] = toolIds[index]; - toolIds[index] = temp; - - return toolIds; - }, - - async toggleFavorite(toolId) { - const response = await fetch(STUDIP.URLHelper.getURL(`plugins.php/toolbox/toolbox/toggle_favorite/${toolId}`), { - method: 'POST' - }); - - const data = await response.json(); - - if(data.success) { - if (data.favorite) { - document.getElementById(toolId).classList.remove('not-favorite') - document.getElementById(toolId).classList.add('favorite') - } else { - document.getElementById(toolId).classList.remove('favorite') - document.getElementById(toolId).classList.add('not-favorite') - } - } - - } - }; - } -</script> - +<script src="<?= $plugin->getPluginURL().'/assets/js/toolbox-index.js'?>"></script> -- GitLab From 9916b7dc68b4a95dd5861aee4e2bb3b772093f2f Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Thu, 4 Jul 2024 17:02:08 +0200 Subject: [PATCH 28/31] Pack JS script inside STUDIP.Toolbox object --- assets/js/toolbox-index.js | 3 ++- views/admin/edit_tool.php | 12 +++++++----- views/toolbox/index.php | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/assets/js/toolbox-index.js b/assets/js/toolbox-index.js index 25b5434..4a8a69c 100644 --- a/assets/js/toolbox-index.js +++ b/assets/js/toolbox-index.js @@ -10,6 +10,7 @@ STUDIP.Toolbox = { STUDIP.Toolbox.storeToolOrder(); }, }), + storeToolOrder: async () => { let formData = new FormData(); formData.append('order', STUDIP.Toolbox.sortable.toArray()); @@ -20,7 +21,7 @@ STUDIP.Toolbox = { }); }, - initApp: () => { + initToolboxApp: () => { return { sortTool($event) { if ($event.keyCode >= 37 && $event.keyCode <= 40) { diff --git a/views/admin/edit_tool.php b/views/admin/edit_tool.php index 6b1304a..f676919 100644 --- a/views/admin/edit_tool.php +++ b/views/admin/edit_tool.php @@ -1,7 +1,7 @@ <form class="default" enctype="multipart/form-data" action="<?= PluginEngine::getLink($plugin, [], 'admin/edit_tool/'.$tool->id)?>" - x-data="initApp()" + x-data="STUDIP.Toolbox.initToolboxAdminApp()" method="post"> <?= CSRFProtection::tokenTag() ?> <label> @@ -112,9 +112,11 @@ <script> var iconType = <?= $tool->isNew() ? json_encode('studip') : json_encode($tool['icon_type']) ?>; - function initApp() { - return { - selectedIconType: iconType - }; + STUDIP.Toolbox = { + initToolboxAdminApp: () => { + return { + selectedIconType: iconType + }; + } } </script> diff --git a/views/toolbox/index.php b/views/toolbox/index.php index e5029ad..260e619 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -9,7 +9,7 @@ <ul id="userTools" class="content-items toolbox" - x-data="STUDIP.Toolbox.initApp()"> + x-data="STUDIP.Toolbox.initToolboxApp()"> <? foreach ($tools as $key => $tool) : ?> <li toolId="<?= $tool->id ?>" class="content-item dragarea"> <div class="top_icons"> -- GitLab From 5ddf0baa411745bcadce60b1089e6e21e4223944 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Fri, 5 Jul 2024 09:19:35 +0200 Subject: [PATCH 29/31] Add debounce to Function prototype --- assets/js/toolbox-index.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/assets/js/toolbox-index.js b/assets/js/toolbox-index.js index 4a8a69c..1554265 100644 --- a/assets/js/toolbox-index.js +++ b/assets/js/toolbox-index.js @@ -1,3 +1,14 @@ +Function.prototype.debounce = function(debounceTime) { + let timeoutIdentifier; + const self = this; + return function(...args) { + clearTimeout(timeoutIdentifier); + timeoutIdentifier = setTimeout(() => { + self.apply(this, args) + }, debounceTime) + } +} + STUDIP.Toolbox = { sortable: Sortable.create(userTools, { group: 'toolbox', @@ -11,6 +22,10 @@ STUDIP.Toolbox = { }, }), + storeToolOrderWithDelay: (() => { + STUDIP.Toolbox.storeToolOrder(); + }).debounce(1000), + storeToolOrder: async () => { let formData = new FormData(); formData.append('order', STUDIP.Toolbox.sortable.toArray()); @@ -33,14 +48,14 @@ STUDIP.Toolbox = { } STUDIP.Toolbox.sortable.sort(this.swapTool(toolId, step), true); - STUDIP.Toolbox.storeToolOrder(); + STUDIP.Toolbox.storeToolOrderWithDelay(); $event.target.focus(); } }, swapTool(toolId, step) { let toolIds = STUDIP.Toolbox.sortable.toArray(); - let index = toolIds.findIndex(tool => tool == toolId); + let index = toolIds.findIndex(id => id == toolId); let temp = toolIds[index + step]; toolIds[index + step] = toolIds[index]; -- GitLab From 52c67d90fd1db82ec111824e09d2bd56e6806824 Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Fri, 5 Jul 2024 09:22:43 +0200 Subject: [PATCH 30/31] Remove unused params --- assets/js/toolbox-index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/js/toolbox-index.js b/assets/js/toolbox-index.js index 1554265..32245bb 100644 --- a/assets/js/toolbox-index.js +++ b/assets/js/toolbox-index.js @@ -4,8 +4,8 @@ Function.prototype.debounce = function(debounceTime) { return function(...args) { clearTimeout(timeoutIdentifier); timeoutIdentifier = setTimeout(() => { - self.apply(this, args) - }, debounceTime) + self.apply(this, args); + }, debounceTime); } } @@ -17,7 +17,7 @@ STUDIP.Toolbox = { dataIdAttr: 'toolId', handle: ".dragarea", - onSort: (event) => { + onSort: () => { STUDIP.Toolbox.storeToolOrder(); }, }), @@ -73,11 +73,11 @@ STUDIP.Toolbox = { if(data.success) { if (data.favorite) { - document.getElementById(toolId).classList.remove('not-favorite') - document.getElementById(toolId).classList.add('favorite') + document.getElementById(toolId).classList.remove('not-favorite'); + document.getElementById(toolId).classList.add('favorite'); } else { - document.getElementById(toolId).classList.remove('favorite') - document.getElementById(toolId).classList.add('not-favorite') + document.getElementById(toolId).classList.remove('favorite'); + document.getElementById(toolId).classList.add('not-favorite'); } } -- GitLab From 11723b7d3f42c9768d6ca6f269363a36c6b193ee Mon Sep 17 00:00:00 2001 From: Murtaza Sultani <sultani@data-quest.de> Date: Fri, 5 Jul 2024 10:05:55 +0200 Subject: [PATCH 31/31] Load JS scripts from controller --- assets/js/toolbox-index.js | 2 +- controllers/toolbox.php | 2 ++ views/toolbox/index.php | 9 +++------ 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/assets/js/toolbox-index.js b/assets/js/toolbox-index.js index 32245bb..1221422 100644 --- a/assets/js/toolbox-index.js +++ b/assets/js/toolbox-index.js @@ -10,7 +10,7 @@ Function.prototype.debounce = function(debounceTime) { } STUDIP.Toolbox = { - sortable: Sortable.create(userTools, { + sortable: Sortable.create(userSortableTools, { group: 'toolbox', animation: 150, ghostClass: 'dropable-placeholder', diff --git a/controllers/toolbox.php b/controllers/toolbox.php index 3b25007..f2dfc34 100644 --- a/controllers/toolbox.php +++ b/controllers/toolbox.php @@ -4,6 +4,8 @@ class ToolboxController extends PluginController { public function before_filter(&$action, &$args) { + PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/toolbox-index.js', ['defer' => true]); + PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/alpine.min.js', ['defer' => true]); PageLayout::addScript($this->plugin->getPluginURL().'/assets/js/sortable.min.js'); parent::before_filter($action, $args); diff --git a/views/toolbox/index.php b/views/toolbox/index.php index 260e619..257a874 100644 --- a/views/toolbox/index.php +++ b/views/toolbox/index.php @@ -7,9 +7,10 @@ <?php endif ?> <?php endif ?> <ul - id="userTools" + id="<?= $isHomePageActive ? 'userTools' : 'userSortableTools' ?>" class="content-items toolbox" - x-data="STUDIP.Toolbox.initToolboxApp()"> + x-data="STUDIP.Toolbox.initToolboxApp()" + > <? foreach ($tools as $key => $tool) : ?> <li toolId="<?= $tool->id ?>" class="content-item dragarea"> <div class="top_icons"> @@ -73,9 +74,5 @@ </li> <? endforeach ?> </ul> - </div> -<script src="<?= $plugin->getPluginURL().'/assets/js/alpine.min.js'?>" defer></script> -<script src="<?= $plugin->getPluginURL().'/assets/js/toolbox-index.js'?>"></script> - -- GitLab