summaryrefslogtreecommitdiffhomepage
path: root/modules/luci-base/htdocs/luci-static
diff options
context:
space:
mode:
authorRamon Van Gorkom <Ramon00c00@gmail.com>2024-11-21 21:34:14 +0100
committerPaul Donald <newtwen+github@gmail.com>2024-11-27 21:43:16 +0100
commit80f18df48fcc39d22dfabb133e9bc823d7aeff86 (patch)
treead2c055a0340c4b35aa61bf0b4261b86f315f0e6 /modules/luci-base/htdocs/luci-static
parentfefb9acf57ad11012c336e4d056b074620b87d2f (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.js4
-rw-r--r--modules/luci-base/htdocs/luci-static/resources/ui.js103
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;