diff --git a/TODO.adoc b/TODO.adoc index 3c98ea3..bd4736d 100644 --- a/TODO.adoc +++ b/TODO.adoc @@ -40,8 +40,8 @@ independence from connectivity. == Custom layers & data * [x] GPX/GeoJSON drag-and-drop import (Wikiloc, Komoot, own GPX tracks — also FIT), auto-styled by category with a generated elevation profile — KML not supported (only GPX/FIT/GeoJSON/.zip) -* [x] User overlay: offline drawing of custom points with notes (e.g. "turning space for a long vehicle", "narrow gate", "good swimming spot") — click the map, pick or name a `.geojson` layer, saved into the same GpxStore the Tracks panel already reads (so visibility/popup come for free) -** [ ] Polygon notes (not just points) — needs gpx.js's point/line split (`_addLayers`) taught a third, Polygon-shaped bucket first, or a saved polygon wouldn't render back on reload +* [x] User overlay: offline drawing of custom points *and areas* with notes (e.g. "turning space for a long vehicle", "narrow gate", "good swimming spot") — Point/Area mode toggle with a live drawing preview, saved into the same GpxStore the Tracks panel already reads (so visibility/popup come for free); gpx.js's `_addLayers` now renders Polygon features (fill + outline) alongside points/lines, not just points +** [ ] The Unigrid Matrix category filter only re-slices the *points* source (`_applyCategoryFilter`) — toggling the "Note" category off hides point notes but not polygon ones sharing that category * [ ] Offline satellite tile caching (e.g. ESRI) for a selected route area — key for judging surface quality and obstacles when wild camping == Nice to have (later) diff --git a/public/css/style.css b/public/css/style.css index 7bd78d6..6f09f2e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -774,17 +774,63 @@ html, body { display: none; } +.notes-panel__modes { + display: flex; + gap: 4px; + margin-bottom: 6px; +} + +.notes-panel__mode { + flex: 1; + width: auto; + height: auto; + padding: 4px 8px; + border: none; + border-radius: 4px; + background: #f0f0f0; + font: inherit; + font-size: 12px; + cursor: pointer; +} + +.notes-panel__mode.active { + background: #dbe9ff; + font-weight: 600; +} + .notes-panel__hint { - margin: 0; + margin: 0 0 6px; color: #888; } +.notes-panel__finish { + width: 100%; + box-sizing: border-box; + margin-bottom: 6px; + padding: 4px 8px; + border: none; + border-radius: 4px; + background: #1a5fb4; + color: #fff; + font: inherit; + font-size: 12px; + cursor: pointer; +} + .notes-panel__form { display: flex; flex-direction: column; gap: 6px; } +/* Same "author display wins over the UA [hidden] rule" gotcha as + .gpx-panel__action[hidden] above — both this and .notes-panel__modes set + their own "display", so hiding them needs an explicit override too. */ +.notes-panel__form[hidden], +.notes-panel__modes[hidden] { + display: none; +} + .notes-panel__text { width: 100%; box-sizing: border-box; diff --git a/public/js/gpx.js b/public/js/gpx.js index f741d0b..473483a 100644 --- a/public/js/gpx.js +++ b/public/js/gpx.js @@ -540,6 +540,29 @@ export class GpxPanelControl { }); this._registerLineInteractivity(`${linesId}-line`); + // Polygon notes (see notes.js) — the same "lines" source already only + // renders LineString geometry through the line layer above (MapLibre's + // own "line" layer type silently ignores Polygon features), so this is + // just two more layers reading the same source, not a new bucket. + // Popup-on-click like a waypoint (_registerPointInteractivity), not the + // track line's hover-summary/profile-panel behavior above — a note + // polygon has no elevation profile to open. + this._map.addLayer({ + id: `${linesId}-polygon-fill`, + type: "fill", + source: linesId, + filter: ["==", ["geometry-type"], "Polygon"], + paint: { "fill-color": TRACK_COLOR, "fill-opacity": 0.2 }, + }); + this._map.addLayer({ + id: `${linesId}-polygon-outline`, + type: "line", + source: linesId, + filter: ["==", ["geometry-type"], "Polygon"], + paint: { "line-color": TRACK_COLOR, "line-width": 2 }, + }); + this._registerPointInteractivity(`${linesId}-polygon-fill`); + // clusterRadius/clusterMaxZoom are harmless when there's nothing dense // enough to cluster (a handful of waypoints just render individually). this._map.addSource(pointsId, { type: "geojson", data: points, cluster: true, clusterRadius: 50, clusterMaxZoom: 14 }); @@ -704,6 +727,8 @@ export class GpxPanelControl { const pointsId = pointsSourceId(path); [ `${linesId}-line`, + `${linesId}-polygon-fill`, + `${linesId}-polygon-outline`, `${pointsId}-clusters`, `${pointsId}-cluster-count`, `${pointsId}-unclustered`, diff --git a/public/js/notes.js b/public/js/notes.js index 4c2e356..c440a2f 100644 --- a/public/js/notes.js +++ b/public/js/notes.js @@ -2,20 +2,26 @@ import { NOTE_PIN_ICON } from "/js/icons.js"; import { notifyGpxStoreChanged } from "/js/panels.js"; const NEW_LAYER_VALUE = "__new__"; +const PREVIEW_SOURCE_ID = "notes-preview"; +const PREVIEW_FILL_LAYER_ID = "notes-preview-fill"; +const PREVIEW_LINE_LAYER_ID = "notes-preview-line"; +const PREVIEW_POINT_LAYER_ID = "notes-preview-points"; +const PREVIEW_COLOR = "#c0392b"; -// A quick "pin it with a note" tool — click the map, write a short note, -// pick which .geojson "layer" file it joins (or start a new one). Saved -// server-side (POST /api/gpx/notes) into the same GpxStore everything else -// in the Tracks panel already reads from, so a note shows up there with no +// A quick "pin it with a note" tool — click the map (Point mode) or click +// out a few corners and Finish (Area mode), write a short note, pick which +// .geojson "layer" file it joins (or start a new one). Saved server-side +// (POST /api/gpx/notes) into the same GpxStore everything else in the +// Tracks panel already reads from, so a note shows up there with no // separate rendering path of its own: same tree, same checkbox visibility, -// same popup. Points only for now — a polygon mode would need gpx.js's -// point/line split (public/js/gpx.js's _addLayers) taught a third, -// Polygon-shaped bucket first, so it's left for a follow-up rather than -// saving areas that wouldn't render back once reloaded. +// same popup — public/js/gpx.js's _addLayers renders a Polygon feature the +// same way it already renders points/lines, no separate code path here. export class NotesControl { constructor() { this._active = false; - this._pendingLngLat = null; + this._mode = "point"; // "point" | "area" + this._areaPoints = []; // vertices while drawing an area, before Finish + this._pendingGeometry = null; // the finished Point/Polygon geometry, once placed } onAdd(map) { @@ -26,7 +32,12 @@ export class NotesControl { this._container.innerHTML = `