From 067e52bc973931680da025b4f012f92f94bdbe53 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Fri, 14 Nov 2025 08:35:35 +0100 Subject: fix(build): move src files to src/ The nix derivation src didn't use lib.cleanSource. Putting the source files into a separate directory is even better since it avoids unnecessary rebuilds. --- src/index.html | 26 +++++++++++++ src/script.js | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 src/index.html create mode 100644 src/script.js (limited to 'src') diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..a3a747d --- /dev/null +++ b/src/index.html @@ -0,0 +1,26 @@ + + + + Share a geographic position + + +
+ + + + + diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..8e539d8 --- /dev/null +++ b/src/script.js @@ -0,0 +1,117 @@ +const LINKS = [ + { + label: 'Google Maps', + url: 'https://www.google.com/maps/search/?api=1&query={lat},{lon}', + }, + { + label: 'Apple Maps', + url: 'https://maps.apple.com/?q={lat},{lon}&t=m', + }, + { + label: 'Default app', + url: 'geo:{lat},{lon}', + }, + { + label: 'OpenStreetMap', + url: 'https://www.openstreetmap.org/?mlat={lat}&mlon={lon}&zoom=15&layers=M', + }, +]; + +const content = document.getElementById('content'); + +route(); +window.addEventListener('hashchange', route); + +function route() { + const latLon = parseLatLon(location.hash.slice(1)); + content.innerHTML = ''; + + if (!latLon) { + document.title = 'Share a location'; + content.append(createEl('h1', {}, [document.title])); + const input = createEl('input', { placeholder: '55.7, 12.5' }); + const shareInputButton = createEl('button', {}, ['Share']); + const inputGroup = createEl('div', { class: 'input-group' }, [input, shareInputButton]); + const errorDiv = createEl('div', { class: 'error' }); + content.append(createEl('label', {}, ['Enter latitude, longitude', inputGroup])); + content.append(errorDiv); + function shareInput() { + if (!input.value.trim()) { + return; + } + try { + const [lat, lon] = parseLatLon(input.value); + shareLocation(lat, lon); + } catch (error) { + errorDiv.textContent = error; + } + } + input.addEventListener('keydown', (e) => { + if (e.key == 'Enter') { + shareInput(); + } + }); + shareInputButton.addEventListener('click', shareInput); + if (navigator.geolocation) { + const shareCurPos = createEl('button', {}, ['Share current position']); + content.append('or ', shareCurPos, errorDiv); + shareCurPos.addEventListener('click', () => { + navigator.geolocation.getCurrentPosition( + (pos) => { + shareLocation(pos.coords.latitude, pos.coords.longitude); + }, + (err) => { + errorDiv.textContent = err.message; + }, + ); + }); + } + } else { + document.title = 'Open location'; + + content.append(createEl('p', {}, ['Open in'])); + + const linkList = createEl('ul'); + const [lat, lon] = latLon; + + for (const linkSpec of LINKS) { + const url = linkSpec.url.replace('{lat}', lat).replace('{lon}', lon); + const link = createEl('a', { href: url }, [linkSpec.label]); + linkList.append(createEl('li', {}, [link])); + } + content.append(linkList); + } +} + +function shareLocation(lat, lon) { + // TODO: use navigator.share if supported + window.location = `#${lat},${lon}`; +} + +function parseLatLon(text) { + if (text == '') { + return null; + } + + let [lat, lon] = text.split(','); + lat = parseFloat(lat); + lon = parseFloat(lon); + if (Number.isNaN(lat) || Number.isNaN(lon)) { + throw Error('failed to parse latitude or longitude'); + } + if (lat < -180 || lat > 180 || lon < -90 || lon > 90) { + throw Error('latitude or longitude are out of range'); + } + return [lat, lon]; +} + +function createEl(tag, attrs = {}, content = []) { + const el = document.createElement(tag); + for (const [name, value] of Object.entries(attrs)) { + el.setAttribute(name, value); + } + for (const child of content) { + el.append(child); + } + return el; +} -- cgit v1.3.1