Corriger la revue stage1 de la refonte : fiche de dalle, thème du fond, cadre d'export, tokens de couleur
Retour/Échap depuis la fiche détaillée ne retouche plus la hauteur du volet téléphone (openTab remplacé par un rétablissement direct de l'onglet) ; le fond OSM suit le thème sauf choix explicite (migration du localStorage et des défauts serveur en v3, plus de dark:true hérité de l'ex-webapp) et part du bon thème dès sa création (plus de flash sombre) ; l'onglet Export mémorisé ne recadre plus la vue au premier rendu (lien partagé préservé) et referme son cadre pendant que la fiche détaillée est ouverte ; la qualité affichée suit le nom réel de la dalle rendue ; un second clic referme la fiche au lieu de déplacer son contour derrière elle ; couleurs en dur restantes tokenisées (--tile-sel, --gps, .gen-log) et lisibles en thème clair ; localStorage corrompu (lidar-panel, lidar-print) n'y casse plus l'interface. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -44,6 +44,10 @@ const PREC_LEVELS = 16;
|
||||
|
||||
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).
|
||||
let BOOTSTRAPPING = true;
|
||||
|
||||
// --- thème ------------------------------------------------------------------
|
||||
// auto = suit le système ; ☀/☾ force clair ou sombre ; 3ᵉ appui : auto.
|
||||
@ -174,7 +178,10 @@ ZoomAnimTileLayer.addInitHook(function () {
|
||||
});
|
||||
|
||||
const base = new ZoomAnimTileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 19, maxNativeZoom: 19, keepBuffer: 2, className: 'base-dark',
|
||||
// Classe posée dès la création d'après le thème courant (pas un « dark » figé) :
|
||||
// sinon le fond flashe sombre en thème clair le temps que /api/map/meta réponde
|
||||
// (et pour de bon si l'appel échoue). applyLayers() la corrige ensuite.
|
||||
maxZoom: 19, maxNativeZoom: 19, keepBuffer: 2, className: themeIsDark() ? 'base-dark' : '',
|
||||
// Sinon _setView recrée un niveau en cours d'animation malgré noUpdate.
|
||||
updateWhenZooming: false,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
@ -210,7 +217,9 @@ function defaultState(meta) {
|
||||
precOpacity: clamp01(meta.default_precision_opacity, 0.6),
|
||||
base: { on: b.on !== undefined ? !!b.on : true,
|
||||
opacity: b.opacity !== undefined ? Number(b.opacity) : 0.85,
|
||||
dark: b.dark !== undefined ? !!b.dark : null },
|
||||
// null = suit le thème (choix explicite seulement si b.dark est déjà
|
||||
// un booléen posé côté serveur).
|
||||
dark: b.dark == null ? null : !!b.dark },
|
||||
};
|
||||
}
|
||||
|
||||
@ -219,10 +228,15 @@ function loadState(meta) {
|
||||
let st = def;
|
||||
const s = lsGet(LS_KEY, null);
|
||||
if (s) {
|
||||
// Ancien format (avant v3) : l'ex-webapp persistait base.dark:true à chaque
|
||||
// sauvegarde, jamais un vrai choix de l'utilisateur — ignoré ici (le fond
|
||||
// suit alors le thème) ; v3 seulement écrit un dark explicite (voir saveState).
|
||||
const sBase = Object.assign({}, s.base || {});
|
||||
if (s.v !== 3) delete sBase.dark;
|
||||
st = { main: validMain(meta, s.main) || def.main,
|
||||
mode: validMode(s.mode) || def.mode,
|
||||
precOpacity: clamp01(s.precOpacity, def.precOpacity),
|
||||
base: Object.assign({}, def.base, s.base || {}) };
|
||||
base: Object.assign({}, def.base, sBase) };
|
||||
}
|
||||
// Lien partagé : prime sur l'état local (les anciens liens &L=… de la
|
||||
// pile de couches sont ignorés, la vue s'ouvre alors sur l'état courant).
|
||||
@ -236,7 +250,9 @@ function loadState(meta) {
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
lsSet(LS_KEY, STATE);
|
||||
// v: 3 marque un format où base.dark, quand posé, est un vrai choix de
|
||||
// l'utilisateur (voir loadState) — jamais mélangé à STATE lui-même.
|
||||
lsSet(LS_KEY, Object.assign({ v: 3 }, STATE));
|
||||
}
|
||||
|
||||
// --- hash de vue ----------------------------------------------------------
|
||||
@ -488,7 +504,7 @@ function selectTileOutline(data) {
|
||||
if (!data || !data.corners) return;
|
||||
// Panneau dédié au-dessus de la pile LiDAR : dans l'overlayPane par défaut
|
||||
// (z-index 400, comme lidarStack), le contour passait sous les tuiles.
|
||||
tileSel = L.polygon(data.corners, { pane: 'tileSel', color: '#ffd24a', weight: 2,
|
||||
tileSel = L.polygon(data.corners, { pane: 'tileSel', color: cssVar('--tile-sel'), weight: 2,
|
||||
fill: false, dashArray: '6 4',
|
||||
interactive: false }).addTo(map);
|
||||
}
|
||||
@ -586,6 +602,10 @@ let tileReturnTab = null; // onglet à rouvrir après « ← Retour »
|
||||
|
||||
function showTilePopup(data, latlng) {
|
||||
lastTileQuery = latlng || lastTileQuery;
|
||||
// Un nouveau clic pendant que la fiche détaillée d'une AUTRE dalle est
|
||||
// ouverte : on la referme (sinon le contour bouge sur la carte alors que
|
||||
// la fiche affiche encore l'ancienne dalle).
|
||||
if (!el('tileView').hidden) restoreTabAfterDetails();
|
||||
if (tilePopup) tilePopup.remove();
|
||||
const html = !data
|
||||
? '<div class="tile-pop">Serveur injoignable : réessayez dans un instant.</div>'
|
||||
@ -614,6 +634,10 @@ function openTileDetails() {
|
||||
tileReturnTab = PANEL.tab;
|
||||
for (const t of ['view', 'print', 'gen', 'share']) el('tab-' + t).hidden = true;
|
||||
el('tileView').hidden = false;
|
||||
// La fiche masque l'onglet Export : son cadre et sa poignée n'ont rien à
|
||||
// faire sur la carte tant qu'elle est ouverte (restoreTabAfterDetails les
|
||||
// rétablit via panel:tab en revenant sur l'onglet Export).
|
||||
printSetOpen(false);
|
||||
if (isPhone() && PANEL.sheet === 'closed') setSheet('half');
|
||||
if (!isPhone() && PANEL.collapsed) setCollapsed(false);
|
||||
showTileInfo(data, lastTileQuery);
|
||||
@ -627,12 +651,22 @@ function hideTileView() {
|
||||
if (tileSel) { map.removeLayer(tileSel); tileSel = null; }
|
||||
}
|
||||
|
||||
// Referme la fiche détaillée et rétablit l'onglet d'origine SANS passer par
|
||||
// openTab : celui-ci retouche la hauteur du volet quand l'onglet visé est déjà
|
||||
// l'onglet actif (règle « re-toucher l'onglet actif ») — ce qui n'a pas lieu
|
||||
// d'être ici, on rouvre simplement ce qui était affiché avant.
|
||||
function restoreTabAfterDetails() {
|
||||
hideTileView();
|
||||
PANEL.tab = PANEL.want = tileReturnTab || 'view';
|
||||
panelSave();
|
||||
panelRender();
|
||||
}
|
||||
|
||||
// Ferme la bulle, sinon la fiche ; renvoie true si quelque chose a été fermé.
|
||||
function closeTileViews() {
|
||||
if (tilePopup) { tilePopup.remove(); tilePopup = null; return true; }
|
||||
if (!el('tileView').hidden) {
|
||||
hideTileView();
|
||||
openTab(tileReturnTab || 'view');
|
||||
restoreTabAfterDetails();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -1039,12 +1073,18 @@ function printSave() {
|
||||
}
|
||||
|
||||
function printLoad() {
|
||||
const s = lsGet(PRINT_LS, {});
|
||||
const raw = lsGet(PRINT_LS, {});
|
||||
// Valeur corrompue (clé partagée avec une autre appli, quota dépassé
|
||||
// pendant l'écriture…) : objet non conforme ignoré plutôt que planté.
|
||||
const s = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
||||
for (const [k, id] of [['paper', 'printPaper'], ['orient', 'printOrient'],
|
||||
['scale', 'printScale'], ['title', 'printTitle']]) {
|
||||
if (s[k] != null) el(id).value = s[k];
|
||||
if (typeof s[k] === 'string') el(id).value = s[k];
|
||||
}
|
||||
if (Array.isArray(s.center) && s.center.length === 2 &&
|
||||
s.center.every((n) => typeof n === 'number' && Number.isFinite(n))) {
|
||||
PRINT.center = L.latLng(s.center);
|
||||
}
|
||||
if (Array.isArray(s.center) && s.center.length === 2) PRINT.center = L.latLng(s.center);
|
||||
}
|
||||
|
||||
// Recadre sur le cadre en laissant libre la place du panneau (colonne à
|
||||
@ -1122,13 +1162,16 @@ 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é).
|
||||
function printSetOpen(on) {
|
||||
// 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) {
|
||||
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(true);
|
||||
printRefreshFrame(fit !== false);
|
||||
} else {
|
||||
if (PRINT.rect) PRINT.rect.remove();
|
||||
if (PRINT.handle) PRINT.handle.remove();
|
||||
@ -1174,13 +1217,25 @@ el('printTitle').addEventListener('change', printSave);
|
||||
printLoad();
|
||||
|
||||
document.addEventListener('panel:tab', (e) => {
|
||||
printSetOpen(e.detail.visible === 'print');
|
||||
printSetOpen(e.detail.visible === 'print', !BOOTSTRAPPING);
|
||||
if (e.detail.visible !== 'gen' && genDrawing) genSetDrawing(false);
|
||||
});
|
||||
|
||||
// --- panneau à onglets --------------------------------------------------------
|
||||
const PANEL_LS = 'lidar-panel';
|
||||
const PANEL = Object.assign({ tab: 'view', collapsed: false, sheet: 'half' }, lsGet(PANEL_LS, {}));
|
||||
// Ne retient que les clés connues, du bon type et (pour sheet) dans la liste
|
||||
// valide : une valeur corrompue casserait sinon le CSS (panel.dataset.sheet
|
||||
// inconnu de map.css) ou l'onglet affiché.
|
||||
function _sanitizedPanelLS() {
|
||||
const raw = lsGet(PANEL_LS, {});
|
||||
const s = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {};
|
||||
const out = {};
|
||||
if (typeof s.tab === 'string') out.tab = s.tab;
|
||||
if (typeof s.collapsed === 'boolean') out.collapsed = s.collapsed;
|
||||
if (['closed', 'half', 'full'].includes(s.sheet)) out.sheet = s.sheet;
|
||||
return out;
|
||||
}
|
||||
const PANEL = Object.assign({ tab: 'view', collapsed: false, sheet: 'half' }, _sanitizedPanelLS());
|
||||
// want = onglet choisi (mémorisé) ; tab = onglet affiché. Génération encore
|
||||
// inconnue au démarrage (api/status) : Affichage en attendant, puis l'onglet
|
||||
// choisi revient dès qu'il est proposé.
|
||||
@ -1314,6 +1369,7 @@ fetch('api/map/meta').then(r => r.json()).then(meta => {
|
||||
renderPanel();
|
||||
renderBaseRow();
|
||||
panelRender();
|
||||
BOOTSTRAPPING = false;
|
||||
renderUseCard();
|
||||
genFillViz();
|
||||
// Droits de génération : boutons affichés si le serveur peut générer
|
||||
@ -1449,7 +1505,7 @@ el('gps').addEventListener('click', () => {
|
||||
map.setView(ll, Math.max(map.getZoom(), 16));
|
||||
if (window._gpsMarker) window._gpsMarker.remove();
|
||||
window._gpsMarker = L.circleMarker(ll, {
|
||||
radius: 6, color: '#39d98a', fillColor: '#39d98a', fillOpacity: 0.9
|
||||
radius: 6, color: cssVar('--gps'), fillColor: cssVar('--gps'), fillOpacity: 0.9
|
||||
}).addTo(map);
|
||||
}, (err) => {
|
||||
btn.classList.remove('busy');
|
||||
|
||||
Reference in New Issue
Block a user