Files
Alfredv2/alfred/app/static/js/dona-navigation-v8-3.js
2026-10-30 22:10:03 +01:00

122 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* DONA_NAV_V83 — navigation interactive et commandes vocales, sans clé API. */
(()=>{
'use strict';
if(window.DonaNavigationV83)return;
const $=id=>document.getElementById(id);
const el=(tag,cls,text)=>{const n=document.createElement(tag);if(cls)n.className=cls;if(text!==undefined)n.textContent=String(text);return n;};
const button=(label,fn,cls='dn-btn')=>{const b=el('button',cls,label);b.type='button';b.addEventListener('click',fn);return b;};
const safeUrl=(raw,host)=>{try{const u=new URL(String(raw));return u.protocol==='https:'&&(u.hostname===host||u.hostname==='www.'+host)?u.href:'';}catch(_){return '';}};
let active=false,loading=false,selected=null,map=null,leaflet=null,readyPromise=null,headButton=null;
const status=msg=>{if($('dnStatus'))$('dnStatus').textContent=String(msg||'');};
function initialize(){
const root=$('alfredMainV1');if(!root){setTimeout(initialize,200);return;}
const head=root.querySelector('.av-head-actions');
if(head){headButton=button('⌖',open,'av-round dn-open-button');headButton.title='Navigation';headButton.setAttribute('aria-label','Ouvrir la navigation');head.prepend(headButton);}
const overlay=el('div','dn-overlay');overlay.id='donaNavigationV83';overlay.hidden=true;
const panel=el('section','dn-panel');panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','true');panel.setAttribute('aria-label','Navigation Dona');
const header=el('header','dn-header');const tit=el('div');tit.append(el('span','dn-eyebrow','DONA · NAVIGATION'),el('h2','','Carte et itinéraires'));
header.append(tit,button('×',close,'dn-close'));panel.append(header);
const form=el('form','dn-form');form.id='dnSearchForm';
const dst=el('input','dn-input');dst.id='dnDestination';dst.name='destination';dst.placeholder='Destination : Metz, Luxembourg…';dst.required=true;dst.maxLength=180;dst.autocomplete='off';dst.setAttribute('aria-label','Destination');
const org=el('input','dn-input');org.id='dnOrigin';org.name='origin';org.placeholder='Départ (facultatif)';org.maxLength=180;org.autocomplete='off';org.setAttribute('aria-label','Point de départ');
const submit=button('Afficher',()=>{},'dn-btn dn-primary');submit.type='submit';submit.id='dnSubmit';form.append(dst,org,submit);form.addEventListener('submit',event=>{event.preventDefault();search(dst.value,org.value);});panel.append(form);
const message=el('p','dn-status');message.id='dnStatus';message.setAttribute('role','status');message.setAttribute('aria-live','polite');panel.append(message);
const layout=el('div','dn-layout');const main=el('div','dn-map-area');const mapbox=el('div','dn-map');mapbox.id='dnMap';main.append(mapbox);
main.append(el('p','dn-credit','© OpenStreetMap contributors · Temps de trajet indicatifs, hors trafic temps réel'));
const aside=el('aside','dn-aside');aside.append(el('h3','','Destination'));
const address=el('p','dn-destination-title','Choisis une destination.');address.id='dnPlace';aside.append(address);
const details=el('div','dn-route-details');details.id='dnDetails';aside.append(details);
const actions=el('div','dn-actions');
const wa=el('a','dn-btn dn-waze','Ouvrir Waze');wa.target='_blank';wa.rel='noopener noreferrer';wa.id='dnWaze';wa.hidden=true;
const gm=el('a','dn-btn','Google Maps');gm.target='_blank';gm.rel='noopener noreferrer';gm.id='dnMaps';gm.hidden=true;
const phone=button('Envoyer à mon téléphone',sendPhone,'dn-btn dn-primary');phone.id='dnSend';phone.disabled=true;
actions.append(wa,gm,phone);aside.append(actions);
aside.append(el('p','dn-help','Dona ne connaît pas ta position. Le départ est facultatif ; pour obtenir un temps de trajet, renseigne-le. Pour l’envoi au téléphone, configure d’abord Home Assistant.'));
layout.append(main,aside);panel.append(layout);overlay.append(panel);document.body.append(overlay);
overlay.addEventListener('click',event=>{if(event.target===overlay)close();});
window.addEventListener('keydown',event=>{if(event.key==='Escape'&&active)close();});
fetch('/api/dona/navigation/config',{credentials:'same-origin'}).then(r=>r.ok?r.json():null).then(data=>{if(data&&data.origin&&!org.value)org.value=data.origin;}).catch(()=>{});
connectSocket();
}
function open(){if(!$( 'donaNavigationV83'))return;active=true;$('donaNavigationV83').hidden=false;document.body.classList.add('dn-active');if(map)setTimeout(()=>map.invalidateSize(),80);}
function close(){active=false;$('donaNavigationV83').hidden=true;document.body.classList.remove('dn-active');}
function loadLeaflet(){
if(window.L)return Promise.resolve(window.L);
if(readyPromise)return readyPromise;
readyPromise=new Promise((resolve,reject)=>{
const style=document.createElement('link');style.rel='stylesheet';style.href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';document.head.append(style);
const script=document.createElement('script');script.src='https://unpkg.com/leaflet@1.9.4/dist/leaflet.js';script.async=true;
script.onload=()=>window.L?resolve(window.L):reject(new Error('Bibliothèque de cartes indisponible.'));
script.onerror=()=>reject(new Error('Carte interactive indisponible.'));
document.head.append(script);
setTimeout(()=>{if(!window.L)reject(new Error('Carte interactive non chargée.'));},12000);
});return readyPromise;
}
function drawFallback(place){
const box=$('dnMap');if(map){try{map.remove();}catch(_){}map=null;}
box.replaceChildren();
if(!place){box.append(el('p','dn-map-fallback','Destination à rechercher.'));return;}
const lat=Number(place.lat),lon=Number(place.lon);if(!Number.isFinite(lat)||!Number.isFinite(lon))return;
const iframe=el('iframe','dn-osm-frame');iframe.title='Carte OpenStreetMap de la destination';iframe.loading='lazy';iframe.referrerPolicy='strict-origin-when-cross-origin';
const delta=.035;
iframe.src='https://www.openstreetmap.org/export/embed.html?bbox='+encodeURIComponent([lon-delta,lat-delta,lon+delta,lat+delta].join(','))+'&layer=mapnik&marker='+encodeURIComponent(lat+','+lon);
box.append(iframe);
}
async function drawMap(data){
const place=data.place;const route=data.route;
if(!place){drawFallback(null);return;}
try{
const L=await loadLeaflet();if(!active)return;
if(map){map.remove();map=null;}
$('dnMap').replaceChildren();map=L.map('dnMap',{scrollWheelZoom:false});
const tileUrl='https://tile.openstreetmap.org/{z}/{x}/{y}.png';
L.tileLayer(tileUrl,{attribution:'© OpenStreetMap contributors',maxZoom:19}).addTo(map);
const loc=L.latLng(Number(place.lat),Number(place.lon));L.marker(loc).addTo(map).bindPopup(String(data.destination||'Destination').slice(0,120));
if(route&&Array.isArray(route.line)&&route.line.length>1){
const pts=route.line.filter(p=>Array.isArray(p)&&p.length===2&&p.every(Number.isFinite));
if(pts.length>1){const line=L.polyline(pts,{color:'#64d8ff',weight:5,opacity:.85}).addTo(map);map.fitBounds(line.getBounds(),{padding:[28,28]});}
else map.setView(loc,12);
if(data.origin_place){const start=L.latLng(Number(data.origin_place.lat),Number(data.origin_place.lon));L.circleMarker(start,{radius:8,color:'#93ffc9',fillColor:'#93ffc9',fillOpacity:.9}).addTo(map).bindPopup('Départ');}
}else map.setView(loc,12);
setTimeout(()=>map?.invalidateSize(),100);
}catch(_){drawFallback(place);status((data.note?data.note+' ':'')+'Carte simplifiée affichée : le module interactif n’a pas pu être chargé.');}
}
function show(data){
if(!data||data.type!=='dona_navigation')return;
selected=data.destination||null;open();
$('dnDestination').value=String(data.destination||'').slice(0,180);
if(data.origin)$('dnOrigin').value=String(data.origin).slice(0,180);
$('dnPlace').textContent=data.place?.name||String(data.destination||'Destination indisponible');
const info=$('dnDetails');info.replaceChildren();
if(data.route){info.append(el('strong','',data.route.distance_km+' km · '+data.route.duration_min+' min'),
el('p','','Durée estimée, sans trafic temps réel.'));}
else info.append(el('p','','Itinéraire non calculé — ouvre Waze pour le guidage.'));
const waze=safeUrl(data.links?.waze,'waze.com'),maps=safeUrl(data.links?.google_maps,'google.com');
for(const [id,url] of [['dnWaze',waze],['dnMaps',maps]]){const a=$(id);a.hidden=!url;if(url)a.href=url;else a.removeAttribute('href');}
$('dnSend').disabled=!selected;
status(data.error||data.send_error||(data.sent?'Itinéraire envoyé sur ton téléphone.':data.note||'Destination trouvée.'));
drawMap(data);
}
async function search(destination,origin=''){
if(loading)return;destination=String(destination||'').trim();origin=String(origin||'').trim();
if(destination.length<2||destination.length>180){status('Indique une destination valide.');return;}
loading=true;$('dnSubmit').disabled=true;open();status('Recherche de la destination et de l’itinéraire…');
try{
const res=await fetch('/api/dona/navigation/search',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({destination,origin})});
const data=await res.json();if(!res.ok||!data.ok)throw new Error(data.error||'Recherche indisponible.');
show({...data,type:'dona_navigation'});
}catch(e){status(e.message||'Erreur de navigation.');}
finally{loading=false;$('dnSubmit').disabled=false;}
}
async function sendPhone(){
if(!selected){status('Choisis d’abord une destination.');return;}
const b=$('dnSend');b.disabled=true;status('Envoi vers Home Assistant…');
try{
const resp=await fetch('/api/dona/navigation/send',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({destination:selected})});
const data=await resp.json();if(!resp.ok||!data.ok)throw new Error(data.error||'Envoi impossible.');status(data.message);
}catch(e){status(e.message||'Impossible d’envoyer le trajet.');}finally{b.disabled=false;}
}
function connectSocket(){const sk=window.alfredSocket;if(!sk){setTimeout(connectSocket,250);return;}sk.on('alfred_render',p=>{if(p?.type==='dona_navigation')show(p);});}
window.DonaNavigationV83={open,close,show,search,sendPhone};initialize();
})();