aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2025-11-14 08:35:35 +0100
committerMartin Fischer <martin@push-f.com>2025-11-14 08:36:40 +0100
commit067e52bc973931680da025b4f012f92f94bdbe53 (patch)
tree77713dee7e54fa5b161178f8907fe3cf5cc70a84 /src
parent036895f35ed6cb1b7832c0c1dc42c5d95a8b247f (diff)
fix(build): move src files to src/HEADmaster
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.
Diffstat (limited to 'src')
-rw-r--r--src/index.html26
-rw-r--r--src/script.js117
2 files changed, 143 insertions, 0 deletions
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 @@
+<!doctype html>
+<html>
+ <meta charset="utf-8">
+ <title>Share a geographic position</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
+ <body>
+ <div id="content"></div>
+ <noscript>This website requires JavaScript</noscript>
+ <script src="script.js"></script>
+ <style>
+ body {
+ max-width: 40ch;
+ margin: 1em auto;
+ padding: 0 0.5em;
+ font-size: 1.2em;
+ }
+ li {
+ padding: 1em;
+ }
+ .input-group { display: flex; gap: 0.2em; margin-bottom: 1em; }
+ .input-group input { flex-grow: 1; }
+ input, button { font-size: inherit; }
+ .error { color: darkred; }
+ </style>
+ </body>
+</html>
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;
+}