import show_error from './show-error.mjs'; import * as marked from './marked.js'; console.clear(); window.marked = marked; class MarkdownElement extends HTMLElement { constructor() { super(); } connectedCallback() { const shadowRoot = this.attachShadow({mode:'open'}); const markdown_changed = () => { const html = marked.parse(this.textContent); shadowRoot.innerHTML = html; for(let a of shadowRoot.querySelectorAll('a')) { a.setAttribute('target','_blank'); } } const observer = new MutationObserver(markdown_changed); observer.observe(this, {characterData: true, subtree: true}); markdown_changed(); } } customElements.define('mark-down', MarkdownElement); class LeafletElement extends HTMLElement { constructor() { super(); } addStylesheet(url) { const linkElem = document.createElement("link"); linkElem.setAttribute("rel", "stylesheet"); linkElem.setAttribute("href", url); this.shadowRoot.append(linkElem); } connectedCallback() { const shadowRoot = this.attachShadow({mode:'open'}); this.addStylesheet("https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"); this.addStylesheet('map.css'); const div = this.div = document.createElement('div'); div.style.height = '100%'; shadowRoot.append(div); const map = this.map = L.map(div); this.markers = []; this.update_view(); this.update_markers(); L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); map.on('move', () => { const {lat,lng} = map.getCenter(); }) map.on('click', e => { const ce = new CustomEvent('mapclick', {detail: {latlng: e.latlng}}); this.dispatchEvent(ce); }) map.on('move', e => { this.dispatchEvent(new CustomEvent('mapmove')); }) } static get observedAttributes() { return ['lat', 'lon', 'markers'] }; update_view() { const centre = this.getAttribute('centre') == 'true'; const home = ['lat','lon'].map(a => parseFloat(this.getAttribute(a)) || 0); if(centre) { this.map.setView(home, this.map.getZoom() || 13); } } update_markers() { this.markers.forEach(m => { m.marker.remove(); }) const data = JSON.parse(this.getAttribute('markers')); this.markers = data.map(({id,pos,icon}) => { const marker = L.marker( pos, { icon: L.divIcon({html: `${icon}`}), iconSize: [20,20] } ); marker.addTo(this.map); marker.on('click', e => { this.dispatchEvent(new CustomEvent('markerclick', {detail: {id}})); }) return {pos, marker}; }); } attributeChangedCallback(name, oldValue, newValue) { if(!this.map) { return; } switch(name) { case 'lat': case 'lon': case 'centre': this.update_view(); break; case 'markers': this.update_markers(); break; } } set html(value) { this.shadowRoot.innerHTML = value; } } customElements.define('leaflet-map', LeafletElement); let opfs = await navigator.storage.getDirectory(); async function init_app() { const compilation_error = await show_error; if(compilation_error) { return; } let markers = []; try { markers = await (await fetch('data/markers.json')).json(); } catch(e) { try { const fh = await opfs.getFileHandle('markers.json'); const f = await fh.getFile(); markers = JSON.parse(await f.text()) } catch(e) { } } const emoji = await (await fetch('emoji_metadata.json')).json(); const app = Elm.App.init({node: document.body, flags: {emoji, markers}}); const params = new URLSearchParams(location.search); navigator.geolocation.watchPosition( (r) => { app.ports.receive_position.send(r.coords); }, (e) => console.error(e), {enableHighAccuracy: true} ); const send_value_handlers = { save: async ({markers}) => { const f = await opfs.getFileHandle('markers.json', {create:true}); const w = await f.createWritable(); await w.write(JSON.stringify(markers)); await w.close(); const fd = new FormData(); fd.set('content', JSON.stringify(markers)); fetch('cgi-bin/save_data.py', { method: 'POST', body: fd }) } } app.ports.send_value.subscribe(msg => { send_value_handlers[msg.type](msg); }); } init_app();