194 lines
4.6 KiB
JavaScript
194 lines
4.6 KiB
JavaScript
![]() |
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: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||
|
}).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: `<span id="${id}">${icon}</span>`}),
|
||
|
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();
|