Files
RoadTripsGenerator/rtg/templates/carte.js
T
FabriceandClaude Opus 5.5 f0e1c2dfce Phase 1 : moteur Python et commande rtg
- Paquet roadtripsgenerator (pyproject.toml), commande rtg :
  generer, etat, analyser, compresser, accueil, migrer
- Réglages site.toml / voyage.toml (anciens mymaps.txt et moto.txt
  encore lus) ; qualité vidéo en trois crans
- Sources laissées en place : traces découpées par journée d'après
  leurs horodatages (coupure à 4 h) ou la date du nom de fichier,
  photos copiées, HEIC converties en JPEG, position déduite de la
  trace pour les photos sans GPS
- extraction_infos.sh et compress.sh portés en Python (plus de bash) ;
  sorties vidéo écrites sous un nom temporaire ; positions des rushs
  inchangés reprises du dernier export ; export vidéo conservé quand
  le disque des rushs est débranché
- Pages, accueil et carte globale repris dans le paquet : sortie
  identique aux scripts (123 fichiers comparés sur 7 voyages)
- Tests pytest sur un voyage d'exemple fabriqué à la volée

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mf2kATMtqVABHSYdxJMnim
2026-10-09 11:31:31 +00:00

184 lines
7.6 KiB
JavaScript

/* =============================================================================
Pages d'un voyage : vidéos, diaporamas photo et carnet de route.
Copié dans <voyage>/assets/ à chaque génération.
Les gestionnaires sont appelés par des onclick="" dans les popups : Leaflet
bloque la propagation des clics hors des popups, une délégation au niveau
du document ne les verrait pas.
============================================================================= */
// --- Vidéos ------------------------------------------------------------------
// Le 720p (H.264) est chargé par défaut et se lit partout. Le 1080p est en
// HEVC : si le navigateur ne le décode pas (Firefox surtout), on grise le bouton.
var HEVC_OK = (function () {
try {
var t = document.createElement('video').canPlayType('video/mp4; codecs="hvc1.1.6.L120.B0"');
return t === 'probably' || t === 'maybe';
} catch (e) { return false; }
})();
function applyCodecSupport(popup) {
if (HEVC_OK) return;
var btns = popup.querySelectorAll('.quality-btn[data-hevc="1"]');
for (var i = 0; i < btns.length; i++) {
btns[i].classList.add('qbtn-disabled');
btns[i].title = "Le 1080p est encodé en HEVC, que ce navigateur ne sait pas décoder. Essaie Safari, Chrome ou Edge.";
}
var note = popup.querySelector('.hevc-note');
if (note) note.style.display = 'block';
}
function changeQuality(btn) {
if (btn.classList.contains('qbtn-disabled')) return;
var box = btn.closest('.popup-video');
var video = box.querySelector('video');
var newSrc = btn.getAttribute('data-src');
// video.src est une URL absolue, data-src est relatif : on compare les noms.
var nomActuel = decodeURIComponent(video.src).split('/').pop();
var nomVoulu = decodeURIComponent(newSrc).split('/').pop();
if (nomActuel === nomVoulu) return;
var savedTime = video.currentTime;
var wasPlaying = !video.paused;
video.src = newSrc;
video.onloadedmetadata = function () {
this.currentTime = savedTime;
if (wasPlaying) {
var p = this.play();
if (p !== undefined) p.catch(function () { console.log("Lecture bloquée"); });
}
this.onloadedmetadata = null;
};
video.load();
var buttons = box.querySelectorAll('.quality-btn');
for (var i = 0; i < buttons.length; i++) buttons[i].classList.remove('active-720', 'active-1080');
btn.classList.add(btn.getAttribute('data-hevc') === '1' ? 'active-1080' : 'active-720');
}
// À l'ouverture d'une popup vidéo : codec, image d'aperçu, puis lecture.
// L'aperçu n'est posé qu'à ce moment-là : en attribut poster="" direct, le
// navigateur chargerait d'un coup les aperçus de toutes les vidéos du jour.
function nomFichier(u) { return decodeURIComponent(u || '').split('/').pop(); }
// Filet de sécurité : si une version ne se lit pas (1080p HEVC malgré la
// détection, fichier abîmé ou absent), on essaie l'autre.
function surErreurVideo() {
var video = this, box = video.closest('.popup-video');
video._essais = video._essais || {};
video._essais[nomFichier(video.src)] = true;
var candidats = [video.getAttribute('data-fallback')];
var hd = box.querySelector('.quality-btn[data-hevc="1"]');
if (hd && HEVC_OK) candidats.push(hd.getAttribute('data-src'));
for (var i = 0; i < candidats.length; i++) {
var c = candidats[i];
if (c && !video._essais[nomFichier(c)]) {
var btn = box.querySelector('.quality-btn[data-src="' + c + '"]');
if (btn) changeQuality(btn); else { video.src = c; video.load(); }
var p = video.play();
if (p !== undefined) p.catch(function () {});
return;
}
}
}
function preparePopup(node) {
applyCodecSupport(node);
var video = node.querySelector('video');
if (!video) return;
// Leaflet réinsère la même popup à chaque ouverture : une seule initialisation.
if (!video._pret) {
video._pret = true;
var apercu = video.getAttribute('data-poster');
if (apercu) video.setAttribute('poster', apercu);
video.addEventListener('error', surErreurVideo);
}
var p = video.play();
if (p !== undefined) p.catch(function (e) { console.log("Autoplay bloqué par le navigateur", e); });
}
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
m.addedNodes.forEach(function (node) {
if (node.nodeType === 1 && node.classList && node.classList.contains('leaflet-popup')) {
preparePopup(node);
}
});
});
});
document.addEventListener('DOMContentLoaded', function () {
observer.observe(document.body, { childList: true, subtree: true });
});
// --- Diaporama photos ----------------------------------------------------------
function moveSlide(btn, step) {
var slides = btn.parentElement.querySelectorAll('.slides .slide');
var actif = Array.prototype.findIndex.call(slides, function (s) { return s.style.display !== 'none'; });
if (actif === -1) actif = 0;
slides[actif].style.display = 'none';
var suivant = (actif + step + slides.length) % slides.length;
slides[suivant].style.display = 'block';
btn.parentElement.querySelector('.slide-counter').innerText = (suivant + 1) + '/' + slides.length;
}
// --- Carnet de route -----------------------------------------------------------
// localStorage lève une exception sur file:// dans certains navigateurs
// (Safari, navigation privée) : on l'enveloppe.
function memLire(k) { try { return localStorage.getItem(k); } catch (e) { return null; } }
function memEcrire(k, v) { try { localStorage.setItem(k, v); } catch (e) {} }
// Journée à afficher : celle de l'URL (#jour-AAAA-MM-JJ) ou celle de la page.
function journalCible() {
var m = /#jour-(\d{4}-\d{2}-\d{2})/.exec(window.location.hash || '');
return m ? m[1] : (window.JOUR_COURANT || null);
}
function journalDefile(jour) {
var panel = document.getElementById('journal-panel');
if (!panel || !jour) return false;
var el = document.getElementById('ancre-' + jour);
if (!el) return false;
var r = el.getBoundingClientRect(), pr = panel.getBoundingClientRect();
panel.scrollTop += (r.top - pr.top) - 70; // laisse voir le titre du jour
var actif = panel.querySelector('.jour-actif');
if (actif) actif.classList.remove('jour-actif');
el.classList.add('jour-actif');
return true;
}
// Rejoué plusieurs fois : les CSS distantes (Leaflet, Font Awesome) arrivent
// après DOMContentLoaded et décalent les hauteurs.
function journalRecale() {
var j = journalCible();
journalDefile(j);
[150, 500, 1200].forEach(function (d) { setTimeout(function () { journalDefile(j); }, d); });
}
function toggleJournal(open) {
var panel = document.getElementById('journal-panel');
if (!panel) return;
panel.classList.toggle('open', open);
memEcrire('journalOpen', open ? 'true' : 'false');
if (open) journalRecale();
}
function journalInit() {
var panel = document.getElementById('journal-panel');
if (!panel) return;
var vientDuCarnet = /#jour-\d{4}-\d{2}-\d{2}/.test(window.location.hash || '');
if (memLire('journalOpen') === 'true' || vientDuCarnet) {
panel.classList.add('open');
if (vientDuCarnet) memEcrire('journalOpen', 'true');
}
journalRecale();
}
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', journalInit);
} else {
journalInit();
}
window.addEventListener('load', journalRecale);
window.addEventListener('hashchange', function () { toggleJournal(true); });