Dev #1

Merged
w-vandal merged 62 commits from dev into main 2026-08-26 04:56:44 +00:00
2 changed files with 93 additions and 68 deletions
Showing only changes of commit 850242d1d4 - Show all commits
+28 -24
View File
@@ -150,33 +150,37 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
toutes les trois occupent exactement la même hauteur (celle qui reste
disponible), avec leur propre défilement interne si leur contenu dépasse. */
.builder3{ display:flex; gap:20px; align-items:stretch; margin-top:14px; flex:1 1 auto; min-height:0; }
.builderPanel{ flex:0 0 260px; min-width:0; height:100%; overflow-y:auto; }
.builderCanvasArea{ flex:1 1 auto; min-width:0; height:100%; overflow-y:auto; display:flex; flex-direction:column; }
/* ---------- Panneau de propriétés flottant ---------- */
/* Sorti du flux flex de .builder3 (position:fixed) : .builderCanvasArea
(flex:1 1 auto) récupère automatiquement l'espace libéré, sans réglage
supplémentaire — un panneau flottant repositionné par l'utilisateur
n'agrandit/ne rétrécit donc jamais le canevas de travers. Position et
état ouvert/fermé mémorisés par écran (voir restorePropsPanelState dans
screen_edit.html) pour survivre à un changement de sélection ou un
rechargement. */
.builderPanelRight{
position:fixed; top:90px; right:24px; width:340px; max-height:calc(100vh - 120px);
/* ---------- Panneaux gauche/droite flottants ---------- */
/* Les deux panneaux (éléments de l'écran / propriétés) sont sortis du flux
flex de .builder3 (position:fixed) : .builderCanvasArea (flex:1 1 auto)
récupère automatiquement tout l'espace libéré, sans réglage
supplémentaire — les fermer donne donc une prévisualisation aussi large
que possible, aussi fidèle que possible à ce que "Jouer" affiche
réellement. Position et état ouvert/fermé mémorisés par écran (voir
floatPanel*() dans screen_edit.html) pour survivre à un changement de
sélection (qui remplace tout #builder3) ou un rechargement. */
.floatPanel{
position:fixed; top:90px; max-height:calc(100vh - 120px);
min-width:0; display:flex; flex-direction:column; z-index:60;
background:var(--panel); border:1px solid var(--border); border-radius:10px;
box-shadow:0 12px 40px rgba(0,0,0,.55);
}
.builderPanelRight.hidden{ display:none; }
.propsPanelHeader{
.floatPanel.hidden{ display:none; }
.floatPanel--left{ left:24px; width:280px; }
.floatPanel--right{ right:24px; width:340px; }
.floatPanelHeader{
display:flex; align-items:center; justify-content:space-between; gap:8px;
padding:8px 10px; border-bottom:1px solid var(--border); background:var(--panel2);
border-radius:10px 10px 0 0; cursor:move; flex:0 0 auto; user-select:none;
}
.propsPanelHeader .propsPanelTitle{ font-size:11px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em; }
.propsPanelBody{ padding:12px; overflow-y:auto; min-height:0; flex:1 1 auto; }
#propsPanelReopenBtn{ position:fixed; top:90px; right:24px; z-index:60; display:none; }
#propsPanelReopenBtn.visible{ display:inline-flex; }
.floatPanelHeader .floatPanelTitle{ font-size:11px; color:var(--text-dim); text-transform:uppercase; letter-spacing:.05em; }
.floatPanelBody{ padding:12px; overflow-y:auto; min-height:0; flex:1 1 auto; }
.floatPanelReopenBtn{ position:fixed; top:90px; z-index:60; display:none; }
.floatPanelReopenBtn.visible{ display:inline-flex; }
#panelLeftReopenBtn{ left:24px; }
#propsPanelReopenBtn{ right:24px; }
.widgetGrid{ display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.widgetBtnForm{ margin:0; }
@@ -314,7 +318,7 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.sliderValue{ min-width:44px; text-align:right; }
input[type=color]{ width:52px; height:34px; padding:2px; }
.propsPanel .sectionDivider, .builderPanelRight .sectionDivider{ border:none; border-top:1px solid var(--border); margin:14px 0; }
.propsPanel .sectionDivider, .floatPanel .sectionDivider{ border:none; border-top:1px solid var(--border); margin:14px 0; }
.actionAddForm{ margin-top:10px; }
.uploadRow{ margin-top:6px; }
@@ -330,13 +334,13 @@ code{ background:var(--panel2); border:1px solid var(--border); border-radius:4p
body.builderBody{ height:auto; overflow:visible; }
body.builderBody > main.content{ overflow:visible; }
.builder3{ flex-direction:column; flex:none; }
.builderPanel, .builderCanvasArea{ flex:none; width:100%; height:auto; max-height:none; overflow:visible; }
.builderCanvasArea{ flex:none; width:100%; height:auto; max-height:none; overflow:visible; }
/* Trop peu de largeur pour qu'un panneau flottant ait un sens (il
couvrirait tout l'écran) : il rejoint la pile normale comme les deux
autres colonnes, redimensionnement/déplacement désactivés (voir le
garde useFloatingPanel() dans screen_edit.html). */
.builderPanelRight{ position:static; flex:none; width:100%; height:auto; max-height:none; box-shadow:none; top:auto; right:auto; }
#propsPanelReopenBtn{ display:none !important; }
couvrirait tout l'écran) : les deux rejoignent la pile normale et
restent toujours ouverts/visibles, quel que soit l'état mémorisé —
.hidden est ignoré grâce au "!important" sur "display" ci-dessous. */
.floatPanel{ position:static; flex:none; width:100%; height:auto; max-height:none; box-shadow:none; top:auto; left:auto; right:auto; display:flex !important; }
.floatPanelReopenBtn{ display:none !important; }
}
/* ---------- Logique de la scène : éditeur de flow à nœuds ---------- */
+65 -44
View File
@@ -57,8 +57,16 @@
<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">
<!-- Panneau gauche : ajouter un élément (icônes, sans jargon technique) —
flottant, déplaçable, fermable, même mécanisme que le panneau droit
(voir floatPanel*() plus bas). -->
<button type="button" id="panelLeftReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openLeftPanel()" title="Rouvrir le panneau des éléments">🧩 Éléments</button>
<div class="floatPanel floatPanel--left" id="panelLeftFloat">
<div class="floatPanelHeader" data-drag-panel="panelLeftFloat" data-drag-key="left">
<span class="floatPanelTitle">⋮⋮ Éléments de cet écran</span>
<button type="button" class="button is-small" onclick="closeLeftPanel()" title="Fermer">✕</button>
</div>
<div class="floatPanelBody">
<h2 class="subtitle is-6">Éléments de cet écran</h2>
{% if top_level_elements %}
<div class="elementTree">
@@ -112,6 +120,7 @@
{% endif %}
</div>
</details>
</div>
</div>
<!-- Menu contextuel (clic droit sur un élément de l'arborescence) -->
@@ -144,14 +153,14 @@
<!-- Panneau droit : propriétés de l'élément sélectionné — flottant,
déplaçable à la souris par son en-tête, fermable pour agrandir le
canevas (voir bindPropsPanelFloating() plus bas). -->
<button type="button" id="propsPanelReopenBtn" class="button is-small" onclick="openPropsPanel()" title="Rouvrir le panneau de propriétés">⚙️ Propriétés</button>
<div class="builderPanel builderPanelRight" id="propsPanelFloat">
<div class="propsPanelHeader" id="propsPanelDragHandle">
<span class="propsPanelTitle">⋮⋮ Propriétés</span>
canevas (voir floatPanel*() plus bas). -->
<button type="button" id="propsPanelReopenBtn" class="button is-small floatPanelReopenBtn" onclick="openPropsPanel()" title="Rouvrir le panneau de propriétés">⚙️ Propriétés</button>
<div class="floatPanel floatPanel--right" id="propsPanelFloat">
<div class="floatPanelHeader" data-drag-panel="propsPanelFloat" data-drag-key="props">
<span class="floatPanelTitle">⋮⋮ Propriétés</span>
<button type="button" class="button is-small" onclick="closePropsPanel()" title="Fermer">✕</button>
</div>
<div class="propsPanelBody">
<div class="floatPanelBody">
{% if not selected_id %}
<div class="hint" style="padding:20px 0;">Sélectionne un élément (sur l'écran ou dans la liste) pour voir et modifier ses propriétés ici.</div>
{% else %}
@@ -738,35 +747,38 @@ function restoreTreeCollapsedState() {
});
}
// ---------- Panneau de propriétés : flottant, déplaçable, fermable ----------
// ---------- Panneaux gauche/droite : flottants, déplaçables, fermables ----------
//
// Sorti du flux (position:fixed en CSS) pour ne jamais rétrécir le canevas
// selon où on le pose — le fermer libère toute cette largeur pour une
// Sortis du flux (position:fixed en CSS) pour ne jamais rétrécir le canevas
// selon où on les pose — les fermer libère cette largeur pour une
// prévisualisation plus fidèle à ce que "Jouer" affiche réellement (voir
// bug remonté : un panneau trop large réduisait le canevas au point de
// casser la mise en page des onglets). Position et état ouvert/fermé
// mémorisés par écran pour survivre à un changement de sélection (qui
// remplace tout #builder3, voir refreshBuilderPanel) ou un rechargement.
function propsPanelStateKey() {
return 'forgePropsPanel:' + GAME_SLUG + ':' + SCREEN_ID;
// bug remonté : des panneaux trop larges réduisaient le canevas au point de
// casser la mise en page des onglets). Un seul jeu de fonctions génériques
// (key = identifiant court mémorisé dans localStorage, distinct par
// panneau) plutôt qu'une copie par panneau. Position et état ouvert/fermé
// survivent à un changement de sélection (qui remplace tout #builder3, voir
// refreshBuilderPanel) ou un rechargement — voir les appels à
// restoreFloatPanelState() dans initBuilderPanel().
function floatPanelStateKey(key) {
return 'forgeFloatPanel:' + GAME_SLUG + ':' + SCREEN_ID + ':' + key;
}
function savePropsPanelState(state) {
function saveFloatPanelState(key, state) {
try {
var current = JSON.parse(localStorage.getItem(propsPanelStateKey()) || '{}');
localStorage.setItem(propsPanelStateKey(), JSON.stringify(Object.assign(current, state)));
var current = JSON.parse(localStorage.getItem(floatPanelStateKey(key)) || '{}');
localStorage.setItem(floatPanelStateKey(key), JSON.stringify(Object.assign(current, state)));
} catch (e) {}
}
function loadPropsPanelState() {
try { return JSON.parse(localStorage.getItem(propsPanelStateKey()) || '{}'); } catch (e) { return {}; }
function loadFloatPanelState(key) {
try { return JSON.parse(localStorage.getItem(floatPanelStateKey(key)) || '{}'); } catch (e) { return {}; }
}
function restorePropsPanelState() {
var panel = document.getElementById('propsPanelFloat');
var reopenBtn = document.getElementById('propsPanelReopenBtn');
function restoreFloatPanelState(key, panelId, reopenBtnId) {
var panel = document.getElementById(panelId);
var reopenBtn = document.getElementById(reopenBtnId);
if (!panel) return;
var state = loadPropsPanelState();
var state = loadFloatPanelState(key);
if (state.left != null && state.top != null) {
panel.style.left = state.left + 'px';
panel.style.top = state.top + 'px';
@@ -777,27 +789,35 @@ function restorePropsPanelState() {
if (reopenBtn) reopenBtn.classList.toggle('visible', !open);
}
function closePropsPanel() {
var panel = document.getElementById('propsPanelFloat');
var reopenBtn = document.getElementById('propsPanelReopenBtn');
function closeFloatPanel(key, panelId, reopenBtnId) {
var panel = document.getElementById(panelId);
var reopenBtn = document.getElementById(reopenBtnId);
if (panel) panel.classList.add('hidden');
if (reopenBtn) reopenBtn.classList.add('visible');
savePropsPanelState({ open: false });
saveFloatPanelState(key, { open: false });
}
function openPropsPanel() {
var panel = document.getElementById('propsPanelFloat');
var reopenBtn = document.getElementById('propsPanelReopenBtn');
function openFloatPanel(key, panelId, reopenBtnId) {
var panel = document.getElementById(panelId);
var reopenBtn = document.getElementById(reopenBtnId);
if (panel) panel.classList.remove('hidden');
if (reopenBtn) reopenBtn.classList.remove('visible');
savePropsPanelState({ open: true });
saveFloatPanelState(key, { open: true });
}
if (!window.__forgePropsPanelDragBound) {
window.__forgePropsPanelDragBound = true;
// Raccourcis utilisés directement par les attributs onclick du gabarit.
function closePropsPanel() { closeFloatPanel('props', 'propsPanelFloat', 'propsPanelReopenBtn'); }
function openPropsPanel() { openFloatPanel('props', 'propsPanelFloat', 'propsPanelReopenBtn'); }
function closeLeftPanel() { closeFloatPanel('left', 'panelLeftFloat', 'panelLeftReopenBtn'); }
function openLeftPanel() { openFloatPanel('left', 'panelLeftFloat', 'panelLeftReopenBtn'); }
if (!window.__forgeFloatPanelDragBound) {
window.__forgeFloatPanelDragBound = true;
document.addEventListener('mousedown', function (e) {
if (!e.target.closest('#propsPanelDragHandle') || e.target.closest('button')) return;
var panel = document.getElementById('propsPanelFloat');
var handle = e.target.closest('.floatPanelHeader');
if (!handle || e.target.closest('button')) return;
var panel = document.getElementById(handle.dataset.dragPanel);
var key = handle.dataset.dragKey;
if (!panel) return;
var rect = panel.getBoundingClientRect();
var startX = e.clientX, startY = e.clientY;
@@ -814,7 +834,7 @@ if (!window.__forgePropsPanelDragBound) {
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
savePropsPanelState({ left: parseFloat(panel.style.left), top: parseFloat(panel.style.top) });
saveFloatPanelState(key, { left: parseFloat(panel.style.left), top: parseFloat(panel.style.top) });
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
@@ -960,8 +980,8 @@ function refreshAfterReorder() {
curCanvas.innerHTML = newCanvas.innerHTML;
applySelectionHighlight(CURRENT_SELECTED_ID);
}
var newTree = doc.querySelector('.builderPanel .elementTree');
var curTree = document.querySelector('.builderPanel .elementTree');
var newTree = doc.querySelector('#panelLeftFloat .elementTree');
var curTree = document.querySelector('#panelLeftFloat .elementTree');
if (newTree && curTree) { curTree.innerHTML = newTree.innerHTML; }
restoreTreeCollapsedState();
});
@@ -1016,8 +1036,8 @@ 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 newTree = doc.querySelector('.builderPanel .elementTree');
var curTree = document.querySelector('.builderPanel .elementTree');
var newTree = doc.querySelector('#panelLeftFloat .elementTree');
var curTree = document.querySelector('#panelLeftFloat .elementTree');
if (newTree && curTree) { curTree.innerHTML = newTree.innerHTML; restoreTreeCollapsedState(); }
});
}
@@ -1224,7 +1244,8 @@ function initBuilderPanel() {
bindJaugeDefinitionSelect();
restoreTreeCollapsedState();
applySelectionHighlight(CURRENT_SELECTED_ID);
restorePropsPanelState();
restoreFloatPanelState('props', 'propsPanelFloat', 'propsPanelReopenBtn');
restoreFloatPanelState('left', 'panelLeftFloat', 'panelLeftReopenBtn');
}
// Jauge : quand on change l'objet de données à suivre SANS quitter le