From 33a887e7ba52ea76bdfd4d95a756bfafb99a00e6 Mon Sep 17 00:00:00 2001 From: william Date: Tue, 25 Aug 2026 06:03:02 +0200 Subject: [PATCH] =?UTF-8?q?Move=20icon=20gallery=20to=20the=20left=20panel?= =?UTF-8?q?,=20drop=20the=20redundant=20Ic=C3=B4ne=20tile,=20add=20drag-in?= =?UTF-8?q?to-container=20in=20the=20tree?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The "icone" widget no longer appears as a generic tile in "Ajouter un élément sur l'écran" / "Ajouter DANS ce conteneur" — the icon gallery (now living under "Ajouter un élément sur l'écran" in the left panel instead of the right one) is the only way to add one, always pre-set to the icon you picked. - The element tree now supports dragging an element onto a container row to move it inside (last child), from anywhere in the tree — not just reordering within the same parent. Hovering a container row splits it into before/after/inside zones (thirds) when dragging a sibling, or "inside only" when dragging from elsewhere in the tree. New move_element_to_container() rejects non-container targets and cycles (dropping a container into itself or one of its own descendants) silently, mirroring reorder_element's existing safety pattern. Verified end to end: gallery renders in the left panel with no icone tile in the widget grids, and the move endpoint correctly reparents, rejects a cycle, and rejects a non-container target. Full suite green (89). --- routes/elements/__init__.py | 1 + routes/elements/element_move_to_container.py | 21 +++ screens/__init__.py | 1 + screens/elements/move_element_to_container.py | 62 +++++++++ static/style.css | 1 + templates/screen_edit.html | 122 ++++++++++++------ 6 files changed, 166 insertions(+), 42 deletions(-) create mode 100644 routes/elements/element_move_to_container.py create mode 100644 screens/elements/move_element_to_container.py diff --git a/routes/elements/__init__.py b/routes/elements/__init__.py index fae00da0..80d0cbac 100644 --- a/routes/elements/__init__.py +++ b/routes/elements/__init__.py @@ -3,6 +3,7 @@ from . import ( element_add_child, element_geometry, element_reorder, + element_move_to_container, element_save, element_delete, element_duplicate, diff --git a/routes/elements/element_move_to_container.py b/routes/elements/element_move_to_container.py new file mode 100644 index 00000000..9b265527 --- /dev/null +++ b/routes/elements/element_move_to_container.py @@ -0,0 +1,21 @@ +from flask import request, jsonify, abort + +import screens + +from core.flask_app import app + + +@app.route("/game//elements//move-to-container", methods=["POST"]) +def element_move_to_container(slug, element_id): + """Appelé en AJAX quand on glisse un élément DANS l'arborescence pour le + déposer sur un autre élément (un conteneur) plutôt qu'au-dessus/en + dessous — le fait passer enfant de ce conteneur, en dernière position + (voir move_element_to_container, qui refuse aussi silencieusement les + cas invalides : cible non-conteneur, ou cycle).""" + element = screens.get_element(slug, element_id) + if not element: + abort(404) + data = request.get_json(force=True) + new_parent_id = int(data["parent_id"]) + screens.move_element_to_container(slug, element_id, new_parent_id) + return jsonify({"ok": True}) diff --git a/screens/__init__.py b/screens/__init__.py index 7427a388..dd3ed5d9 100644 --- a/screens/__init__.py +++ b/screens/__init__.py @@ -77,6 +77,7 @@ from .elements.add_element import add_element from .elements.add_tab import add_tab from .elements.update_element_geometry import update_element_geometry from .elements.reorder_element import reorder_element +from .elements.move_element_to_container import move_element_to_container from .elements.save_element_controls import save_element_controls from .elements.delete_element import delete_element from .elements.duplicate_element import duplicate_element diff --git a/screens/elements/move_element_to_container.py b/screens/elements/move_element_to_container.py new file mode 100644 index 00000000..17e6bf67 --- /dev/null +++ b/screens/elements/move_element_to_container.py @@ -0,0 +1,62 @@ +import db + +from ..widgets.container_widgets import CONTAINER_WIDGETS + + +def move_element_to_container(slug, element_id, new_parent_id): + """Déplace element_id pour qu'il devienne un enfant de new_parent_id + (glisser-déposer dans l'arborescence, voir screen_edit.html) — posé en + DERNIER enfant, l'ordre précis à l'intérieur se règle ensuite comme + d'habitude par glisser-déposer entre frères et sœurs (voir + reorder_element.py). Refuse silencieusement (ne touche à rien) si : + new_parent_id n'est pas un widget conteneur, si c'est element_id + lui-même, ou si c'est un de ses propres descendants (ça créerait un + cycle — un conteneur ne peut pas finir posé à l'intérieur de lui-même).""" + conn = db.connect(slug) + rows = conn.execute( + "SELECT id, screen_id, parent_id, widget, z_index FROM _screen_elements WHERE id IN (?, ?)", + (element_id, new_parent_id), + ).fetchall() + by_id = {r["id"]: r for r in rows} + el = by_id.get(element_id) + target = by_id.get(new_parent_id) + if not el or not target or el["screen_id"] != target["screen_id"]: + conn.close() + return + if target["widget"] not in CONTAINER_WIDGETS: + conn.close() + return + if new_parent_id == element_id: + conn.close() + return + + # Cycle : new_parent_id ne doit pas être element_id, ni un descendant de + # element_id (direct ou indirect) — sinon element_id finirait posé à + # l'intérieur de sa propre arborescence. + children_of = {} + for r in conn.execute( + "SELECT id, parent_id FROM _screen_elements WHERE screen_id = ?", (el["screen_id"],) + ).fetchall(): + if r["parent_id"]: + children_of.setdefault(r["parent_id"], []).append(r["id"]) + stack = list(children_of.get(element_id, [])) + descendants = set() + while stack: + node = stack.pop() + if node in descendants: + continue + descendants.add(node) + stack.extend(children_of.get(node, [])) + if new_parent_id in descendants: + conn.close() + return + + max_z = conn.execute( + "SELECT MAX(z_index) AS m FROM _screen_elements WHERE screen_id = ?", (el["screen_id"],) + ).fetchone()["m"] + conn.execute( + "UPDATE _screen_elements SET parent_id = ?, z_index = ? WHERE id = ?", + (new_parent_id, (max_z or 0) + 1, element_id), + ) + conn.commit() + conn.close() diff --git a/static/style.css b/static/style.css index 40540c29..9a7dc07e 100644 --- a/static/style.css +++ b/static/style.css @@ -303,6 +303,7 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d .treeRow.dragging{ opacity:0.4; } .treeRow.dropBefore{ box-shadow: inset 0 2px 0 0 var(--accent); } .treeRow.dropAfter{ box-shadow: inset 0 -2px 0 0 var(--accent); } +.treeRow.dropInside{ background:rgba(91,140,255,.25); box-shadow: inset 0 0 0 1px var(--accent); border-radius:4px; } .contextMenu{ position:fixed; z-index:500; background:var(--panel); border:1px solid var(--border); diff --git a/templates/screen_edit.html b/templates/screen_edit.html index 15f926cc..a3dc0050 100644 --- a/templates/screen_edit.html +++ b/templates/screen_edit.html @@ -31,7 +31,7 @@ {% macro render_tree_node(el) %} {% set kids = children_of.get(el.id, []) %}
-
+
{% if kids %} {% else %} @@ -82,7 +82,7 @@ Ajouter un élément sur l'écran
- {% for wkey, w in widgets.items() %} + {% for wkey, w in widgets.items() if wkey != 'icone' %}
+ +
+ 🔤 Icônes +
+ +
+ {% for slug in fontawesome_icons %} + + {% endfor %} +
+

Glisse une icône sur le canevas, ou clique dessus pour l'ajouter — sa couleur et sa taille se règlent ensuite dans ses propriétés ("Icône"). Sélection non exhaustive : d'autres icônes pourront être ajoutées à cette liste plus tard si besoin.

+
+
+