Interface locale : rtg interface (voyages, sources, journées, récit, génération)

Serveur web local sans dépendance nouvelle (http.server, Jinja2, htmx et
Leaflet fournis). Écrans : Mes voyages, nouveau voyage, un voyage (journées
avec carte, rattachement des traces sans heures à un jour avec suggestion
d'après les photos, sources avec choix de dossier, infos et récit,
génération avec avancement et arrêt), réglages du site et vérification
des outils.

Chaîne : jours imposés ([dates_traces] dans voyage.toml), arrêt propre de
la compression, suivi des étapes, état structuré (site.tableau_etat),
titre du voyage repris de voyage.toml sur l'accueil.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
This commit is contained in:
FabriceandClaude Opus 5.5 committed 2026-10-09 14:07:47 +00:00
1 parent 608334b9d8
commit 06153a227e
37 files changed
+2618 -57

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
// Onglets, cartes Leaflet, fenêtre de choix de dossier, journal des traitements.
(function () {
"use strict";
// --- Onglets (#journees, #sources…) --------------------------------------
function afficherOnglet() {
const liens = document.querySelectorAll(".onglets a");
if (!liens.length) return;
const nom = (location.hash || liens[0].getAttribute("href")).slice(1);
let trouve = false;
liens.forEach(a => {
const actif = a.getAttribute("href") === "#" + nom;
a.classList.toggle("actif", actif);
trouve = trouve || actif;
});
if (!trouve) { liens[0].classList.add("actif"); }
document.querySelectorAll(".onglet").forEach(s => {
s.classList.toggle("actif", s.id === "onglet-" + (trouve ? nom : liens[0].getAttribute("href").slice(1)));
});
document.querySelectorAll(".onglet.actif .carte").forEach(cadrer);
}
window.addEventListener("hashchange", afficherOnglet);
document.addEventListener("DOMContentLoaded", afficherOnglet);
// --- Cartes -----------------------------------------------------------------
// Une carte préparée dans un onglet caché n'a pas de taille : on la recadre
// la première fois qu'elle devient visible.
function cadrer(el) {
if (!el._carte || !el.offsetWidth) return;
el._carte.invalidateSize();
if (!el._cadree) {
el._cadree = true;
if (el._bornes) el._carte.fitBounds(el._bornes, { padding: [20, 20] });
else el._carte.setView([46.5, 2.5], 5);
}
}
function initCartes(racine) {
if (!window.L) return;
racine.querySelectorAll(".carte[data-couches]").forEach(el => {
if (el._carte) return;
const carte = L.map(el, { scrollWheelZoom: false });
el._carte = carte;
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 18, attribution: "© OpenStreetMap"
}).addTo(carte);
const groupe = L.featureGroup().addTo(carte);
JSON.parse(el.dataset.couches).forEach(c => {
c.segments.forEach(s => {
L.polyline(s, { color: c.couleur, weight: 4, opacity: 0.85, dashArray: c.pointille ? "6 8" : null })
.bindTooltip(c.nom + (c.km ? " · " + c.km + " km" : ""), { sticky: true })
.addTo(groupe);
});
});
el._bornes = groupe.getLayers().length ? groupe.getBounds() : null;
carte.setView([46.5, 2.5], 5);
cadrer(el);
// La molette ne zoome qu'après un clic dans la carte (pas en faisant défiler la page).
carte.on("click", () => carte.scrollWheelZoom.enable());
carte.on("mouseout", () => carte.scrollWheelZoom.disable());
});
}
// --- Après chaque échange htmx --------------------------------------------
document.addEventListener("htmx:afterSettle", e => {
initCartes(e.detail.elt.parentElement || document);
document.querySelectorAll(".journal").forEach(j => { j.scrollTop = j.scrollHeight; });
});
document.addEventListener("DOMContentLoaded", () => {
initCartes(document);
document.querySelectorAll(".journal").forEach(j => { j.scrollTop = j.scrollHeight; });
});
// --- Fenêtre (choix de dossier) -------------------------------------------
function fermer() { document.getElementById("fenetre").innerHTML = ""; }
document.addEventListener("click", e => {
if (e.target.matches("[data-fermer-fond]")) fermer();
if (e.target.matches("[data-fermer]") && !e.target.hasAttribute("hx-post")) fermer();
});
document.addEventListener("htmx:afterRequest", e => {
if (e.detail.elt.matches && e.detail.elt.matches("[data-fermer]") && e.detail.successful) fermer();
});
document.addEventListener("keydown", e => { if (e.key === "Escape") fermer(); });
// Les messages (erreurs) s'effacent d'eux-mêmes.
document.addEventListener("htmx:afterSwap", e => {
if (e.detail.target.id === "messages") {
clearTimeout(window._rtgMessage);
window._rtgMessage = setTimeout(() => { e.detail.target.innerHTML = ""; }, 6000);
}
});
})();