Redesign the screen editor's element tree and add duplication
The "Éléments de cet écran" tree now renders every level of nesting (previously stopped after one level of children) as a compact single-line list, and right-clicking a row opens a context menu to duplicate the element (and its full subtree) in place, in its current container. Also: all property panels start collapsed instead of some being open by default, the redundant nested element list inside "Ajouter DANS ce conteneur" is removed (it only needs the widget picker), and the now-unneeded "a container is selected" warning banner is gone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3039489e39
commit
bb79f2f93d
+82
-44
@@ -29,16 +29,31 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% macro render_tree_node(el) %}
|
||||
<div class="treeNode">
|
||||
<a class="treeRowLink {{ 'selected' if el.id == selected_id else '' }}"
|
||||
href="{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id, selected=el.id) }}"
|
||||
data-element-id="{{ el.id }}"
|
||||
oncontextmenu="return showElementContextMenu(event, {{ el.id }});">
|
||||
<span class="treeIcon">{{ widgets.get(el.widget, {}).get('icon', '❔') }}</span>
|
||||
<span class="treeLabel">{{ el|elabel }}</span>
|
||||
</a>
|
||||
{% set kids = children_of.get(el.id, []) %}
|
||||
{% if kids %}
|
||||
<div class="treeChildren">
|
||||
{% for child in kids %}
|
||||
{{ render_tree_node(child) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="builder3" id="builder3">
|
||||
{% set el = elements|selectattr("id", "equalto", selected_id)|first if selected_id else none %}
|
||||
<!-- Panneau gauche : ajouter un élément (icônes, sans jargon technique) -->
|
||||
<div class="builderPanel">
|
||||
{% if selected_id and el and el.widget in container_widgets %}
|
||||
<p class="hint" style="background:#2a1f10; border:1px solid #6b4a12; border-radius:8px; padding:8px 12px; margin-bottom:10px;">
|
||||
⚠️ Un conteneur est sélectionné. La grille ci-dessous pose l'élément directement sur l'écran, PAS à l'intérieur du conteneur — pour poser un élément dans le conteneur sélectionné, utilise plutôt <strong>« 📥 Ajouter DANS ce conteneur »</strong> dans le panneau de droite.
|
||||
</p>
|
||||
{% endif %}
|
||||
<details class="propGroup" open>
|
||||
<details class="propGroup">
|
||||
<summary>Ajouter un élément sur l'écran</summary>
|
||||
<div class="propGroupBody">
|
||||
<div class="widgetGrid">
|
||||
@@ -59,7 +74,7 @@
|
||||
Outil, IA...) : chacun a désormais SA PROPRE tuile, avec son nom,
|
||||
plutôt qu'une unique tuile générique "Élément de jeu" qui ajoutait
|
||||
toujours le premier du catalogue sans laisser choisir lequel. -->
|
||||
<details class="propGroup" {{ 'open' if element_types else '' }}>
|
||||
<details class="propGroup">
|
||||
<summary>🧩 Mes éléments de jeu{{ ' (' ~ element_types|length ~ ')' if element_types else '' }}</summary>
|
||||
<div class="propGroupBody">
|
||||
{% if element_types %}
|
||||
@@ -83,24 +98,9 @@
|
||||
|
||||
<h2>Éléments de cet écran</h2>
|
||||
{% if top_level_elements %}
|
||||
<div class="list">
|
||||
<div class="elementTree">
|
||||
{% for el in top_level_elements %}
|
||||
<a class="listRow {{ 'selected' if el.id == selected_id else '' }}" id="listrow-{{ el.id }}"
|
||||
href="{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id, selected=el.id) }}">
|
||||
<div class="rowTitle">{{ widgets.get(el.widget, {}).get('icon', '❔') }} {{ el|elabel }}</div>
|
||||
<div class="rowSub">{{ (el.content or '—')[:30] }}</div>
|
||||
</a>
|
||||
{% if el.widget in container_widgets and children_of.get(el.id) %}
|
||||
<div class="childList">
|
||||
{% for child in children_of.get(el.id) %}
|
||||
<a class="listRow childRow {{ 'selected' if child.id == selected_id else '' }}"
|
||||
href="{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id, selected=child.id) }}">
|
||||
<div class="rowTitle">↳ {{ widgets.get(child.widget, {}).get('icon', '❔') }} {{ child|elabel }}</div>
|
||||
<div class="rowSub">{{ (child.content or '—')[:26] }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ render_tree_node(el) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -108,6 +108,11 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Menu contextuel (clic droit sur un élément de l'arborescence) -->
|
||||
<div id="elementContextMenu" class="contextMenu" style="display:none;">
|
||||
<button type="button" onclick="contextMenuDuplicate()">📄 Dupliquer</button>
|
||||
</div>
|
||||
|
||||
<!-- Centre : l'écran, en très grand -->
|
||||
<div class="builderCanvasArea">
|
||||
{% set current_aspect = screen.preview_aspect or '9/16' %}
|
||||
@@ -156,23 +161,9 @@
|
||||
</div>
|
||||
|
||||
{% if el.widget in container_widgets %}
|
||||
<details class="propGroup containerContentGroup" open>
|
||||
<details class="propGroup containerContentGroup">
|
||||
<summary>📥 Ajouter DANS ce conteneur</summary>
|
||||
<div class="propGroupBody">
|
||||
{% set children = children_of.get(el.id, []) %}
|
||||
{% if children %}
|
||||
<div class="list">
|
||||
{% for child in children %}
|
||||
<a class="listRow childRow {{ 'selected' if child.id == selected_id else '' }}"
|
||||
href="{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id, selected=child.id) }}">
|
||||
<div class="rowTitle">↳ {{ widgets.get(child.widget, {}).get('icon', '❔') }} {{ child|elabel }}</div>
|
||||
<div class="rowSub">{{ (child.content or '—')[:26] }}</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="hint">Aucun élément à l'intérieur pour l'instant — ajoute-en un ci-dessous.</p>
|
||||
{% endif %}
|
||||
<div class="widgetGrid">
|
||||
{% for wkey, w in widgets.items() %}
|
||||
<form method="post" action="{{ url_for('element_add_child', slug=game.slug, parent_id=el.id) }}" class="widgetBtnForm">
|
||||
@@ -193,7 +184,7 @@
|
||||
{% for c in selected_controls if c.key in size_ctrl_keys %}{% set _ = size_ctrls.update({c.key: c}) %}{% endfor %}
|
||||
|
||||
<form method="post" action="{{ url_for('element_save', slug=game.slug, element_id=el.id) }}" id="propsForm" onsubmit="event.preventDefault(); return false;">
|
||||
<details class="propGroup" open>
|
||||
<details class="propGroup">
|
||||
<summary>Position & taille</summary>
|
||||
<div class="propGroupBody">
|
||||
<div class="posGrid">
|
||||
@@ -236,7 +227,7 @@
|
||||
</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
<details class="propGroup" {{ 'open' if not ns.started else '' }}>
|
||||
<details class="propGroup">
|
||||
<summary>{{ c.group or 'Propriétés' }}</summary>
|
||||
<div class="propGroupBody">
|
||||
{% set ns.last_group = c.group %}
|
||||
@@ -587,6 +578,53 @@ function saveGeometry(id, x, y, width, height) {
|
||||
|
||||
var SCREEN_EDIT_URL = "{{ url_for('screen_edit', slug=game.slug, screen_id=screen.id) }}";
|
||||
var CURRENT_SELECTED_ID = {{ selected_id or 'null' }};
|
||||
var GAME_SLUG = "{{ game.slug }}";
|
||||
|
||||
// ---------- Menu contextuel (clic droit) de l'arborescence des éléments ----------
|
||||
//
|
||||
// Une seule action pour l'instant : dupliquer l'élément dans son conteneur
|
||||
// actuel (même parent, ou même écran si posé directement dessus) — voir
|
||||
// screens/elements/duplicate_element.py. La duplication passe par un vrai
|
||||
// <form method="post"> soumis dynamiquement : pjax.js intercepte tous les
|
||||
// formulaires internes, donc pas besoin de gérer le fetch/la redirection
|
||||
// nous-mêmes ici.
|
||||
var contextMenuElementId = null;
|
||||
|
||||
function showElementContextMenu(event, elementId) {
|
||||
event.preventDefault();
|
||||
contextMenuElementId = elementId;
|
||||
var menu = document.getElementById('elementContextMenu');
|
||||
menu.style.left = event.pageX + 'px';
|
||||
menu.style.top = event.pageY + 'px';
|
||||
menu.style.display = 'block';
|
||||
return false;
|
||||
}
|
||||
|
||||
function hideElementContextMenu() {
|
||||
var menu = document.getElementById('elementContextMenu');
|
||||
if (menu) menu.style.display = 'none';
|
||||
contextMenuElementId = null;
|
||||
}
|
||||
|
||||
function contextMenuDuplicate() {
|
||||
var id = contextMenuElementId;
|
||||
hideElementContextMenu();
|
||||
if (!id) return;
|
||||
var form = document.createElement('form');
|
||||
form.method = 'post';
|
||||
form.action = '/game/' + GAME_SLUG + '/elements/' + id + '/duplicate';
|
||||
document.body.appendChild(form);
|
||||
form.requestSubmit();
|
||||
}
|
||||
|
||||
if (!window.__forgeContextMenuBound) {
|
||||
window.__forgeContextMenuBound = true;
|
||||
document.addEventListener('click', function (e) {
|
||||
var menu = document.getElementById('elementContextMenu');
|
||||
if (menu && menu.style.display === 'block' && !menu.contains(e.target)) hideElementContextMenu();
|
||||
});
|
||||
document.addEventListener('scroll', hideElementContextMenu, true);
|
||||
}
|
||||
|
||||
// ---------- Navigation dans l'éditeur sans AUCUN rechargement de page ----------
|
||||
//
|
||||
@@ -636,9 +674,9 @@ function refreshCanvasOnly() {
|
||||
// Aperçu du contenu dans l'arborescence de gauche (ex: texte tronqué) —
|
||||
// sans toucher au panneau de droite, pour ne jamais faire perdre le
|
||||
// focus/curseur pendant qu'on modifie un réglage.
|
||||
var newList = doc.querySelector('.builderPanel .list');
|
||||
var curList = document.querySelector('.builderPanel .list');
|
||||
if (newList && curList) curList.innerHTML = newList.innerHTML;
|
||||
var newTree = doc.querySelector('.builderPanel .elementTree');
|
||||
var curTree = document.querySelector('.builderPanel .elementTree');
|
||||
if (newTree && curTree) curTree.innerHTML = newTree.innerHTML;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user