Fix round 2 : fermer le cadre d'export sous la fiche détaillée en toutes circonstances, ne pas la recadrer au retour, réarmer BOOTSTRAPPING sur échec

panelVisibleTab() renvoie null tant que la fiche détaillée est ouverte,
quel que soit ce qui redéclenche panelRender (grip, redimensionnement,
changement phoneQuery…) : le cadre d'export ne rouvre plus derrière elle.
Rétablir l'onglet Export après un retour (restoreTabAfterDetails / M5)
repose désormais le cadre sans recadrer la vue ni le recentrer
(nouveau drapeau RESTORING_TAB, distingué d'une ouverture explicite par
l'utilisateur). BOOTSTRAPPING est aussi réarmé quand /api/map/meta échoue,
pour qu'une ouverture explicite ultérieure de l'onglet recadre bien la vue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Antoine Jacquin
2026-09-27 19:21:50 +02:00
parent 72b10cd228
commit 72c50dca99
2 changed files with 72 additions and 16 deletions

View File

@ -46,8 +46,15 @@ let META = null;
let STATE = null;
// Vrai jusqu'au premier rendu du panneau au démarrage : évite qu'un onglet
// Export mémorisé recadre la vue par-dessus un lien partagé ou l'URL initiale
// (cf. printSetOpen).
// (cf. printSetOpen). Remis à faux dans le .then() ET le .catch() du chargement
// de /api/map/meta (sinon un serveur injoignable au démarrage le laisse
// bloqué à vrai pour toujours : l'onglet Export ne recadrerait plus jamais).
let BOOTSTRAPPING = true;
// Vrai le temps d'un panelRender() qui RÉTABLIT un onglet déjà connu
// (restoreTabAfterDetails, retour de la fiche détaillée) plutôt que d'en
// ouvrir un nouveau à la demande de l'utilisateur : le cadre d'export ne doit
// alors ni recadrer la vue, ni recentrer le cadre lui-même (cf. printSetOpen).
let RESTORING_TAB = false;
// --- thème ------------------------------------------------------------------
// auto = suit le système ; ☀/☾ force clair ou sombre ; 3ᵉ appui : auto.
@ -659,7 +666,12 @@ function restoreTabAfterDetails() {
hideTileView();
PANEL.tab = PANEL.want = tileReturnTab || 'view';
panelSave();
// Ce rendu RÉTABLIT l'onglet Export tel qu'il était (cadre à sa place,
// vue inchangée) : ni recadrage de la vue, ni recentrage du cadre — cf.
// printSetOpen, qui lit ce drapeau via le listener panel:tab.
RESTORING_TAB = true;
panelRender();
RESTORING_TAB = false;
}
// Ferme la bulle, sinon la fiche ; renvoie true si quelque chose a été fermé.
@ -1162,16 +1174,23 @@ function printCenterHere() {
// Cadre affiché tant que l'onglet Export PDF est visible (événement panel:tab,
// émis à chaque rendu du panneau : on ne fait rien si l'état est inchangé).
// fit : recadrer la vue sur le cadre une fois posé — faux au tout premier
// rendu (onglet Export mémorisé) pour ne pas écraser le zoom d'un lien
// partagé ou de l'URL au chargement.
function printSetOpen(on, fit) {
// explicit : vrai seulement pour une ouverture voulue par l'utilisateur
// (clic sur l'onglet, raccourci clavier) — faux pour un rendu qui RÉTABLIT
// un état déjà connu (démarrage avec onglet mémorisé, retour de la fiche
// détaillée) : dans ce cas ni recadrage de la vue sur le cadre, ni
// recentrage du cadre lui-même sur la vue (sinon un cadre posé ailleurs sur
// le terrain saute au centre de l'écran, ou le zoom d'un lien partagé est
// écrasé — cf. printRefreshFrame(fit)).
function printSetOpen(on, explicit) {
if (PRINT.on === on) return;
PRINT.on = on;
if (on) {
// cadre gardé s'il est encore dans la vue, sinon posé au centre
if (!PRINT.center || !map.getBounds().contains(PRINT.center)) PRINT.center = map.getCenter();
printRefreshFrame(fit !== false);
// Cadre recentré sur la vue seulement s'il est hors champ ET que
// l'ouverture est explicite ; sinon gardé où il était (ou posé au centre
// s'il n'a jamais existé — premier passage sans lidar-print sauvegardé).
if (!PRINT.center) PRINT.center = map.getCenter();
else if (explicit && !map.getBounds().contains(PRINT.center)) PRINT.center = map.getCenter();
printRefreshFrame(!!explicit);
} else {
if (PRINT.rect) PRINT.rect.remove();
if (PRINT.handle) PRINT.handle.remove();
@ -1217,7 +1236,7 @@ el('printTitle').addEventListener('change', printSave);
printLoad();
document.addEventListener('panel:tab', (e) => {
printSetOpen(e.detail.visible === 'print', !BOOTSTRAPPING);
printSetOpen(e.detail.visible === 'print', !BOOTSTRAPPING && !RESTORING_TAB);
if (e.detail.visible !== 'gen' && genDrawing) genSetDrawing(false);
});
@ -1249,8 +1268,14 @@ function panelTabs() {
return ['view', 'print', 'gen', 'share'].filter(t => t !== 'gen' || (GEN.available && GEN.allowed));
}
// Onglet réellement visible, ou null (panneau replié, volet fermé).
// Onglet réellement visible, ou null (panneau replié, volet fermé, ou fiche
// détaillée affichée par-dessus — auquel cas AUCUN onglet n'est à l'écran,
// pas même celui mémorisé : panel:tab doit fermer le cadre d'export s'il
// était ouvert, quel que soit ce qui a déclenché ce rendu du panneau — grip
// glissée/touchée, redimensionnement, changement phoneQuery, le
// setTimeout(panelRender, 220) d'openTab…).
function panelVisibleTab() {
if (!el('tileView').hidden) return null;
if (isPhone() ? PANEL.sheet === 'closed' : PANEL.collapsed) return null;
return PANEL.tab;
}
@ -1383,7 +1408,14 @@ fetch('api/map/meta').then(r => r.json()).then(meta => {
panelRender();
if (show) { genUpdateStatus(d); genPollNow(); }
}).catch(() => {});
}).catch(() => { toast('Serveur de tuiles injoignable.', 10000); panelRender(); });
}).catch(() => {
toast('Serveur de tuiles injoignable.', 10000);
panelRender();
// Ce rendu-là compte aussi comme le premier (échec du chargement initial) :
// sans ce reset, un onglet Export mémorisé ne recadrerait plus JAMAIS la
// vue, même après une ouverture explicite ultérieure.
BOOTSTRAPPING = false;
});
let applyingHash = false;
window.addEventListener('hashchange', () => {