Éditeur de scène : Logique/Timeline en onglets plein écran, réglages d'écran déplacés dans le panneau Éléments
Remplace l'ancien panneau du bas rétractable/redimensionnable à la souris (Logique de la scène, Timeline d'animation) par un système d'onglets au centre de l'éditeur - Écran / Logique de la scène / Timeline d'animation - un seul visible à la fois, occupant systématiquement tout l'espace disponible (switchBuilderTab() dans screen_edit.html). Changer d'onglet équivaut à "fermer" celui qu'on quitte ; plus besoin d'une poignée de redimensionnement séparée puisque l'onglet actif prend déjà toute la place. Supprime au passage tout l'ancien mécanisme (toggleFlowPanel/ toggleAnimPanel, poignées flowResizeHandle/animResizeHandle, classe CSS .flowPanel) devenu inutile. Déplace aussi le renommage de l'écran, le bouton "Jouer depuis le début" et le choix du format d'aperçu (Portrait/Paysage/Carré) - jusqu'ici au-dessus du canevas - dans le panneau flottant "Éléments" (celui qui porte déjà ce nom, à gauche) : des réglages qu'on touche rarement une fois l'écran en construction, qui n'ont plus besoin de rester en permanence visibles au-dessus de la zone de travail. Corrige au passage un bug découvert pendant ce tour : sur la page "Nouvel objet" (2 colonnes), le bouton "+ Ajouter un champ" avait disparu - placé APRÈS la zone de liste à défilement (flex-grow:1) dans la colonne de droite, un flex-grow imprévisible dans ce contexte le poussait hors de vue. Déplacé avant la liste (statique, toujours visible), pattern déjà éprouvé ailleurs sur cette même page. Deux tests mis à jour pour refléter intentionnellement la nouvelle structure : la présence de "animTabPanel" (au lieu de l'ancien "animPanel"), et un marqueur plus précis pour distinguer le bloc de propriétés "Onglets" d'une simple occurrence du même texte dans une liste déroulante de la Logique de la scène (qui apparaît désormais plus tôt dans le document, cet éditeur de flow étant maintenant un onglet du centre plutôt qu'un panneau tout en bas de page). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
2051b375ac
commit
ea9f91fc19
+19
-31
@@ -114,7 +114,7 @@ body.objectEditBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden
|
||||
.content-objectEdit .colLeft{ overflow-y:auto; height:100%; }
|
||||
.content-objectEdit .colRight{ display:flex; flex-direction:column; min-height:0; height:100%; }
|
||||
.content-objectEdit .colRight h2{ flex:0 0 auto; }
|
||||
.content-objectEdit .colRight > .hint{ flex:0 0 auto; }
|
||||
.content-objectEdit .colRight > .hint, .content-objectEdit .colRight > button{ flex:0 0 auto; }
|
||||
|
||||
.fieldsTableWrap{ flex:1 1 auto; min-height:0; overflow:auto; border:1px solid var(--border); border-radius:8px; }
|
||||
.fieldsTable{ width:100%; border-collapse:collapse; font-size:12.5px; }
|
||||
@@ -149,7 +149,6 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
|
||||
width:100%;
|
||||
max-width:1600px;
|
||||
}
|
||||
.content-builder > .fieldRow:first-child{ flex:0 0 auto; }
|
||||
|
||||
/* 3 colonnes : ajout d'éléments (gauche) / écran en grand (centre) / propriétés (droite) —
|
||||
toutes les trois occupent exactement la même hauteur (celle qui reste
|
||||
@@ -375,37 +374,26 @@ code{ background:var(--panel2); border:1px solid var(--border); border-radius:4p
|
||||
.floatPanelReopenBtn{ display:none !important; }
|
||||
}
|
||||
|
||||
/* ---------- Logique de la scène : éditeur de flow à nœuds ---------- */
|
||||
/* Panneau du bas, dans le même flux flexbox que .builder3 (pas une
|
||||
surcouche/overlay) : replié, il ne prend que la hauteur de son en-tête et
|
||||
.builder3 récupère tout le reste de l'espace ; ouvert, sa hauteur est
|
||||
fixée en pixels (réglable à la souris via la poignée juste au-dessus) et
|
||||
.builder3 rétrécit d'autant automatiquement — jamais l'un par-dessus
|
||||
l'autre. */
|
||||
.flowPanel{
|
||||
flex:0 0 auto; border:1px solid var(--border); border-radius:10px; background:var(--panel);
|
||||
margin-top:8px; display:flex; flex-direction:column; overflow:hidden;
|
||||
/* ---------- Onglets du centre : Écran / Logique de la scène / Timeline
|
||||
d'animation ---------- */
|
||||
/* Un seul onglet visible à la fois (voir switchBuilderTab() dans
|
||||
screen_edit.html), chacun occupant tout l'espace disponible dans
|
||||
.builderCanvasArea — remplace l'ancien panneau du bas rétractable/
|
||||
redimensionnable à la souris : un onglet "prend toute la place" par
|
||||
nature, pas besoin d'une poignée de redimensionnement séparée, et en
|
||||
changer d'onglet équivaut à "fermer" celui qu'on quitte. */
|
||||
.builderTabs{ display:flex; gap:2px; flex:0 0 auto; border-bottom:1px solid var(--border); margin-bottom:10px; }
|
||||
.builderTabBtn{
|
||||
background:transparent; border:none; border-bottom:2px solid transparent; border-radius:0;
|
||||
padding:8px 12px; color:var(--text-dim); font-size:12.5px; cursor:pointer;
|
||||
}
|
||||
.flowPanelHeader{
|
||||
flex:0 0 auto; padding:10px 16px; font-weight:700; cursor:pointer; user-select:none;
|
||||
display:flex; align-items:center; gap:6px;
|
||||
}
|
||||
.flowPanelHeader:hover{ background:var(--panel2); }
|
||||
.flowToggleIcon{ color:var(--text-dim); font-size:11px; transition:transform .15s; display:inline-block; }
|
||||
.flowPanel.open .flowToggleIcon{ transform:rotate(90deg); }
|
||||
.flowPanelBody{ flex:1 1 auto; min-height:0; padding:0 16px 16px; display:none; flex-direction:column; }
|
||||
.flowPanel.open .flowPanelBody{ display:flex; }
|
||||
.flowToolbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:10px; flex:0 0 auto; }
|
||||
.builderTabBtn:hover{ color:var(--text); }
|
||||
.builderTabBtn.active{ color:var(--accent); border-bottom-color:var(--accent); font-weight:600; }
|
||||
|
||||
.flowResizeHandle{
|
||||
flex:0 0 auto; height:10px; margin-top:6px; cursor:row-resize; position:relative; display:none;
|
||||
}
|
||||
.flowResizeHandle.visible{ display:block; }
|
||||
.flowResizeHandle::before{
|
||||
content:""; position:absolute; left:50%; top:3px; width:60px; height:4px;
|
||||
background:var(--border); border-radius:2px; transform:translateX(-50%);
|
||||
}
|
||||
.flowResizeHandle:hover::before{ background:var(--accent); }
|
||||
.builderTabPanel{ flex:1 1 auto; min-height:0; display:none; flex-direction:column; }
|
||||
.builderTabPanel.active{ display:flex; }
|
||||
|
||||
.flowToolbar{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:10px; flex:0 0 auto; }
|
||||
|
||||
.nodeForm{ border:1px solid var(--border); border-radius:8px; padding:12px; margin-bottom:12px; background:var(--panel2); max-width:420px; flex:0 0 auto; }
|
||||
.nodeForm label{ margin-top:8px; }
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
|
||||
<div class="colRight">
|
||||
<h2>Champs</h2>
|
||||
<button type="button" class="button is-outlined is-fullwidth mb-2" id="btnAddField">+ Ajouter un champ</button>
|
||||
<div class="fieldsTableWrap" style="padding:10px;">
|
||||
<div id="fieldsList"></div>
|
||||
</div>
|
||||
<button type="button" class="button is-outlined is-fullwidth mt-2" id="btnAddField">+ Ajouter un champ</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+323
-413
@@ -12,16 +12,6 @@
|
||||
<li class="is-active"><a>{{ screen.name }}</a></li>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<form method="post" action="{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class="field has-addons" style="align-items:center; margin:0;">
|
||||
<div class="control"><input class="input" type="text" name="name" value="{{ screen.name }}" required style="font-size:16px; font-weight:700; width:auto;"></div>
|
||||
<div class="control"><button type="submit" class="button is-small">Renommer</button></div>
|
||||
</form>
|
||||
{% if not screen.is_template %}
|
||||
<a class="button is-small is-primary" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">▶️ Jouer depuis le début</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if screen.is_template %}
|
||||
<p class="hint" style="background:#1c2130; border:1px solid #2b3140; border-radius:8px; padding:10px 14px; margin:10px 0;">
|
||||
🧩 Contenu d'un élément de jeu réutilisable.
|
||||
@@ -67,6 +57,25 @@
|
||||
<button type="button" class="button is-small" onclick="closeLeftPanel()" title="Fermer">✕</button>
|
||||
</div>
|
||||
<div class="floatPanelBody">
|
||||
<!-- Nom de l'écran, aperçu jouable, et format de l'aperçu (Portrait/
|
||||
Paysage/Carré) : regroupés ici plutôt qu'au-dessus du canevas —
|
||||
ce sont des réglages qu'on touche rarement une fois l'écran en
|
||||
cours de construction, pas besoin qu'ils prennent en permanence de
|
||||
la place dans la zone centrale. -->
|
||||
<form method="post" action="{{ url_for('screen_rename', slug=game.slug, screen_id=screen.id) }}" class="field has-addons" style="align-items:center; margin-bottom:8px;">
|
||||
<div class="control is-expanded"><input class="input is-small" type="text" name="name" value="{{ screen.name }}" required></div>
|
||||
<div class="control"><button type="submit" class="button is-small">Renommer</button></div>
|
||||
</form>
|
||||
{% if not screen.is_template %}
|
||||
<a class="button is-small is-primary is-fullwidth mb-2" href="{{ url_for('game_play', slug=game.slug) }}" target="_blank">▶️ Jouer depuis le début</a>
|
||||
{% endif %}
|
||||
{% set current_aspect = screen.preview_aspect or '9/16' %}
|
||||
<div class="buttons has-addons are-small mb-2" style="flex-wrap:nowrap;">
|
||||
<button type="button" class="button smallBtn aspectBtn {{ 'active' if current_aspect == '9/16' else '' }}" data-aspect="9/16">📱 Portrait</button>
|
||||
<button type="button" class="button smallBtn aspectBtn {{ 'active' if current_aspect == '16/9' else '' }}" data-aspect="16/9">🖥️ Paysage</button>
|
||||
<button type="button" class="button smallBtn aspectBtn {{ 'active' if current_aspect == '1/1' else '' }}" data-aspect="1/1">◻️ Carré</button>
|
||||
</div>
|
||||
<hr style="border-color:var(--border); margin:10px 0;">
|
||||
<h2 class="subtitle is-6">Éléments de cet écran</h2>
|
||||
{% if top_level_elements %}
|
||||
<div class="elementTree">
|
||||
@@ -149,27 +158,299 @@
|
||||
<button type="button" onclick="contextMenuDuplicate()">📄 Dupliquer</button>
|
||||
</div>
|
||||
|
||||
<!-- Centre : l'écran, en très grand -->
|
||||
<!-- Centre : l'écran (onglet "Écran"), ou la logique de la scène / la
|
||||
timeline d'animation quand leur onglet est actif — un seul onglet
|
||||
visible à la fois, chacun occupant tout l'espace disponible (voir
|
||||
switchBuilderTab() plus bas). Absents sur un écran-modèle (élément
|
||||
de jeu) : ses déclencheurs référenceraient des éléments dont l'id
|
||||
change à chaque copie (voir instantiate_template_tree), donc une
|
||||
logique/animation posée ici ne fonctionnerait plus une fois
|
||||
l'élément réutilisé — seul l'onglet "Écran" a alors un sens. -->
|
||||
<div class="builderCanvasArea">
|
||||
{% set current_aspect = screen.preview_aspect or '9/16' %}
|
||||
<div class="canvasToolbar">
|
||||
<span class="hint">Aperçu :</span>
|
||||
<button type="button" class="smallBtn aspectBtn {{ 'active' if current_aspect == '9/16' else '' }}" data-aspect="9/16">📱 Portrait</button>
|
||||
<button type="button" class="smallBtn aspectBtn {{ 'active' if current_aspect == '16/9' else '' }}" data-aspect="16/9">🖥️ Paysage</button>
|
||||
<button type="button" class="smallBtn aspectBtn {{ 'active' if current_aspect == '1/1' else '' }}" data-aspect="1/1">◻️ Carré</button>
|
||||
{% if not screen.is_template %}
|
||||
<div class="builderTabs" id="builderTabs">
|
||||
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">🖥️ Écran</button>
|
||||
<button type="button" class="builderTabBtn" data-tab="flow" onclick="switchBuilderTab('flow')">🔀 Logique de la scène</button>
|
||||
<button type="button" class="builderTabBtn" data-tab="anim" onclick="switchBuilderTab('anim')">🎬 Timeline d'animation</button>
|
||||
</div>
|
||||
<div class="canvasFrame">
|
||||
<div class="canvas" id="canvas" style="aspect-ratio:{{ current_aspect }};">
|
||||
{% for el in top_level_elements %}
|
||||
<div class="canvasElement {{ 'selected' if el.id == selected_id else '' }}" id="el-{{ el.id }}" style="{{ el|elstyle }}" data-anim-target="{{ el.id }}"
|
||||
onmousedown="onElementMouseDown(event, {{ el.id }})">
|
||||
<div class="canvasElementInner" style="{{ el|eltransform }}">{{ el.rendered_html|safe }}</div>
|
||||
<div class="resizeHandle" onmousedown="onResizeMouseDown(event, {{ el.id }})"></div>
|
||||
{% endif %}
|
||||
|
||||
<div class="builderTabPanel active" data-tab-panel="screen">
|
||||
<div class="canvasFrame">
|
||||
<div class="canvas" id="canvas" style="aspect-ratio:{{ current_aspect }};">
|
||||
{% for el in top_level_elements %}
|
||||
<div class="canvasElement {{ 'selected' if el.id == selected_id else '' }}" id="el-{{ el.id }}" style="{{ el|elstyle }}" data-anim-target="{{ el.id }}"
|
||||
onmousedown="onElementMouseDown(event, {{ el.id }})">
|
||||
<div class="canvasElementInner" style="{{ el|eltransform }}">{{ el.rendered_html|safe }}</div>
|
||||
<div class="resizeHandle" onmousedown="onResizeMouseDown(event, {{ el.id }})"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner.</p>
|
||||
</div>
|
||||
|
||||
{% if not screen.is_template %}
|
||||
<div class="builderTabPanel" id="flowTabPanel" data-tab-panel="flow">
|
||||
<div class="flowToolbar">
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('trigger')">➕ Déclencheur</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('condition')">➕ Condition</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('action')">➕ Action</button>
|
||||
<span class="hint">Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.</span>
|
||||
</div>
|
||||
|
||||
<div id="nodeForm" class="nodeForm" style="display:none;">
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<strong id="nodeFormTitle">Nouveau nœud</strong>
|
||||
<button type="button" class="smallBtn button is-small" onclick="closeNodeForm()">✕</button>
|
||||
</div>
|
||||
|
||||
<div id="nodeFormTrigger" style="display:none;">
|
||||
<label>Événement<select id="nf-trigger-event" onchange="toggleFlowTriggerFields()">
|
||||
{% for key, label in trigger_events %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label id="nf-trigger-element-row">Élément<select id="nf-trigger-element">
|
||||
{% for e2 in elements %}
|
||||
<option value="{{ e2.id }}">{{ e2|elabel }} (elt-{{ e2.id }})</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<p class="hint" id="nf-trigger-repeater-hint" style="display:none;"></p>
|
||||
<p class="hint" id="nf-trigger-affichage-hint" style="display:none;"></p>
|
||||
</div>
|
||||
|
||||
<div id="nodeFormCondition" style="display:none;">
|
||||
{% if not definitions %}
|
||||
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('object_new', slug=game.slug) }}">crées-en un</a>.</p>
|
||||
{% else %}
|
||||
<label>Objet<select id="nf-cond-definition" onchange="onFlowDefinitionChange('cond')">
|
||||
{% for d in definitions %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label>Ligne concernée<select id="nf-cond-row"></select></label>
|
||||
<label>Champ<select id="nf-cond-field" onchange="onFlowFieldChange()"></select></label>
|
||||
<label>Condition<select id="nf-cond-operator">
|
||||
{% for key, label in condition_operators %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label>Valeur de comparaison<input type="text" id="nf-cond-value" placeholder="ex. 1, vrai, 10..."></label>
|
||||
|
||||
<div id="nf-cond-extra-wrap">
|
||||
<label id="nf-cond-combinator-row" style="display:none;">Combiner les conditions avec
|
||||
<select id="nf-cond-combinator">
|
||||
<option value="et">ET (toutes les conditions doivent être vraies)</option>
|
||||
<option value="ou">OU (au moins une condition doit être vraie)</option>
|
||||
</select>
|
||||
</label>
|
||||
<div id="nf-cond-extra-clauses"></div>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addFlowConditionClause()">+ Ajouter une condition</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="nodeFormAction" style="display:none;">
|
||||
<label>Type d'action<select id="nf-action-type" onchange="toggleFlowActionFields()">
|
||||
{% for key, label in action_type_labels.items() %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
|
||||
<select id="nf-action-target-screen" style="display:none;">
|
||||
{% for s in all_screens %}
|
||||
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<div id="nf-action-element-fields" style="display:none;">
|
||||
<select id="nf-action-target-element">
|
||||
{% for e2 in elements %}
|
||||
<option value="{{ e2.id }}">{{ e2|elabel }} (elt-{{ e2.id }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="hint" id="nf-action-onglet-hint" style="display:none;"></p>
|
||||
<div id="nf-action-element-property-wrap">
|
||||
<select id="nf-action-element-property" onchange="toggleFlowElementPropertyValue()" style="margin-top:6px;">
|
||||
{% for key, label in element_action_properties %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-element-value-visibility" style="margin-top:6px;">
|
||||
{% for key, label in element_visibility_values %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-element-value-surbrillance" style="display:none; margin-top:6px;">
|
||||
{% for key, label in element_surbrillance_values %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-element-value-desactive" style="display:none; margin-top:6px;">
|
||||
{% for key, label in element_desactive_values %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="color" id="nf-action-element-value-color" style="display:none; margin-top:6px;" value="#5b8cff">
|
||||
<input type="number" id="nf-action-element-value-number" style="display:none; margin-top:6px;" value="50" placeholder="Nouvelle valeur en %">
|
||||
<input type="text" id="nf-action-element-value-text" style="display:none; margin-top:6px;" placeholder="Nouveau texte affiché">
|
||||
|
||||
<label id="nf-action-element-toggle-row" style="display:flex; align-items:center; gap:6px; margin-top:10px; font-size:12px; color:var(--text-dim);">
|
||||
<input type="checkbox" id="nf-action-element-toggle" onchange="toggleFlowElementToggleMode()" style="width:auto; margin:0;">
|
||||
Va-et-vient : au 1er clic, passer à cette valeur ; au 2e clic, revenir à la précédente ; etc.
|
||||
</label>
|
||||
<div id="nf-action-element-value2-wrap" style="display:none; margin-top:6px;">
|
||||
<input type="color" id="nf-action-element-value2-color" style="display:none;" value="#ff0000">
|
||||
<input type="number" id="nf-action-element-value2-number" style="display:none;" placeholder="Valeur au 2e état en %">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="nf-action-wait-fields" style="display:none;">
|
||||
<label>Délai en secondes avant de continuer
|
||||
<input type="number" id="nf-action-wait-seconds" min="0" step="0.5" value="3" placeholder="ex. 3">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="nf-action-data-fields" style="display:none;">
|
||||
{% if not definitions %}
|
||||
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('object_new', slug=game.slug) }}">crées-en un</a>.</p>
|
||||
{% else %}
|
||||
<select id="nf-action-data-definition" onchange="onFlowDefinitionChange('action')">
|
||||
{% for d in definitions %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-data-row" style="margin-top:6px;"></select>
|
||||
<select id="nf-action-data-field" style="margin-top:6px;"></select>
|
||||
<select id="nf-action-data-operation" style="margin-top:6px;">
|
||||
{% for key, label in data_operations %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" id="nf-action-data-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="nf-action-variable-fields" style="display:none;">
|
||||
{% if not global_variables %}
|
||||
<p class="hint">Aucune variable globale pour l'instant — crées-en une depuis "Condition de visibilité" dans les propriétés d'un élément.</p>
|
||||
{% else %}
|
||||
<select id="nf-action-variable-name">
|
||||
{% for v in global_variables %}
|
||||
<option value="{{ v.name }}">{{ v.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-variable-operation" style="margin-top:6px;">
|
||||
{% for key, label in data_operations %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" id="nf-action-variable-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actionsRow"><button type="button" class="smallBtn button is-small is-primary" onclick="submitNodeForm()">+ Ajouter ce nœud</button></div>
|
||||
</div>
|
||||
|
||||
<div class="flowCanvasWrap">
|
||||
<svg id="flowEdges" class="flowEdgesSvg"></svg>
|
||||
<div id="flowCanvas" class="flowCanvas"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner.</p>
|
||||
|
||||
<div class="builderTabPanel" id="animTabPanel" data-tab-panel="anim">
|
||||
<div class="flowToolbar">
|
||||
<label style="display:flex; align-items:center; gap:6px;">
|
||||
Piste
|
||||
<select id="anim-new-track-element">
|
||||
{% for e2 in elements %}
|
||||
<option value="{{ e2.id }}">{{ e2|elabel }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addAnimTrack()">➕ Ajouter un clip</button>
|
||||
</div>
|
||||
|
||||
<div id="animClipForm" class="nodeForm" style="display:none;">
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<strong id="animClipFormTitle">Clip d'animation</strong>
|
||||
<button type="button" class="smallBtn button is-small" onclick="closeAnimClipForm()">✕</button>
|
||||
</div>
|
||||
|
||||
<label>Type
|
||||
<select id="anim-clip-kind" onchange="toggleAnimClipKind()">
|
||||
<option value="animate_css">Animation Animate.css</option>
|
||||
<option value="custom">Animation personnalisée — images-clés</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div id="anim-clip-animate-css-fields">
|
||||
<label>Animation
|
||||
<select id="anim-clip-animate-name">
|
||||
{% for category, items in animate_css_catalog %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for key, label in items %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="anim-clip-custom-fields" style="display:none;">
|
||||
<div id="anim-clip-keyframes"></div>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
|
||||
</div>
|
||||
|
||||
<div class="fieldRow">
|
||||
<label style="flex:1;">Départ en secondes
|
||||
<input type="number" id="anim-clip-start" min="0" step="0.1" value="0">
|
||||
</label>
|
||||
<label style="flex:1;">Durée en secondes
|
||||
<input type="number" id="anim-clip-duration" min="0.1" step="0.1" value="1">
|
||||
</label>
|
||||
</div>
|
||||
<div class="fieldRow">
|
||||
<label style="flex:1;">Délai en secondes
|
||||
<input type="number" id="anim-clip-delay" min="0" step="0.1" value="0">
|
||||
</label>
|
||||
<label style="flex:1;">Vitesse
|
||||
<select id="anim-clip-easing">
|
||||
<option value="ease">Progressive</option>
|
||||
<option value="linear">Constante</option>
|
||||
<option value="ease-in">Accélère</option>
|
||||
<option value="ease-out">Ralentit</option>
|
||||
<option value="ease-in-out">Accélère puis ralentit</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label>Répétitions
|
||||
<select id="anim-clip-iteration">
|
||||
<option value="1">Une fois</option>
|
||||
<option value="2">2 fois</option>
|
||||
<option value="3">3 fois</option>
|
||||
<option value="infinite">En boucle</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="actionsRow">
|
||||
<button type="button" class="smallBtn button is-small is-primary" onclick="saveAnimClipForm()">Enregistrer</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="previewAnimClip()">▶ Aperçu</button>
|
||||
<button type="button" class="smallBtn button is-small is-danger" onclick="deleteAnimClipForm()">🗑️ Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="animTimelineWrap">
|
||||
<div id="animRuler" class="animRuler"></div>
|
||||
<div id="animTracks" class="animTracks"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Panneau droit : propriétés de l'élément sélectionné — flottant,
|
||||
@@ -468,298 +749,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not screen.is_template %}
|
||||
<!-- Poignée de redimensionnement : visible seulement quand le panneau du bas
|
||||
est ouvert, on la tire à la souris pour régler sa hauteur. -->
|
||||
<div class="flowResizeHandle" id="flowResizeHandle"></div>
|
||||
|
||||
<!-- Panneau bas, rétractable : logique de la scène (éditeur de flow à nœuds).
|
||||
Volontairement PAS un <details> natif : sa hauteur ouverte doit pouvoir
|
||||
être réglée à la souris (voir flowResizeHandle), ce qu'un <details> ne
|
||||
permet pas nativement. Absent pour un écran-modèle (élément de jeu) :
|
||||
ses déclencheurs référenceraient des éléments dont l'id change à chaque
|
||||
copie (voir instantiate_template_tree), donc une logique posée ici ne
|
||||
fonctionnerait plus une fois l'élément réutilisé. -->
|
||||
<div class="flowPanel" id="flowPanel">
|
||||
<div class="flowPanelHeader" onclick="toggleFlowPanel()">
|
||||
<span class="flowToggleIcon">▸</span> 🔀 Logique de la scène — nœuds et connexions
|
||||
</div>
|
||||
<div class="flowPanelBody" id="flowPanelBody">
|
||||
<div class="flowToolbar">
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('trigger')">➕ Déclencheur</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('condition')">➕ Condition</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="openNodeForm('action')">➕ Action</button>
|
||||
<span class="hint">Clique un point de sortie d'un nœud, puis le point d'entrée d'un autre nœud, pour les relier. Reclique une connexion pour la supprimer.</span>
|
||||
</div>
|
||||
|
||||
<div id="nodeForm" class="nodeForm" style="display:none;">
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<strong id="nodeFormTitle">Nouveau nœud</strong>
|
||||
<button type="button" class="smallBtn button is-small" onclick="closeNodeForm()">✕</button>
|
||||
</div>
|
||||
|
||||
<div id="nodeFormTrigger" style="display:none;">
|
||||
<label>Événement<select id="nf-trigger-event" onchange="toggleFlowTriggerFields()">
|
||||
{% for key, label in trigger_events %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label id="nf-trigger-element-row">Élément<select id="nf-trigger-element">
|
||||
{% for e2 in elements %}
|
||||
<option value="{{ e2.id }}">{{ e2|elabel }} (elt-{{ e2.id }})</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<p class="hint" id="nf-trigger-repeater-hint" style="display:none;"></p>
|
||||
<p class="hint" id="nf-trigger-affichage-hint" style="display:none;"></p>
|
||||
</div>
|
||||
|
||||
<div id="nodeFormCondition" style="display:none;">
|
||||
{% if not definitions %}
|
||||
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('object_new', slug=game.slug) }}">crées-en un</a>.</p>
|
||||
{% else %}
|
||||
<label>Objet<select id="nf-cond-definition" onchange="onFlowDefinitionChange('cond')">
|
||||
{% for d in definitions %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label>Ligne concernée<select id="nf-cond-row"></select></label>
|
||||
<label>Champ<select id="nf-cond-field" onchange="onFlowFieldChange()"></select></label>
|
||||
<label>Condition<select id="nf-cond-operator">
|
||||
{% for key, label in condition_operators %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
<label>Valeur de comparaison<input type="text" id="nf-cond-value" placeholder="ex. 1, vrai, 10..."></label>
|
||||
|
||||
<div id="nf-cond-extra-wrap">
|
||||
<label id="nf-cond-combinator-row" style="display:none;">Combiner les conditions avec
|
||||
<select id="nf-cond-combinator">
|
||||
<option value="et">ET (toutes les conditions doivent être vraies)</option>
|
||||
<option value="ou">OU (au moins une condition doit être vraie)</option>
|
||||
</select>
|
||||
</label>
|
||||
<div id="nf-cond-extra-clauses"></div>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addFlowConditionClause()">+ Ajouter une condition</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="nodeFormAction" style="display:none;">
|
||||
<label>Type d'action<select id="nf-action-type" onchange="toggleFlowActionFields()">
|
||||
{% for key, label in action_type_labels.items() %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select></label>
|
||||
|
||||
<select id="nf-action-target-screen" style="display:none;">
|
||||
{% for s in all_screens %}
|
||||
<option value="{{ s.id }}">{{ s.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<div id="nf-action-element-fields" style="display:none;">
|
||||
<select id="nf-action-target-element">
|
||||
{% for e2 in elements %}
|
||||
<option value="{{ e2.id }}">{{ e2|elabel }} (elt-{{ e2.id }})</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<p class="hint" id="nf-action-onglet-hint" style="display:none;"></p>
|
||||
<div id="nf-action-element-property-wrap">
|
||||
<select id="nf-action-element-property" onchange="toggleFlowElementPropertyValue()" style="margin-top:6px;">
|
||||
{% for key, label in element_action_properties %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-element-value-visibility" style="margin-top:6px;">
|
||||
{% for key, label in element_visibility_values %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-element-value-surbrillance" style="display:none; margin-top:6px;">
|
||||
{% for key, label in element_surbrillance_values %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-element-value-desactive" style="display:none; margin-top:6px;">
|
||||
{% for key, label in element_desactive_values %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="color" id="nf-action-element-value-color" style="display:none; margin-top:6px;" value="#5b8cff">
|
||||
<input type="number" id="nf-action-element-value-number" style="display:none; margin-top:6px;" value="50" placeholder="Nouvelle valeur en %">
|
||||
<input type="text" id="nf-action-element-value-text" style="display:none; margin-top:6px;" placeholder="Nouveau texte affiché">
|
||||
|
||||
<label id="nf-action-element-toggle-row" style="display:flex; align-items:center; gap:6px; margin-top:10px; font-size:12px; color:var(--text-dim);">
|
||||
<input type="checkbox" id="nf-action-element-toggle" onchange="toggleFlowElementToggleMode()" style="width:auto; margin:0;">
|
||||
Va-et-vient : au 1er clic, passer à cette valeur ; au 2e clic, revenir à la précédente ; etc.
|
||||
</label>
|
||||
<div id="nf-action-element-value2-wrap" style="display:none; margin-top:6px;">
|
||||
<input type="color" id="nf-action-element-value2-color" style="display:none;" value="#ff0000">
|
||||
<input type="number" id="nf-action-element-value2-number" style="display:none;" placeholder="Valeur au 2e état en %">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="nf-action-wait-fields" style="display:none;">
|
||||
<label>Délai en secondes avant de continuer
|
||||
<input type="number" id="nf-action-wait-seconds" min="0" step="0.5" value="3" placeholder="ex. 3">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="nf-action-data-fields" style="display:none;">
|
||||
{% if not definitions %}
|
||||
<p class="hint">Aucun objet défini dans ce jeu pour l'instant — <a href="{{ url_for('object_new', slug=game.slug) }}">crées-en un</a>.</p>
|
||||
{% else %}
|
||||
<select id="nf-action-data-definition" onchange="onFlowDefinitionChange('action')">
|
||||
{% for d in definitions %}
|
||||
<option value="{{ d.id }}">{{ d.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-data-row" style="margin-top:6px;"></select>
|
||||
<select id="nf-action-data-field" style="margin-top:6px;"></select>
|
||||
<select id="nf-action-data-operation" style="margin-top:6px;">
|
||||
{% for key, label in data_operations %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" id="nf-action-data-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="nf-action-variable-fields" style="display:none;">
|
||||
{% if not global_variables %}
|
||||
<p class="hint">Aucune variable globale pour l'instant — crées-en une depuis "Condition de visibilité" dans les propriétés d'un élément.</p>
|
||||
{% else %}
|
||||
<select id="nf-action-variable-name">
|
||||
{% for v in global_variables %}
|
||||
<option value="{{ v.name }}">{{ v.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select id="nf-action-variable-operation" style="margin-top:6px;">
|
||||
{% for key, label in data_operations %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" id="nf-action-variable-value" style="margin-top:6px;" placeholder="Valeur / montant">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actionsRow"><button type="button" class="smallBtn button is-small is-primary" onclick="submitNodeForm()">+ Ajouter ce nœud</button></div>
|
||||
</div>
|
||||
|
||||
<div class="flowCanvasWrap">
|
||||
<svg id="flowEdges" class="flowEdgesSvg"></svg>
|
||||
<div id="flowCanvas" class="flowCanvas"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Poignée de redimensionnement du panneau timeline, même principe que
|
||||
flowResizeHandle ci-dessus. -->
|
||||
<div class="flowResizeHandle" id="animResizeHandle"></div>
|
||||
|
||||
<!-- Panneau bas, rétractable, positionné sous "Logique de la scène" :
|
||||
timeline d'animation. Une piste par élément animé, des clips posés sur
|
||||
un axe du temps (secondes depuis l'affichage de l'écran) — glisse un
|
||||
clip pour changer son départ, tire son bord droit pour changer sa
|
||||
durée. Chaque clip est soit une animation de la bibliothèque
|
||||
Animate.css, soit une animation personnalisée (images-clés propres à
|
||||
ce jeu). -->
|
||||
<div class="flowPanel" id="animPanel">
|
||||
<div class="flowPanelHeader" onclick="toggleAnimPanel()">
|
||||
<span class="flowToggleIcon">▸</span> 🎬 Timeline d'animation
|
||||
</div>
|
||||
<div class="flowPanelBody" id="animPanelBody">
|
||||
<div class="flowToolbar">
|
||||
<label style="display:flex; align-items:center; gap:6px;">
|
||||
Piste
|
||||
<select id="anim-new-track-element">
|
||||
{% for e2 in elements %}
|
||||
<option value="{{ e2.id }}">{{ e2|elabel }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addAnimTrack()">➕ Ajouter un clip</button>
|
||||
</div>
|
||||
|
||||
<div id="animClipForm" class="nodeForm" style="display:none;">
|
||||
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
|
||||
<strong id="animClipFormTitle">Clip d'animation</strong>
|
||||
<button type="button" class="smallBtn button is-small" onclick="closeAnimClipForm()">✕</button>
|
||||
</div>
|
||||
|
||||
<label>Type
|
||||
<select id="anim-clip-kind" onchange="toggleAnimClipKind()">
|
||||
<option value="animate_css">Animation Animate.css</option>
|
||||
<option value="custom">Animation personnalisée — images-clés</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div id="anim-clip-animate-css-fields">
|
||||
<label>Animation
|
||||
<select id="anim-clip-animate-name">
|
||||
{% for category, items in animate_css_catalog %}
|
||||
<optgroup label="{{ category }}">
|
||||
{% for key, label in items %}
|
||||
<option value="{{ key }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="anim-clip-custom-fields" style="display:none;">
|
||||
<div id="anim-clip-keyframes"></div>
|
||||
<button type="button" class="smallBtn button is-small" onclick="addAnimKeyframeRow()">+ Ajouter une image-clé</button>
|
||||
</div>
|
||||
|
||||
<div class="fieldRow">
|
||||
<label style="flex:1;">Départ en secondes
|
||||
<input type="number" id="anim-clip-start" min="0" step="0.1" value="0">
|
||||
</label>
|
||||
<label style="flex:1;">Durée en secondes
|
||||
<input type="number" id="anim-clip-duration" min="0.1" step="0.1" value="1">
|
||||
</label>
|
||||
</div>
|
||||
<div class="fieldRow">
|
||||
<label style="flex:1;">Délai en secondes
|
||||
<input type="number" id="anim-clip-delay" min="0" step="0.1" value="0">
|
||||
</label>
|
||||
<label style="flex:1;">Vitesse
|
||||
<select id="anim-clip-easing">
|
||||
<option value="ease">Progressive</option>
|
||||
<option value="linear">Constante</option>
|
||||
<option value="ease-in">Accélère</option>
|
||||
<option value="ease-out">Ralentit</option>
|
||||
<option value="ease-in-out">Accélère puis ralentit</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label>Répétitions
|
||||
<select id="anim-clip-iteration">
|
||||
<option value="1">Une fois</option>
|
||||
<option value="2">2 fois</option>
|
||||
<option value="3">3 fois</option>
|
||||
<option value="infinite">En boucle</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="actionsRow">
|
||||
<button type="button" class="smallBtn button is-small is-primary" onclick="saveAnimClipForm()">Enregistrer</button>
|
||||
<button type="button" class="smallBtn button is-small" onclick="previewAnimClip()">▶ Aperçu</button>
|
||||
<button type="button" class="smallBtn button is-small is-danger" onclick="deleteAnimClipForm()">🗑️ Supprimer</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="animTimelineWrap">
|
||||
<div id="animRuler" class="animRuler"></div>
|
||||
<div id="animTracks" class="animTracks"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function saveGeometry(id, x, y, width, height) {
|
||||
@@ -2251,58 +2240,24 @@ function submitNodeForm() {
|
||||
}
|
||||
|
||||
// Panneau de logique de scène absent sur un écran-modèle (élément de jeu) —
|
||||
// voir le commentaire Jinja au-dessus de la div flowPanel.
|
||||
var HAS_FLOW_PANEL = !!document.getElementById('flowPanel');
|
||||
// voir le commentaire Jinja au-dessus de .builderTabs.
|
||||
var HAS_FLOW_PANEL = !!document.getElementById('flowTabPanel');
|
||||
if (HAS_FLOW_PANEL) { renderFlow(); }
|
||||
|
||||
// ---------- Ouverture / fermeture et redimensionnement du panneau du bas ----------
|
||||
|
||||
var FLOW_PANEL_DEFAULT_HEIGHT = 340;
|
||||
var flowPanelLastHeight = FLOW_PANEL_DEFAULT_HEIGHT;
|
||||
|
||||
function toggleFlowPanel() {
|
||||
var panel = document.getElementById('flowPanel');
|
||||
var handle = document.getElementById('flowResizeHandle');
|
||||
if (!panel || !handle) return;
|
||||
var isOpen = panel.classList.contains('open');
|
||||
if (isOpen) {
|
||||
flowPanelLastHeight = panel.getBoundingClientRect().height;
|
||||
panel.classList.remove('open');
|
||||
panel.style.height = '';
|
||||
handle.classList.remove('visible');
|
||||
} else {
|
||||
panel.classList.add('open');
|
||||
panel.style.height = flowPanelLastHeight + 'px';
|
||||
handle.classList.add('visible');
|
||||
requestAnimationFrame(drawFlowEdges);
|
||||
}
|
||||
}
|
||||
|
||||
if (HAS_FLOW_PANEL) (function(){
|
||||
var handle = document.getElementById('flowResizeHandle');
|
||||
var panel = document.getElementById('flowPanel');
|
||||
handle.addEventListener('mousedown', function(e){
|
||||
e.preventDefault();
|
||||
if (!panel.classList.contains('open')) return;
|
||||
var startY = e.clientY;
|
||||
var startHeight = panel.getBoundingClientRect().height;
|
||||
var wrap = document.querySelector('.content-builder');
|
||||
var maxHeight = Math.max(160, wrap.getBoundingClientRect().height - 160);
|
||||
function onMove(ev) {
|
||||
var dy = ev.clientY - startY;
|
||||
var newHeight = Math.min(maxHeight, Math.max(140, startHeight - dy));
|
||||
panel.style.height = newHeight + 'px';
|
||||
flowPanelLastHeight = newHeight;
|
||||
drawFlowEdges();
|
||||
}
|
||||
function onUp() {
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
// ---------- Onglets du centre : Écran / Logique de la scène / Timeline
|
||||
// d'animation — un seul visible à la fois, chacun occupant tout l'espace
|
||||
// disponible (remplace l'ancien panneau du bas rétractable/redimensionnable
|
||||
// à la souris : un onglet "prend toute la place" par nature, et en changer
|
||||
// équivaut à "fermer" celui qu'on quitte). ----------
|
||||
function switchBuilderTab(tab) {
|
||||
document.querySelectorAll('.builderTabBtn').forEach(function (btn) {
|
||||
btn.classList.toggle('active', btn.dataset.tab === tab);
|
||||
});
|
||||
})();
|
||||
document.querySelectorAll('.builderTabPanel').forEach(function (panel) {
|
||||
panel.classList.toggle('active', panel.dataset.tabPanel === tab);
|
||||
});
|
||||
if (tab === 'flow') requestAnimationFrame(drawFlowEdges);
|
||||
}
|
||||
|
||||
// ---------- Timeline d'animation : piste par élément, clips Animate.css ou
|
||||
// personnalisés (images-clés), glissés/redimensionnés à la souris sur un
|
||||
@@ -2617,53 +2572,8 @@ function previewAnimClip() {
|
||||
}
|
||||
|
||||
// Panneau timeline absent sur un écran-modèle (élément de jeu), comme le
|
||||
// panneau de logique — voir le commentaire Jinja au-dessus de la div animPanel.
|
||||
var HAS_ANIM_PANEL = !!document.getElementById('animPanel');
|
||||
// panneau de logique — voir le commentaire Jinja au-dessus de .builderTabs.
|
||||
var HAS_ANIM_PANEL = !!document.getElementById('animTabPanel');
|
||||
if (HAS_ANIM_PANEL) { renderAnimTimeline(); }
|
||||
|
||||
var ANIM_PANEL_DEFAULT_HEIGHT = 260;
|
||||
var animPanelLastHeight = ANIM_PANEL_DEFAULT_HEIGHT;
|
||||
|
||||
function toggleAnimPanel() {
|
||||
var panel = document.getElementById('animPanel');
|
||||
var handle = document.getElementById('animResizeHandle');
|
||||
if (!panel || !handle) return;
|
||||
var isOpen = panel.classList.contains('open');
|
||||
if (isOpen) {
|
||||
animPanelLastHeight = panel.getBoundingClientRect().height;
|
||||
panel.classList.remove('open');
|
||||
panel.style.height = '';
|
||||
handle.classList.remove('visible');
|
||||
} else {
|
||||
panel.classList.add('open');
|
||||
panel.style.height = animPanelLastHeight + 'px';
|
||||
handle.classList.add('visible');
|
||||
}
|
||||
}
|
||||
|
||||
if (HAS_ANIM_PANEL) (function() {
|
||||
var handle = document.getElementById('animResizeHandle');
|
||||
var panel = document.getElementById('animPanel');
|
||||
handle.addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
if (!panel.classList.contains('open')) return;
|
||||
var startY = e.clientY;
|
||||
var startHeight = panel.getBoundingClientRect().height;
|
||||
var wrap = document.querySelector('.content-builder');
|
||||
var maxHeight = Math.max(160, wrap.getBoundingClientRect().height - 160);
|
||||
function onMove(ev) {
|
||||
var dy = ev.clientY - startY;
|
||||
var newHeight = Math.min(maxHeight, Math.max(140, startHeight - dy));
|
||||
panel.style.height = newHeight + 'px';
|
||||
animPanelLastHeight = newHeight;
|
||||
}
|
||||
function onUp() {
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -89,7 +89,7 @@ def test_clip_delete_removes_it(client, game):
|
||||
def test_screen_edit_exposes_timeline_panel_and_animate_css_cdn(client, game):
|
||||
screen_id = _create_screen(client, game)
|
||||
html = client.get(f"/game/{game}/screens/{screen_id}/edit").data.decode()
|
||||
assert "animPanel" in html
|
||||
assert "animTabPanel" in html
|
||||
assert "Timeline d'animation" in html
|
||||
assert "animate.css" in html
|
||||
|
||||
|
||||
@@ -20,10 +20,13 @@ def _create_onglets(client, slug, screen_id):
|
||||
|
||||
def _tab_rename_ids(html):
|
||||
"""Ids des formulaires de renommage DANS le bloc "Onglets" du panneau de
|
||||
propriétés — pas le premier /elements/<id>/rename de la page, qui est le
|
||||
champ "Nommer" générique de l'élément actuellement sélectionné (pas
|
||||
forcément un onglet, ex. l'élément Onglets lui-même)."""
|
||||
block = html.split("Onglets (", 1)[-1]
|
||||
propriétés — pas le premier /elements/<id>/rename de la page (le champ
|
||||
"Nommer" générique de l'élément actuellement sélectionné), ni une
|
||||
occurrence de "Onglets (" dans une liste déroulante d'éléments de la
|
||||
Logique de la scène (ex. "<option>Onglets (elt-1)</option>"), qui existe
|
||||
aussi sur la page — seul l'en-tête du groupe de réglages ("<summary>🗂️
|
||||
Onglets (") repère sans ambiguïté le bloc de propriétés lui-même."""
|
||||
block = html.split(">🗂️ Onglets (", 1)[-1]
|
||||
return [int(x) for x in re.findall(r"/elements/(\d+)/rename", block)]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user