Panneaux flottants en plein écran par défaut, avec bouton pour en sortir

Chaque panneau (Nouvel objet, Modifier un objet, Ajouter un champ,
Ajouter une entrée) s'ouvre désormais en plein écran par défaut (marge
de 12px) plutôt qu'en petite fenêtre centrée — plus confortable dès
qu'il y a plusieurs champs/entrées à voir en même temps. Un bouton ⛶
dans l'en-tête bascule vers/depuis la taille et la position précédentes
(mémorisées le temps de la session, pas persistées), pour qui préfère
un panneau plus petit à côté du reste du tableau de bord.

makeFloatPanelFullscreenable(panel, toggleBtn) — générique, posée sur
panel._fsToggle — appelée par les 4 familles de panneaux existantes.
makeFloatPanelDraggable()/makeFloatPanelResizable() sortent d'abord
proprement du plein écran si l'utilisateur interagit manuellement
(glisser l'en-tête ou tirer le coin) : sans ça, bottom/right encore
actifs en plein écran auraient repris la main sur la position/taille
qu'on vient de poser.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-08-27 13:06:58 +02:00
co-authored by Claude Sonnet 5
parent 03db681022
commit 943bf265de
2 changed files with 64 additions and 4 deletions
+59 -4
View File
@@ -221,6 +221,7 @@
<div class="floatPanel floatPanel--center hidden" id="newObjectPanel">
<div class="floatPanelHeader" id="newObjectPanelHeader">
<span class="floatPanelTitle">⋮⋮ Nouvel objet</span>
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
<button type="button" class="button is-small" onclick="closeNewObjectPanel()">✕</button>
</div>
<div class="floatPanelBody">
@@ -261,6 +262,7 @@
<div class="floatPanel floatPanel--center hidden objectEditPanel" data-definition-id="{{ d.id }}" id="objectEditPanel-{{ d.id }}">
<div class="floatPanelHeader">
<span class="floatPanelTitle">⋮⋮ {{ definition.name }}</span>
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
<button type="button" class="button is-small" onclick="closeObjectEditPanel({{ d.id }})">✕</button>
</div>
<div class="floatPanelBody">
@@ -458,6 +460,7 @@
<div class="floatPanel floatPanel--center hidden addFieldPanel" data-definition-id="{{ d.id }}" id="addFieldPanel-{{ d.id }}">
<div class="floatPanelHeader">
<span class="floatPanelTitle">⋮⋮ Ajouter un champ — {{ definition.name }}</span>
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
<button type="button" class="button is-small" onclick="closeAddFieldPanel({{ d.id }})">✕</button>
</div>
<div class="floatPanelBody">
@@ -509,6 +512,7 @@
<div class="floatPanel floatPanel--center hidden addEntryPanel" data-definition-id="{{ d.id }}" id="addEntryPanel-{{ d.id }}">
<div class="floatPanelHeader">
<span class="floatPanelTitle">⋮⋮ Ajouter une entrée — {{ definition.name }}</span>
<button type="button" class="button is-small floatPanelFullscreenBtn" title="Plein écran">⛶</button>
<button type="button" class="button is-small" onclick="closeAddEntryPanel({{ d.id }})">✕</button>
</div>
<div class="floatPanelBody">
@@ -628,10 +632,15 @@ function makeFloatPanelDraggable(panel, header) {
header.addEventListener('mousedown', function (e) {
if (e.target.closest('button')) return;
dragging = true;
// Glisser un panneau en plein écran doit d'abord l'en faire sortir —
// sinon left/top posés juste en dessous seraient aussitôt repris en
// main par bottom/right encore actifs (voir makeFloatPanelFullscreenable).
if (panel.classList.contains('floatPanel--fullscreen')) panel._fsToggle.exit();
var rect = panel.getBoundingClientRect();
panel.style.left = rect.left + 'px';
panel.style.top = rect.top + 'px';
panel.style.right = 'auto';
panel.style.bottom = 'auto';
panel.style.transform = 'none';
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
@@ -651,6 +660,10 @@ function makeFloatPanelResizable(panel, handle) {
var resizing = false, startX = 0, startY = 0, startW = 0, startH = 0;
handle.addEventListener('mousedown', function (e) {
resizing = true;
// Même remarque que pour le glisser-déposer : sortir du plein écran
// d'abord, sinon bottom/right encore actifs reprendraient la main sur
// la largeur/hauteur posées ci-dessous.
if (panel.classList.contains('floatPanel--fullscreen')) panel._fsToggle.exit();
var rect = panel.getBoundingClientRect();
startX = e.clientX; startY = e.clientY;
startW = rect.width; startH = rect.height;
@@ -673,13 +686,50 @@ function makeFloatPanelResizable(panel, handle) {
document.body.style.userSelect = '';
});
}
// Plein écran (voir switchDashTab pour l'esprit général : chaque panneau
// s'ouvre en plein écran par défaut — plus pratique dès qu'il y a plus de
// quelques champs — avec un bouton pour revenir à sa taille/position
// précédente, mémorisée le temps de la session (pas persistée). Renvoie un
// objet {enter, exit} et pose panel._fsToggle dessus, pour que le code qui
// OUVRE le panneau (openXxxPanel ci-dessous) puisse appeler enter() sans
// avoir à re-chercher le bouton.
function makeFloatPanelFullscreenable(panel, toggleBtn) {
var saved = null;
function enter() {
saved = {
left: panel.style.left, top: panel.style.top, right: panel.style.right,
width: panel.style.width, height: panel.style.height,
maxWidth: panel.style.maxWidth, transform: panel.style.transform,
};
panel.classList.add('floatPanel--fullscreen');
panel.style.left = '12px'; panel.style.top = '12px'; panel.style.right = '12px'; panel.style.bottom = '12px';
panel.style.width = 'auto'; panel.style.height = 'auto'; panel.style.maxWidth = 'none'; panel.style.transform = 'none';
toggleBtn.title = 'Quitter le plein écran';
}
function exit() {
panel.classList.remove('floatPanel--fullscreen');
panel.style.bottom = '';
if (saved) {
panel.style.left = saved.left; panel.style.top = saved.top; panel.style.right = saved.right;
panel.style.width = saved.width; panel.style.height = saved.height;
panel.style.maxWidth = saved.maxWidth; panel.style.transform = saved.transform;
}
toggleBtn.title = 'Plein écran';
}
toggleBtn.addEventListener('click', function () {
if (panel.classList.contains('floatPanel--fullscreen')) exit(); else enter();
});
panel._fsToggle = { enter: enter, exit: exit };
return panel._fsToggle;
}
// ---------- Panneau déplaçable "Nouvel objet" ----------
(function () {
var panel = document.getElementById('newObjectPanel');
makeFloatPanelDraggable(panel, document.getElementById('newObjectPanelHeader'));
makeFloatPanelResizable(panel, document.getElementById('newObjectPanelResizeHandle'));
window.openNewObjectPanel = function () { panel.classList.remove('hidden'); };
makeFloatPanelFullscreenable(panel, panel.querySelector('.floatPanelFullscreenBtn'));
window.openNewObjectPanel = function () { panel.classList.remove('hidden'); panel._fsToggle.enter(); };
window.closeNewObjectPanel = function () { panel.classList.add('hidden'); };
})();
@@ -688,10 +738,13 @@ function makeFloatPanelResizable(panel, handle) {
document.querySelectorAll('.objectEditPanel').forEach(function (panel) {
makeFloatPanelDraggable(panel, panel.querySelector('.floatPanelHeader'));
makeFloatPanelResizable(panel, panel.querySelector('.floatPanelResizeHandle'));
makeFloatPanelFullscreenable(panel, panel.querySelector('.floatPanelFullscreenBtn'));
});
window.openObjectEditPanel = function (definitionId, subtab) {
document.querySelectorAll('.objectEditPanel').forEach(function (panel) {
panel.classList.toggle('hidden', panel.dataset.definitionId !== String(definitionId));
var isTarget = panel.dataset.definitionId === String(definitionId);
panel.classList.toggle('hidden', !isTarget);
if (isTarget) panel._fsToggle.enter();
});
if (subtab) {
var panel = document.querySelector('.objectEditPanel[data-definition-id="' + definitionId + '"]');
@@ -725,6 +778,7 @@ function makeFloatPanelResizable(panel, handle) {
document.querySelectorAll('.addFieldPanel').forEach(function (panel) {
makeFloatPanelDraggable(panel, panel.querySelector('.floatPanelHeader'));
makeFloatPanelResizable(panel, panel.querySelector('.floatPanelResizeHandle'));
makeFloatPanelFullscreenable(panel, panel.querySelector('.floatPanelFullscreenBtn'));
var typeSelect = panel.querySelector('.fieldTypeSelect');
var relationTarget = panel.querySelector('.relationTarget');
@@ -739,11 +793,12 @@ function makeFloatPanelResizable(panel, handle) {
document.querySelectorAll('.addEntryPanel').forEach(function (panel) {
makeFloatPanelDraggable(panel, panel.querySelector('.floatPanelHeader'));
makeFloatPanelResizable(panel, panel.querySelector('.floatPanelResizeHandle'));
makeFloatPanelFullscreenable(panel, panel.querySelector('.floatPanelFullscreenBtn'));
});
window.openAddFieldPanel = function (definitionId) {
var panel = document.querySelector('.addFieldPanel[data-definition-id="' + definitionId + '"]');
if (panel) panel.classList.remove('hidden');
if (panel) { panel.classList.remove('hidden'); panel._fsToggle.enter(); }
};
window.closeAddFieldPanel = function (definitionId) {
var panel = document.querySelector('.addFieldPanel[data-definition-id="' + definitionId + '"]');
@@ -751,7 +806,7 @@ function makeFloatPanelResizable(panel, handle) {
};
window.openAddEntryPanel = function (definitionId) {
var panel = document.querySelector('.addEntryPanel[data-definition-id="' + definitionId + '"]');
if (panel) panel.classList.remove('hidden');
if (panel) { panel.classList.remove('hidden'); panel._fsToggle.enter(); }
};
window.closeAddEntryPanel = function (definitionId) {
var panel = document.querySelector('.addEntryPanel[data-definition-id="' + definitionId + '"]');