2025-02-09 19:55:34 +00:00
|
|
|
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);
|
|
|
|
|
2025-02-10 11:56:37 +00:00
|
|
|
let opfs;
|
|
|
|
try {
|
|
|
|
opfs = await navigator.storage.getDirectory();
|
|
|
|
} catch(e) {
|
|
|
|
|
|
|
|
}
|
2025-02-09 19:55:34 +00:00
|
|
|
|
|
|
|
async function init_app() {
|
|
|
|
const compilation_error = await show_error;
|
|
|
|
if(compilation_error) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-02-10 11:52:12 +00:00
|
|
|
const params = (new URL(window.location)).searchParams;
|
|
|
|
console.log(params);
|
|
|
|
let map_id = params.get('map') || '';
|
|
|
|
if(!map_id) {
|
|
|
|
map_id = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16).padStart(8,'0').slice(0,8);
|
|
|
|
}
|
|
|
|
|
2025-02-09 19:55:34 +00:00
|
|
|
let markers = [];
|
|
|
|
|
|
|
|
try {
|
2025-02-10 11:52:12 +00:00
|
|
|
markers = await (await fetch(`data/markers-${map_id}.json`)).json();
|
2025-02-09 19:55:34 +00:00
|
|
|
|
|
|
|
} catch(e) {
|
|
|
|
try {
|
2025-02-10 11:52:12 +00:00
|
|
|
const fh = await opfs.getFileHandle(`markers-${map_id}.json`);
|
2025-02-09 19:55:34 +00:00
|
|
|
const f = await fh.getFile();
|
|
|
|
markers = JSON.parse(await f.text())
|
|
|
|
} catch(e) {
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const emoji = await (await fetch('emoji_metadata.json')).json();
|
|
|
|
|
2025-02-10 11:52:12 +00:00
|
|
|
const app = Elm.App.init({
|
|
|
|
node: document.body,
|
|
|
|
flags: {emoji, markers, map_id}
|
|
|
|
});
|
2025-02-09 19:55:34 +00:00
|
|
|
|
|
|
|
navigator.geolocation.watchPosition(
|
|
|
|
(r) => {
|
|
|
|
app.ports.receive_position.send(r.coords);
|
|
|
|
},
|
|
|
|
(e) => console.error(e),
|
|
|
|
{enableHighAccuracy: true}
|
|
|
|
);
|
|
|
|
|
|
|
|
const send_value_handlers = {
|
|
|
|
save: async ({markers}) => {
|
2025-02-10 11:56:37 +00:00
|
|
|
try {
|
|
|
|
const f = await opfs.getFileHandle(`markers-${map_id}.json`, {create:true});
|
|
|
|
const w = await f.createWritable();
|
|
|
|
await w.write(JSON.stringify(markers));
|
|
|
|
await w.close();
|
|
|
|
} catch(e) {
|
|
|
|
|
|
|
|
}
|
2025-02-09 19:55:34 +00:00
|
|
|
|
|
|
|
const fd = new FormData();
|
|
|
|
fd.set('content', JSON.stringify(markers));
|
2025-02-10 11:52:12 +00:00
|
|
|
fd.set('map_id', map_id);
|
2025-02-09 19:55:34 +00:00
|
|
|
fetch('cgi-bin/save_data.py', {
|
|
|
|
method: 'POST',
|
|
|
|
body: fd
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
app.ports.send_value.subscribe(msg => {
|
|
|
|
send_value_handlers[msg.type](msg);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
init_app();
|