Add drag-and-drop reordering of sibling elements in the tree panel
Elements can now be reordered within the same container by dragging a row above or below another in the left-hand element tree.
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
{% macro render_tree_node(el) %}
|
||||
{% set kids = children_of.get(el.id, []) %}
|
||||
<div class="treeNode" data-element-id="{{ el.id }}">
|
||||
<div class="treeRow">
|
||||
<div class="treeRow" draggable="true" data-parent-id="{{ el.parent_id or '' }}">
|
||||
{% if kids %}
|
||||
<button type="button" class="treeToggle" data-node-id="{{ el.id }}" aria-label="Réduire / déplier">▾</button>
|
||||
{% else %}
|
||||
@@ -738,6 +738,101 @@ if (!window.__forgeTreeToggleBound) {
|
||||
|
||||
restoreTreeCollapsedState();
|
||||
|
||||
// ---------- Arborescence : réordonner par glisser-déposer ----------
|
||||
//
|
||||
// Glisser une ligne au-dessus/en dessous d'une autre ligne DU MÊME
|
||||
// CONTENEUR (même parent, ou toutes deux à la racine) change leur ordre —
|
||||
// voir screens/elements/reorder_element.py. Écouteurs posés une seule fois
|
||||
// sur document (délégation) : ils survivent au remplacement de #builder3
|
||||
// par refreshBuilderPanel/refreshCanvasOnly, pas besoin de les rebrancher.
|
||||
var treeDragElementId = null;
|
||||
var treeDragParentId = null;
|
||||
|
||||
function treeRowDropInfo(row) {
|
||||
var node = row.closest('.treeNode');
|
||||
return { elementId: node ? node.dataset.elementId : null, parentId: row.dataset.parentId || '' };
|
||||
}
|
||||
|
||||
function clearTreeDropIndicators() {
|
||||
document.querySelectorAll('.treeRow.dropBefore, .treeRow.dropAfter').forEach(function (r) {
|
||||
r.classList.remove('dropBefore', 'dropAfter');
|
||||
});
|
||||
}
|
||||
|
||||
if (!window.__forgeTreeDragBound) {
|
||||
window.__forgeTreeDragBound = true;
|
||||
|
||||
document.addEventListener('dragstart', function (e) {
|
||||
var row = e.target.closest('.elementTree .treeRow');
|
||||
if (!row) return;
|
||||
var info = treeRowDropInfo(row);
|
||||
treeDragElementId = info.elementId;
|
||||
treeDragParentId = info.parentId;
|
||||
row.classList.add('dragging');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
try { e.dataTransfer.setData('text/plain', String(info.elementId)); } catch (err) {}
|
||||
});
|
||||
|
||||
document.addEventListener('dragend', function (e) {
|
||||
var row = e.target.closest('.elementTree .treeRow');
|
||||
if (row) row.classList.remove('dragging');
|
||||
clearTreeDropIndicators();
|
||||
treeDragElementId = null;
|
||||
treeDragParentId = null;
|
||||
});
|
||||
|
||||
document.addEventListener('dragover', function (e) {
|
||||
var row = e.target.closest('.elementTree .treeRow');
|
||||
if (!row || treeDragElementId == null) return;
|
||||
var info = treeRowDropInfo(row);
|
||||
if (info.elementId == null || String(info.elementId) === String(treeDragElementId)) return;
|
||||
if (info.parentId !== treeDragParentId) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
clearTreeDropIndicators();
|
||||
var rect = row.getBoundingClientRect();
|
||||
var before = (e.clientY - rect.top) < rect.height / 2;
|
||||
row.classList.add(before ? 'dropBefore' : 'dropAfter');
|
||||
});
|
||||
|
||||
document.addEventListener('drop', function (e) {
|
||||
var row = e.target.closest('.elementTree .treeRow');
|
||||
clearTreeDropIndicators();
|
||||
if (!row || treeDragElementId == null) return;
|
||||
e.preventDefault();
|
||||
var info = treeRowDropInfo(row);
|
||||
if (info.elementId == null || String(info.elementId) === String(treeDragElementId)) return;
|
||||
if (info.parentId !== treeDragParentId) return;
|
||||
var rect = row.getBoundingClientRect();
|
||||
var position = (e.clientY - rect.top) < rect.height / 2 ? 'before' : 'after';
|
||||
var draggedId = treeDragElementId;
|
||||
fetch('/game/{{ game.slug }}/elements/' + draggedId + '/reorder', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ target_id: info.elementId, position: position })
|
||||
}).then(refreshAfterReorder);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAfterReorder() {
|
||||
var url = SCREEN_EDIT_URL + (CURRENT_SELECTED_ID ? ('?selected=' + CURRENT_SELECTED_ID) : '');
|
||||
return fetch(url, { headers: { 'X-Requested-With': 'pjax' } })
|
||||
.then(function (r) { return r.text(); })
|
||||
.then(function (html) {
|
||||
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||||
var newCanvas = doc.getElementById('canvas');
|
||||
var curCanvas = document.getElementById('canvas');
|
||||
if (newCanvas && curCanvas) {
|
||||
curCanvas.innerHTML = newCanvas.innerHTML;
|
||||
applySelectionHighlight(CURRENT_SELECTED_ID);
|
||||
}
|
||||
var newTree = doc.querySelector('.builderPanel .elementTree');
|
||||
var curTree = document.querySelector('.builderPanel .elementTree');
|
||||
if (newTree && curTree) { curTree.innerHTML = newTree.innerHTML; }
|
||||
restoreTreeCollapsedState();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Navigation dans l'éditeur sans AUCUN rechargement de page ----------
|
||||
//
|
||||
// Se déplacer dans le canevas (glisser, redimensionner, cliquer un autre
|
||||
|
||||
Reference in New Issue
Block a user