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.

+
+
+