diff options
author | Ramon Van Gorkom <Ramon00c00@gmail.com> | 2024-11-21 21:34:14 +0100 |
---|---|---|
committer | Paul Donald <newtwen+github@gmail.com> | 2024-11-27 21:43:16 +0100 |
commit | 80f18df48fcc39d22dfabb133e9bc823d7aeff86 (patch) | |
tree | ad2c055a0340c4b35aa61bf0b4261b86f315f0e6 /modules/luci-base/htdocs/luci-static | |
parent | fefb9acf57ad11012c336e4d056b074620b87d2f (diff) |
luci-base: make items of UIDynamicList drag-sortable
Signed-off-by: Ramon Van Gorkom <Ramon00c00@gmail.com>
Diffstat (limited to 'modules/luci-base/htdocs/luci-static')
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/form.js | 4 | ||||
-rw-r--r-- | modules/luci-base/htdocs/luci-static/resources/ui.js | 103 |
2 files changed, 104 insertions, 3 deletions
diff --git a/modules/luci-base/htdocs/luci-static/resources/form.js b/modules/luci-base/htdocs/luci-static/resources/form.js index 7cf2ae7597..94b45d7f76 100644 --- a/modules/luci-base/htdocs/luci-static/resources/form.js +++ b/modules/luci-base/htdocs/luci-static/resources/form.js @@ -2752,7 +2752,6 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p handleDragStart: function(ev) { if (!scope.dragState || !scope.dragState.node.classList.contains('drag-handle')) { scope.dragState = null; - ev.preventDefault(); return false; } @@ -2763,6 +2762,7 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p /** @private */ handleDragOver: function(ev) { + if (scope.dragState === null ) return; var n = scope.dragState.targetNode, r = scope.dragState.rect, t = r.top + r.height / 2; @@ -2783,6 +2783,7 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p /** @private */ handleDragEnter: function(ev) { + if (scope.dragState === null ) return; scope.dragState.rect = ev.currentTarget.getBoundingClientRect(); scope.dragState.targetNode = ev.currentTarget; }, @@ -2808,6 +2809,7 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p /** @private */ handleDrop: function(ev) { + if (scope.dragState === null ) return; var s = scope.dragState; if (s.node && s.targetNode) { diff --git a/modules/luci-base/htdocs/luci-static/resources/ui.js b/modules/luci-base/htdocs/luci-static/resources/ui.js index 8b4b1856d1..00643518dd 100644 --- a/modules/luci-base/htdocs/luci-static/resources/ui.js +++ b/modules/luci-base/htdocs/luci-static/resources/ui.js @@ -2265,10 +2265,99 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ this.addItem(dl, this.values[i], label); } + this.initDragAndDrop(dl); + return this.bind(dl); }, /** @private */ + initDragAndDrop: function(dl) { + let draggedItem = null; + let placeholder = null; + + dl.addEventListener('dragstart', (e) => { + if (e.target.classList.contains('item')) { + draggedItem = e.target; + e.target.classList.add('dragging'); + } + }); + + dl.addEventListener('dragend', (e) => e.target.classList.remove('dragging')); + + dl.addEventListener('dragover', (e) => e.preventDefault()); + + dl.addEventListener('dragenter', (e) => e.target.classList.add('drag-over')); + + dl.addEventListener('dragleave', (e) => e.target.classList.remove('drag-over')); + + dl.addEventListener('drop', (e) => { + e.preventDefault(); + e.target.classList.remove('drag-over'); + const target = e.target.classList.contains('item') ? e.target : dl.querySelector('.add-item'); + dl.insertBefore(draggedItem, target); + }); + + dl.addEventListener('click', (e) => { + if (e.target.closest('.item')) { + const span = e.target.closest('.item').querySelector('SPAN'); + if (span) { + const range = document.createRange(); + range.selectNodeContents(span); + const selection = window.getSelection(); + if (selection.rangeCount === 0 || selection.toString().length === 0) { + selection.removeAllRanges(); + selection.addRange(range); + } else selection.removeAllRanges(); + } + } + }); + + dl.addEventListener('touchstart', (e) => { + const touch = e.touches[0]; + const target = e.target.closest('.item'); + if (target) { + draggedItem = target; + + placeholder = draggedItem.cloneNode(true); + placeholder.className = 'placeholder'; + placeholder.style.height = `${draggedItem.offsetHeight}px`; + draggedItem.parentNode.insertBefore(placeholder, draggedItem.nextSibling); + draggedItem.classList.add('dragging') + } + }); + + dl.addEventListener('touchmove', (e) => { + if (draggedItem) { + const touch = e.touches[0]; + const currentY = touch.clientY; + + const items = Array.from(dl.querySelectorAll('.item')); + const target = items.find(item => { + const rect = item.getBoundingClientRect(); + return currentY > rect.top && currentY < rect.bottom; + }); + + if (target && target !== draggedItem) { + const insertBefore = currentY < target.getBoundingClientRect().top + target.offsetHeight / 2; + dl.insertBefore(placeholder, insertBefore ? target : target.nextSibling); + } + + e.preventDefault(); + } + }); + + dl.addEventListener('touchend', (e) => { + if (draggedItem && placeholder) { + dl.insertBefore(draggedItem, placeholder); + draggedItem.classList.remove('dragging') + placeholder.parentNode.removeChild(placeholder); + placeholder = null; + draggedItem = null; + } + }); + }, + + /** @private */ bind: function(dl) { dl.addEventListener('click', L.bind(this.handleClick, this)); dl.addEventListener('keydown', L.bind(this.handleKeydown, this)); @@ -2287,7 +2376,7 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ /** @private */ addItem: function(dl, value, text, flash) { var exists = false, - new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex': 0 }, [ + new_item = E('div', { 'class': flash ? 'item flash' : 'item', 'tabindex': 0, 'draggable': true }, [ E('span', {}, [ text || value ]), E('input', { 'type': 'hidden', @@ -2359,7 +2448,17 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */ return; if (item) { - this.removeItem(dl, item); + // Get bounding rectangle of the item + var rect = item.getBoundingClientRect(); + + // Get computed styles for the ::after pseudo-element + var afterStyles = window.getComputedStyle(item, '::after'); + var afterWidth = parseFloat(afterStyles.width) || 0; + + // Check if the click is within the ::after region + if (rect.right - ev.clientX <= afterWidth) { + this.removeItem(dl, item); + } } else if (matchesElem(ev.target, '.cbi-button-add')) { var input = ev.target.previousElementSibling; |