mirror of
https://github.com/openwrt/luci.git
synced 2025-12-21 23:34:34 +04:00
luci-base: make items of UIDynamicList drag-sortable
Signed-off-by: Ramon Van Gorkom <Ramon00c00@gmail.com>
This commit is contained in:
committed by
Paul Donald
parent
fefb9acf57
commit
80f18df48f
@@ -2752,7 +2752,6 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
|
|||||||
handleDragStart: function(ev) {
|
handleDragStart: function(ev) {
|
||||||
if (!scope.dragState || !scope.dragState.node.classList.contains('drag-handle')) {
|
if (!scope.dragState || !scope.dragState.node.classList.contains('drag-handle')) {
|
||||||
scope.dragState = null;
|
scope.dragState = null;
|
||||||
ev.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2763,6 +2762,7 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
|
|||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
handleDragOver: function(ev) {
|
handleDragOver: function(ev) {
|
||||||
|
if (scope.dragState === null ) return;
|
||||||
var n = scope.dragState.targetNode,
|
var n = scope.dragState.targetNode,
|
||||||
r = scope.dragState.rect,
|
r = scope.dragState.rect,
|
||||||
t = r.top + r.height / 2;
|
t = r.top + r.height / 2;
|
||||||
@@ -2783,6 +2783,7 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
|
|||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
handleDragEnter: function(ev) {
|
handleDragEnter: function(ev) {
|
||||||
|
if (scope.dragState === null ) return;
|
||||||
scope.dragState.rect = ev.currentTarget.getBoundingClientRect();
|
scope.dragState.rect = ev.currentTarget.getBoundingClientRect();
|
||||||
scope.dragState.targetNode = ev.currentTarget;
|
scope.dragState.targetNode = ev.currentTarget;
|
||||||
},
|
},
|
||||||
@@ -2808,6 +2809,7 @@ var CBITableSection = CBITypedSection.extend(/** @lends LuCI.form.TableSection.p
|
|||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
handleDrop: function(ev) {
|
handleDrop: function(ev) {
|
||||||
|
if (scope.dragState === null ) return;
|
||||||
var s = scope.dragState;
|
var s = scope.dragState;
|
||||||
|
|
||||||
if (s.node && s.targetNode) {
|
if (s.node && s.targetNode) {
|
||||||
|
|||||||
@@ -2265,9 +2265,98 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */
|
|||||||
this.addItem(dl, this.values[i], label);
|
this.addItem(dl, this.values[i], label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.initDragAndDrop(dl);
|
||||||
|
|
||||||
return this.bind(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 */
|
/** @private */
|
||||||
bind: function(dl) {
|
bind: function(dl) {
|
||||||
dl.addEventListener('click', L.bind(this.handleClick, this));
|
dl.addEventListener('click', L.bind(this.handleClick, this));
|
||||||
@@ -2287,7 +2376,7 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */
|
|||||||
/** @private */
|
/** @private */
|
||||||
addItem: function(dl, value, text, flash) {
|
addItem: function(dl, value, text, flash) {
|
||||||
var exists = false,
|
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('span', {}, [ text || value ]),
|
||||||
E('input', {
|
E('input', {
|
||||||
'type': 'hidden',
|
'type': 'hidden',
|
||||||
@@ -2359,8 +2448,18 @@ var UIDynamicList = UIElement.extend(/** @lends LuCI.ui.DynamicList.prototype */
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (item) {
|
if (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);
|
this.removeItem(dl, item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if (matchesElem(ev.target, '.cbi-button-add')) {
|
else if (matchesElem(ev.target, '.cbi-button-add')) {
|
||||||
var input = ev.target.previousElementSibling;
|
var input = ev.target.previousElementSibling;
|
||||||
if (input.value.length && !input.classList.contains('cbi-input-invalid')) {
|
if (input.value.length && !input.classList.contains('cbi-input-invalid')) {
|
||||||
|
|||||||
@@ -624,9 +624,11 @@ select,
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: var(--text-color-high);
|
color: var(--text-color-high);
|
||||||
position: relative;
|
position: relative;
|
||||||
pointer-events: none;
|
pointer-events: auto; /* needed for drag-and-drop in UIDynamicList */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
cursor: move; /* drag-and-drop */
|
||||||
|
user-select: text; /* text selection in drag-and-drop */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .item::after {
|
.cbi-dynlist > .item::after {
|
||||||
@@ -645,10 +647,35 @@ select,
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .item.drag-over {
|
||||||
|
border-top: 1px solid var(--text-color-highest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make item being dragged in UIDynamicList partially transparent*/
|
||||||
|
.cbi-dynlist > .item.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prevent pointer changing when over the span element in UIDynamicList */
|
||||||
|
.cbi-dynlist > .item > span {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item {
|
.cbi-dynlist > .add-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-input-text.drag-over {
|
||||||
|
border-top: 1px solid var(--text-color-highest);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-button-add.drag-over {
|
||||||
|
border-top: 1px solid var(--text-color-highest);
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item > input,
|
.cbi-dynlist > .add-item > input,
|
||||||
.cbi-dynlist > .add-item > button {
|
.cbi-dynlist > .add-item > button {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
|||||||
@@ -1378,10 +1378,12 @@ body:not(.Interfaces) .cbi-rowstyle-2:first-child {
|
|||||||
max-width: 25rem;
|
max-width: 25rem;
|
||||||
margin-right: 2em;
|
margin-right: 2em;
|
||||||
padding: .5em .25em .25em 0;
|
padding: .5em .25em .25em 0;
|
||||||
pointer-events: none;
|
pointer-events: auto; /* needed for drag-and-drop in UIDynamicList */
|
||||||
color: #666;
|
color: #666;
|
||||||
border-bottom: 2px solid rgba(0, 0, 0, .26);
|
border-bottom: 2px solid rgba(0, 0, 0, .26);
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
cursor: move; /* drag-and-drop */
|
||||||
|
user-select: text; /* text selection in drag-and-drop */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-dynlist[name="sshkeys"] > .item {
|
.cbi-dynlist[name="sshkeys"] > .item {
|
||||||
@@ -1403,9 +1405,21 @@ body:not(.Interfaces) .cbi-rowstyle-2:first-child {
|
|||||||
background-color: var(--red-color-high);
|
background-color: var(--red-color-high);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .item.drag-over {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make item being dragged in UIDynamicList partially transparent*/
|
||||||
|
.cbi-dynlist > .item.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prevent pointer changing when over the span element in UIDynamicList */
|
||||||
.cbi-dynlist > .item > span {
|
.cbi-dynlist > .item > span {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item {
|
.cbi-dynlist > .add-item {
|
||||||
@@ -1415,6 +1429,16 @@ body:not(.Interfaces) .cbi-rowstyle-2:first-child {
|
|||||||
min-width: 16rem;
|
min-width: 16rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-input-text.drag-over {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-button-add.drag-over {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item:not([ondrop]) > input {
|
.cbi-dynlist > .add-item:not([ondrop]) > input {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1173,9 +1173,11 @@ textarea {
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: box-shadow .25s ease-in-out;
|
transition: box-shadow .25s ease-in-out;
|
||||||
pointer-events: none;
|
pointer-events: auto; /* needed for drag-and-drop in UIDynamicList */
|
||||||
flex: 1 1 100%;
|
flex: 1 1 100%;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
cursor: move; /* drag-and-drop */
|
||||||
|
user-select: text; /* text selection in drag-and-drop */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .item::after {
|
.cbi-dynlist > .item::after {
|
||||||
@@ -1196,6 +1198,20 @@ textarea {
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .item.drag-over {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make item being dragged in UIDynamicList partially transparent*/
|
||||||
|
.cbi-dynlist > .item.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
/* prevent pointer changing when over the span element in UIDynamicList */
|
||||||
|
.cbi-dynlist > .item > span {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist[disabled] > .item::after {
|
.cbi-dynlist[disabled] > .item::after {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -1209,6 +1225,16 @@ textarea {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-input-text.drag-over {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-button-add.drag-over {
|
||||||
|
border-top: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item > input {
|
.cbi-dynlist > .add-item > input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 18.5rem;
|
min-width: 18.5rem;
|
||||||
|
|||||||
@@ -1340,10 +1340,12 @@ ul.cbi-tabmenu li.cbi-tab-disabled[data-errors]::after {
|
|||||||
border: 1px outset #000;
|
border: 1px outset #000;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
position: relative;
|
position: relative;
|
||||||
pointer-events: none;
|
pointer-events: auto; /* needed for drag-and-drop in UIDynamicList */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
cursor: move; /* drag-and-drop */
|
||||||
|
user-select: text; /* text selection in drag-and-drop */
|
||||||
}
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .item::after {
|
.cbi-dynlist > .item::after {
|
||||||
@@ -1364,10 +1366,35 @@ ul.cbi-tabmenu li.cbi-tab-disabled[data-errors]::after {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .item.drag-over {
|
||||||
|
border-top: 1px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make item being dragged in UIDynamicList partially transparent*/
|
||||||
|
.cbi-dynlist > .item.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prevent pointer changing when over the span element in UIDynamicList */
|
||||||
|
.cbi-dynlist > .item > span {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item {
|
.cbi-dynlist > .add-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-input-text.drag-over {
|
||||||
|
border-top: 1px solid re;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* indication line for drag-and-drop in UIDynamicList*/
|
||||||
|
.cbi-dynlist > .add-item > .cbi-button-add.drag-over {
|
||||||
|
border-top: 1px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
.cbi-dynlist > .add-item > input {
|
.cbi-dynlist > .add-item > input {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user