all repos — onyx @ 82b55554e703fd0b128ca684994a2b49cf80dc59

minimal map annotation and location data sharing tool

rename Polygons to Paths - add option to close polyline or not on creation; bump to v0.2.0
Iris Lightshard nilix@nilfm.cc
PGP Signature
-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEkFh6dA+k/6CXFXU4O3+8IhROY5gFAmMFklwACgkQO3+8IhRO
Y5jB9w/+K19uXd4G0V9o+TJZADwItHXZNenm1Z89c8fYzf1LUaYtm9xHlTnPzQLD
PA62wlHTDCOI1E/V4M6JpBQF3w8t7EVDD7cT0MAPfMWLnKoNlEalxwjxAiU1pitF
0wJdCiQvWt45amKZgLb9s5wOrANCevHB1UD8XiUpvp0S4uNMBa/vJuEo2SBXBfkG
Ghtl4XoFXCOCIiVBM/3TXZtUR23EQJVx+Y40vkJvPfwHmLpUeYrRSILMdIDmesA4
F0ggZPPnbjbhljjD12sFZG7KcTCABrNUuvsW57uGVbKbiEWMBtyWqmmtajpRjyge
WEhhuYezbIEzb9pWGZ2fgdN5V+bEsxi37C6WQJDGBTzCmILGtwfIjqYnumNRprGK
cqTklzPz7/5otsYYKOoddJyweeaF2507Bjdg1qQ7ENNAttS8LKI27BRD+6Yw3p4o
LhkRm8pEX6wvjyzRU5aj59C2OpgTIICtFoeIunCgkHquuh4UE6RgWMbcjzApWCFB
/eDD6JwPxhFeuVgIUZ90tQHUyTQAfRDjs8lJ2bYTP9t9FKNMc9SsKIDBFFiHvs+Z
aIGr+QNCqZc8eRfsLcOMsI0uUoipPQ/I3su5QuOrhXgKd7XxWnk3I3U8JAR3DWWO
PIcfU5cIwE1TD3gg+avEMrYjimEaL7CN4VC+WVRSXzeiREW0Kws=
=gI87
-----END PGP SIGNATURE-----
commit

82b55554e703fd0b128ca684994a2b49cf80dc59

parent

4247dd822b725f62afdbff5afd8988d26e846674

M README.mdREADME.md

@@ -2,7 +2,7 @@ # onyx/scry

## about -`onyx/scry` is a lightweight map annotation and location data management and sharing tool built using [leaflet](https://leafletjs.com) and [typescript](https://typescriptlang.org). It is intended as a standalone tool to generate, manage, and share simple location data in the form of points, circles, and polygons. All of these have associated titles and descriptions and can be easily imported from or exported to JSON format for easy sharing. All data is saved locally via the `localStorage` API, and the only network calls are those to retrieve either the streetmap or satellite tile data (and of course geolocation if you opt in). +`onyx/scry` is a lightweight map annotation and location data management and sharing tool built using [leaflet](https://leafletjs.com) and [typescript](https://typescriptlang.org). It is intended as a standalone tool to generate, manage, and share simple location data in the form of points, circles, and paths (polylines and polygons). All of these have associated titles and descriptions and can be easily imported from or exported to JSON format for easy sharing. All data is saved locally via the `localStorage` API, and the only network calls are those to retrieve either the streetmap or satellite tile data (and of course geolocation if you opt in). ## usage

@@ -15,7 +15,7 @@

- `Home`: If the `home` point has been set, center the map view on it. - `Marker`: Enter marker creation mode. - `Circle`: Enter circle creation mode. -- `Polygon`: Enter polygon creation mode. +- `Path`: Enter path creation mode. - `Tileset`: Swap the map tiles between the streetmap and satellite imagery. - `Save`: Saves your current overlays to local storage. - `Clear`: Clears all overlays from the map.

@@ -30,9 +30,9 @@ Clicking the `Marker`, `Circle`, or `Polygon` buttons, you enter overlay creation mode for that overlay type. A small window at the bottom of the screen appears with a cancel button which allows you to leave this mode.

For Markers and Circles, you just click anywhere on the map to bring up the Overlay Creation window. -For Polygons, you click points on the map to add them to the polygon — you will see the outline once you have added at least two points. Once you have added at least three points, an OK button appears on the window at bottom, and clicking it opens the Overlay Creation window. +For Paths, you click points on the map to add them to the polyline — you will see it once you have added at least two points. Once you have added at least three points, an OK button appears on the window at bottom, and clicking it opens the Overlay Creation window. -In the Overlay Creation window, you can set a name and optionally a description for the overlay. For circles, you also set the radius in meters, which defaults to 500. Pressing the `OK` button saves the overlay to the map. +In the Overlay Creation window, you can set a name and optionally a description for the overlay. For circles, you also set the radius in meters, which defaults to 500. For paths, there is a checkbox for whether to close the polyline (creating a polygon), or leave it as-is. Pressing the `OK` button saves the overlay to the map. ### Overlay Management
M src/10-overlay.tssrc/10-overlay.ts

@@ -168,7 +168,11 @@ menuItem: Node | null = null;

constructor(name: string, desc: string, points: Point[], options: any) { super(name, desc, points, options); - this.self = L.polygon(points, options); + if (options.closed) { + this.self = L.polygon(points, options); + } else { + this.self = L.polyline(points, options); + } this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`); }

@@ -225,13 +229,13 @@

class OverlayState { markers: Marker[]; circles: Circle[]; - polygons: Polygon[]; + paths: Polygon[]; polyline: Polyline; constructor() { this.markers = []; this.circles = []; - this.polygons = []; + this.paths = []; this.polyline = new Polyline(); }

@@ -245,7 +249,7 @@ const model = JSON.parse(overlayData);

return { markers: model.markers.map((m: OverlayData) => OverlayState.fromData(m)), circles: model.circles.map((c: OverlayData) => OverlayState.fromData(c)), - polygons: model.polygons.map((p: OverlayData) => OverlayState.fromData(p)), + paths: model.paths.map((p: OverlayData) => OverlayState.fromData(p)), polyline: new Polyline(), } as OverlayState }

@@ -258,7 +262,7 @@ static export(overlayState: OverlayState): string {

return JSON.stringify({ markers: overlayState.markers.map((m: OverlayBase) => OverlayState.toData(m)), circles: overlayState.circles.map((c: OverlayBase) => OverlayState.toData(c)), - polygons: overlayState.polygons.map((p: OverlayBase) => OverlayState.toData(p)), + paths: overlayState.paths.map((p: OverlayBase) => OverlayState.toData(p)), }, null, 2); }

@@ -269,7 +273,7 @@

static clear(overlayState: OverlayState, map: L.Map): OverlayState { overlayState.markers.forEach((m: Marker) => m.remove(map)); overlayState.circles.forEach((c: Circle) => c.remove(map)); - overlayState.polygons.forEach((p: Polygon) => p.remove(map)); + overlayState.paths.forEach((p: Polygon) => p.remove(map)); const self = new OverlayState(); self.polyline.add(map);

@@ -279,7 +283,11 @@

private static toData(source: OverlayBase): OverlayData { let type = OverlayType.POINT; if (source.points.length > 1) { - type = OverlayType.POLYGON; + if (source.options.closed) { + type = OverlayType.POLYGON; + } else { + type = OverlayType.POLYLINE; + } } else if (source.options.radius) { type = OverlayType.CIRCLE; }

@@ -294,6 +302,7 @@ return new Marker(data.name, data.desc, data.points[0], data.options);

case OverlayType.CIRCLE: return new Circle(data.name, data.desc, data.points[0], data.options); case OverlayType.POLYGON: + case OverlayType.POLYLINE: return new Polygon(data.name, data.desc, data.points, data.options); } }

@@ -310,7 +319,8 @@ case OverlayType.CIRCLE:

overlayState.circles.push(overlay); break; case OverlayType.POLYGON: - overlayState.polygons.push(overlay); + case OverlayType.POLYLINE: + overlayState.paths.push(overlay); break; } overlay.add(map);

@@ -326,8 +336,8 @@ self.circles.forEach((c: Circle) => {

overlayState.circles.push(c); c.add(map); }); - self.polygons.forEach((p: Polygon) => { - overlayState.polygons.push(p); + self.paths.forEach((p: Polygon) => { + overlayState.paths.push(p); p.add(map); }); return true;
M src/20-createOverlayModal.tssrc/20-createOverlayModal.ts

@@ -34,6 +34,21 @@ radiusContainer(): HTMLElement | null {

return document.getElementById("radius-container"); } + closePolyContainer(): HTMLElement | null { + return document.getElementById("close-poly-container"); + } + + closePolyCheckbox(): HTMLInputElement | null { + return document.getElementById("close-poly-checkbox") as HTMLInputElement; + } + + setClosePoly(v: boolean): void { + const checkbox = this.closePolyCheckbox(); + if (checkbox) { + checkbox.checked = v; + } + } + setName(name: string): void { const self = document.getElementById("createOverlay-name") as HTMLInputElement; if (self) {

@@ -111,19 +126,26 @@ }

if (Number(radius?.value) != 500) { radius.value = "500"; } + + this.setClosePoly(false); } setState(state: OverlayType, args: any): void { const _this = this; const title = this.title() const radiusContainer = _this.radiusContainer(); + const closePolyContainer = _this.closePolyContainer(); + const closePoly = _this.closePolyCheckbox(); const submitBtn = _this.submitBtn(); - + const editing = args.self ? true : false; _this.clearInputs(); if (radiusContainer) { radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none"; } - const editing = args.self ? true : false; + if (closePolyContainer) { + closePolyContainer.style.display = (state == OverlayType.POLYGON && !editing) ? "block" : "none"; + } + this.setExtraButtonsVisible(editing); if (editing) { const gotoBtn = this.gotoBtn();

@@ -139,7 +161,7 @@ case OverlayType.CIRCLE:

title.innerHTML = "Edit Circle "; break; case OverlayType.POLYGON: - title.innerHTML = "Edit Polygon "; + title.innerHTML = "Edit Path "; break; } }

@@ -226,7 +248,7 @@ }

break; case OverlayType.POLYGON: if (title) { - title.innerHTML = "Add Polygon "; + title.innerHTML = "Add Path "; } if (submitBtn) { submitBtn.onclick = () => {

@@ -235,9 +257,9 @@ const desc = TextUtils.encodeHTML(_this.descField());

if (name.trim().length < 1) { return; } - const polygon = new Polygon(name, desc, args.points, {}); + const polygon = new Polygon(name, desc, args.points, { closed: closePoly?.checked ?? false, weight: 5 }); polygon.add(args.map); - args.overlays.polygons.push(polygon); + args.overlays.paths.push(polygon); _this.setVisible(false); } }
M src/40-handlers.tssrc/40-handlers.ts

@@ -187,7 +187,7 @@ const cancelBtn = self.modals.cancel.cancelBtn();

if (cancelBtn) { cancelBtn.onclick = MapHandler.polygonClearPoints; } - self.modals.cancel.setMsg("Creating polygon"); + self.modals.cancel.setMsg("Creating path"); self.modals.cancel.setVisible(true); self.map.on("click", MapHandler.polygonAddPoint);

@@ -218,7 +218,7 @@ const cancelBtn = self.modals.okCancel.cancelBtn();

if (cancelBtn) { cancelBtn.onclick = MapHandler.polygonClearPoints; } - self.modals.okCancel.setMsg("Creating polygon"); + self.modals.okCancel.setMsg("Creating path"); self.modals.okCancel.setVisible(true); } }

@@ -268,7 +268,7 @@ self.overlays = OverlayState.clear(self.overlays, self.map);

self.overlays = OverlayState.load(); self.overlays.markers.forEach(m=>m.add(self.map)); self.overlays.circles.forEach(m=>m.add(self.map)); - self.overlays.polygons.forEach(m=>m.add(self.map)); + self.overlays.paths.forEach(m=>m.add(self.map)); self.overlays.polyline.add(self.map); self.modals.okCancel.setVisible(false); MapHandler.resetMapClick();
M src/99-onyx-scry.tssrc/99-onyx-scry.ts

@@ -1,8 +1,13 @@

-const helpLink = "<br>ONYX/scry v0.1.0 [ <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about/LICENSE'>license</a> | <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about'>manual</a> ]"; +const helpLink = "<br>ONYX/scry v0.2.0 [ <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about/LICENSE'>license</a> | <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about'>manual</a> ]"; function init(): void { - let overlays: OverlayState = OverlayState.load() ?? new OverlayState(); + let overlays: OverlayState = new OverlayState(); + try { + overlays = OverlayState.load(); + } catch { + alert("Error reading saved data; initializing with empty data."); + } const map = L.map('map').fitWorld(); const streetLayer = TileLayerWrapper.constructLayer(

@@ -27,7 +32,7 @@ TileLayerWrapper.enableOnly("streetLayer", map);

overlays.markers.forEach(m=>m.add(map)); overlays.circles.forEach(m=>m.add(map)); - overlays.polygons.forEach(m=>m.add(map)); + overlays.paths.forEach(m=>m.add(map)); overlays.polyline.add(map); const modals = new ModalCollection(
M src/build.shsrc/build.sh

@@ -38,5 +38,5 @@

# delete the temporary files rm ${errorOut} - rm ${progname}.ts +rm ${progname}.ts rm ${srcmap}
M static/index.htmlstatic/index.html

@@ -5,7 +5,7 @@ <meta charset='utf-8'>

<meta name='description' content='map annotation tool'/> <meta name='viewport' content='width=device-width,initial-scale=1'> <link rel='stylesheet' type="text/css" href="./leaflet.css"> -<link rel='stylesheet' type='text/css' href='./style.css'> +<link rel='stylesheet' type='text/css' href='./style.css?v=0.2.0'> <meta name="apple-mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" /> <link rel='shortcut icon' href='/favicon.png'>

@@ -48,6 +48,10 @@ <textarea id="createOverlay-desc"></textarea><br/>

<div id="radius-container"> <label for="createOverlay-radius">Radius (meters)</label><br/> <input type="number" step="1" id="createOverlay-radius" value="500" required><br/> + </div> + <div id="close-poly-container"> + <input type="checkbox" id="close-poly-checkbox"> + <label for="close-poly-checkbox">Close polyline</label><br/> </div> <div class="multiBtn-container" id="edit-extra-btns"> <button id="goto-btn">Go Here</button>

@@ -97,7 +101,7 @@ <div id="overlays-menu-container">

<div id="overlays-list"> <details id="markers-wrapper" open><summary>Markers</summary><ul id="markers-list"></ul></details> <details id="circles-wrapper" open><summary>Circles</summary><ul id="circles-list"></ul></details> - <details id="polygons-wrapper" open><summary>Polygons</summary><ul id="polygons-list"></ul></details> + <details id="polygons-wrapper" open><summary>Paths</summary><ul id="polygons-list"></ul></details> </div> <div class="multiBtn-container"> <button id="set-home-btn">Set Home</button>
M static/onyx-scry.jsstatic/onyx-scry.js

@@ -121,7 +121,12 @@ class Polygon extends OverlayBase {

constructor(name, desc, points, options) { super(name, desc, points, options); this.menuItem = null; - this.self = L.polygon(points, options); + if (options.closed) { + this.self = L.polygon(points, options); + } + else { + this.self = L.polyline(points, options); + } this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`); } center() {

@@ -167,7 +172,7 @@ class OverlayState {

constructor() { this.markers = []; this.circles = []; - this.polygons = []; + this.paths = []; this.polyline = new Polyline(); } static load() {

@@ -179,7 +184,7 @@ const model = JSON.parse(overlayData);

return { markers: model.markers.map((m) => OverlayState.fromData(m)), circles: model.circles.map((c) => OverlayState.fromData(c)), - polygons: model.polygons.map((p) => OverlayState.fromData(p)), + paths: model.paths.map((p) => OverlayState.fromData(p)), polyline: new Polyline(), }; }

@@ -190,7 +195,7 @@ static export(overlayState) {

return JSON.stringify({ markers: overlayState.markers.map((m) => OverlayState.toData(m)), circles: overlayState.circles.map((c) => OverlayState.toData(c)), - polygons: overlayState.polygons.map((p) => OverlayState.toData(p)), + paths: overlayState.paths.map((p) => OverlayState.toData(p)), }, null, 2); } static save(overlayState) {

@@ -199,7 +204,7 @@ }

static clear(overlayState, map) { overlayState.markers.forEach((m) => m.remove(map)); overlayState.circles.forEach((c) => c.remove(map)); - overlayState.polygons.forEach((p) => p.remove(map)); + overlayState.paths.forEach((p) => p.remove(map)); const self = new OverlayState(); self.polyline.add(map); return self;

@@ -207,7 +212,12 @@ }

static toData(source) { let type = OverlayType.POINT; if (source.points.length > 1) { - type = OverlayType.POLYGON; + if (source.options.closed) { + type = OverlayType.POLYGON; + } + else { + type = OverlayType.POLYLINE; + } } else if (source.options.radius) { type = OverlayType.CIRCLE;

@@ -222,6 +232,7 @@ return new Marker(data.name, data.desc, data.points[0], data.options);

case OverlayType.CIRCLE: return new Circle(data.name, data.desc, data.points[0], data.options); case OverlayType.POLYGON: + case OverlayType.POLYLINE: return new Polygon(data.name, data.desc, data.points, data.options); } }

@@ -237,7 +248,8 @@ case OverlayType.CIRCLE:

overlayState.circles.push(overlay); break; case OverlayType.POLYGON: - overlayState.polygons.push(overlay); + case OverlayType.POLYLINE: + overlayState.paths.push(overlay); break; } overlay.add(map);

@@ -254,8 +266,8 @@ self.circles.forEach((c) => {

overlayState.circles.push(c); c.add(map); }); - self.polygons.forEach((p) => { - overlayState.polygons.push(p); + self.paths.forEach((p) => { + overlayState.paths.push(p); p.add(map); }); return true;

@@ -340,6 +352,18 @@ }

radiusContainer() { return document.getElementById("radius-container"); } + closePolyContainer() { + return document.getElementById("close-poly-container"); + } + closePolyCheckbox() { + return document.getElementById("close-poly-checkbox"); + } + setClosePoly(v) { + const checkbox = this.closePolyCheckbox(); + if (checkbox) { + checkbox.checked = v; + } + } setName(name) { const self = document.getElementById("createOverlay-name"); if (self) {

@@ -408,17 +432,23 @@ }

if (Number(radius === null || radius === void 0 ? void 0 : radius.value) != 500) { radius.value = "500"; } + this.setClosePoly(false); } setState(state, args) { const _this = this; const title = this.title(); const radiusContainer = _this.radiusContainer(); + const closePolyContainer = _this.closePolyContainer(); + const closePoly = _this.closePolyCheckbox(); const submitBtn = _this.submitBtn(); + const editing = args.self ? true : false; _this.clearInputs(); if (radiusContainer) { radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none"; } - const editing = args.self ? true : false; + if (closePolyContainer) { + closePolyContainer.style.display = (state == OverlayType.POLYGON && !editing) ? "block" : "none"; + } this.setExtraButtonsVisible(editing); if (editing) { const gotoBtn = this.gotoBtn();

@@ -433,7 +463,7 @@ case OverlayType.CIRCLE:

title.innerHTML = "Edit Circle "; break; case OverlayType.POLYGON: - title.innerHTML = "Edit Polygon "; + title.innerHTML = "Edit Path "; break; } }

@@ -513,18 +543,19 @@ }

break; case OverlayType.POLYGON: if (title) { - title.innerHTML = "Add Polygon "; + title.innerHTML = "Add Path "; } if (submitBtn) { submitBtn.onclick = () => { + var _a; const name = TextUtils.encodeHTML(_this.nameField()); const desc = TextUtils.encodeHTML(_this.descField()); if (name.trim().length < 1) { return; } - const polygon = new Polygon(name, desc, args.points, {}); + const polygon = new Polygon(name, desc, args.points, { closed: (_a = closePoly === null || closePoly === void 0 ? void 0 : closePoly.checked) !== null && _a !== void 0 ? _a : false, weight: 5 }); polygon.add(args.map); - args.overlays.polygons.push(polygon); + args.overlays.paths.push(polygon); _this.setVisible(false); }; }

@@ -892,7 +923,7 @@ const cancelBtn = self.modals.cancel.cancelBtn();

if (cancelBtn) { cancelBtn.onclick = MapHandler.polygonClearPoints; } - self.modals.cancel.setMsg("Creating polygon"); + self.modals.cancel.setMsg("Creating path"); self.modals.cancel.setVisible(true); self.map.on("click", MapHandler.polygonAddPoint); }

@@ -919,7 +950,7 @@ const cancelBtn = self.modals.okCancel.cancelBtn();

if (cancelBtn) { cancelBtn.onclick = MapHandler.polygonClearPoints; } - self.modals.okCancel.setMsg("Creating polygon"); + self.modals.okCancel.setMsg("Creating path"); self.modals.okCancel.setVisible(true); } }

@@ -965,7 +996,7 @@ self.overlays = OverlayState.clear(self.overlays, self.map);

self.overlays = OverlayState.load(); self.overlays.markers.forEach(m => m.add(self.map)); self.overlays.circles.forEach(m => m.add(self.map)); - self.overlays.polygons.forEach(m => m.add(self.map)); + self.overlays.paths.forEach(m => m.add(self.map)); self.overlays.polyline.add(self.map); self.modals.okCancel.setVisible(false); MapHandler.resetMapClick();

@@ -1169,10 +1200,15 @@ }

} } MapHandler.instance = null; -const helpLink = "<br>ONYX/scry v0.1 [ <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about/LICENSE'>license</a> | <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about'>manual</a> ]"; +const helpLink = "<br>ONYX/scry v0.2.0 [ <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about/LICENSE'>license</a> | <a target='_blank' href='https://nilfm.cc/git/onyx-scry/about'>manual</a> ]"; function init() { - var _a; - let overlays = (_a = OverlayState.load()) !== null && _a !== void 0 ? _a : new OverlayState(); + let overlays = new OverlayState(); + try { + overlays = OverlayState.load(); + } + catch (_a) { + alert("Error reading saved data; initializing with empty data."); + } const map = L.map('map').fitWorld(); const streetLayer = TileLayerWrapper.constructLayer("streetLayer", L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19,

@@ -1185,7 +1221,7 @@ }));

TileLayerWrapper.enableOnly("streetLayer", map); overlays.markers.forEach(m => m.add(map)); overlays.circles.forEach(m => m.add(map)); - overlays.polygons.forEach(m => m.add(map)); + overlays.paths.forEach(m => m.add(map)); overlays.polyline.add(map); const modals = new ModalCollection(new CreateOverlayModal(), new CancelModal(), new OKCancelModal(), new InfoModal(), new OverlayManagementModal(), new ImportExportModal()); MapHandler.init(map, overlays, TileLayerWrapper.layers, modals);
M static/style.cssstatic/style.css

@@ -195,6 +195,30 @@ .closeBtn:focus {

color: crimson; } +#close-poly-container { + text-align: left; + display: none; +} + +#close-poly-container label { + float: none; + display: inline; +} + +#close-poly-checkbox { + width: 1em; + height: 1em; + color: white; + background: transparent; + border: solid 2px dimgray; + box-sizing: border-box; +} + +#close-poly-checkbox:checked { + background: white; + border: solid 2px black; +} + #createOverlay-container .multiBtn-container button, #createOverlay-submitBtn, .positive-btn,