Make the screen editor's properties panel floating, draggable, and closable

The right-hand properties panel was a fixed 320px flex column, permanently shrinking the canvas — on a screen with real content (tabs, gauges), this made the editor's preview visibly narrower than the actual /play rendering, to the point of wrapping tab labels differently. It's now position:fixed with a drag handle header, moved out of .builder3's flex flow so .builderCanvasArea automatically reclaims the space; closing it (✕, replaced by a small "⚙️ Propriétés" reopen button) frees the full width for a more faithful preview. Position and open/closed state persist per screen in localStorage and get reapplied after every selection change (which replaces #builder3 wholesale). Falls back to a normal stacked column below 1300px width, where a floating panel wouldn't fit usefully.
This commit is contained in:
william
2026-08-24 15:12:50 +02:00
parent a7518355ed
commit 03ca2723c5
2 changed files with 127 additions and 4 deletions
+95 -2
View File
@@ -142,8 +142,16 @@
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner.</p>
</div>
<!-- Panneau droit : propriétés de l'élément sélectionné -->
<div class="builderPanel builderPanelRight">
<!-- 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>
<button type="button" class="button is-small" onclick="closePropsPanel()" title="Fermer">✕</button>
</div>
<div class="propsPanelBody">
{% 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 %}
@@ -370,6 +378,7 @@
{% endif %}
{% endif %}
</div>
</div>
</div>
@@ -729,6 +738,89 @@ function restoreTreeCollapsedState() {
});
}
// ---------- Panneau de propriétés : flottant, déplaçable, fermable ----------
//
// 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
// 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;
}
function savePropsPanelState(state) {
try {
var current = JSON.parse(localStorage.getItem(propsPanelStateKey()) || '{}');
localStorage.setItem(propsPanelStateKey(), JSON.stringify(Object.assign(current, state)));
} catch (e) {}
}
function loadPropsPanelState() {
try { return JSON.parse(localStorage.getItem(propsPanelStateKey()) || '{}'); } catch (e) { return {}; }
}
function restorePropsPanelState() {
var panel = document.getElementById('propsPanelFloat');
var reopenBtn = document.getElementById('propsPanelReopenBtn');
if (!panel) return;
var state = loadPropsPanelState();
if (state.left != null && state.top != null) {
panel.style.left = state.left + 'px';
panel.style.top = state.top + 'px';
panel.style.right = 'auto';
}
var open = state.open !== false; // ouvert par défaut tant que rien n'a été fermé
panel.classList.toggle('hidden', !open);
if (reopenBtn) reopenBtn.classList.toggle('visible', !open);
}
function closePropsPanel() {
var panel = document.getElementById('propsPanelFloat');
var reopenBtn = document.getElementById('propsPanelReopenBtn');
if (panel) panel.classList.add('hidden');
if (reopenBtn) reopenBtn.classList.add('visible');
savePropsPanelState({ open: false });
}
function openPropsPanel() {
var panel = document.getElementById('propsPanelFloat');
var reopenBtn = document.getElementById('propsPanelReopenBtn');
if (panel) panel.classList.remove('hidden');
if (reopenBtn) reopenBtn.classList.remove('visible');
savePropsPanelState({ open: true });
}
if (!window.__forgePropsPanelDragBound) {
window.__forgePropsPanelDragBound = true;
document.addEventListener('mousedown', function (e) {
if (!e.target.closest('#propsPanelDragHandle') || e.target.closest('button')) return;
var panel = document.getElementById('propsPanelFloat');
if (!panel) return;
var rect = panel.getBoundingClientRect();
var startX = e.clientX, startY = e.clientY;
var startLeft = rect.left, startTop = rect.top;
panel.style.right = 'auto';
function onMove(ev) {
var maxLeft = window.innerWidth - panel.offsetWidth - 4;
var maxTop = window.innerHeight - 40;
var left = Math.max(4, Math.min(maxLeft, startLeft + (ev.clientX - startX)));
var top = Math.max(4, Math.min(maxTop, startTop + (ev.clientY - startY)));
panel.style.left = left + 'px';
panel.style.top = top + 'px';
}
function onUp() {
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
savePropsPanelState({ left: parseFloat(panel.style.left), top: parseFloat(panel.style.top) });
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
}
// ---------- Surbrillance sur le canevas de l'élément sélectionné ----------
//
// Un élément posé DIRECTEMENT sur l'écran a déjà son cadre de sélection via
@@ -1132,6 +1224,7 @@ function initBuilderPanel() {
bindJaugeDefinitionSelect();
restoreTreeCollapsedState();
applySelectionHighlight(CURRENT_SELECTED_ID);
restorePropsPanelState();
}
// Jauge : quand on change l'objet de données à suivre SANS quitter le