Fix note UX: unreachable wiki link, white Save hover, stale icon preview, invisible new points
CI / check (push) Successful in 41s
CI / deploy (push) Successful in 12s

- "Nearby on Wikipedia" linked to KIWIX_URL, a Docker-internal hostname
  (http://kiwix:8080) no real browser can resolve. Split it: KIWIX_URL
  stays server-side-only for the reachability check, and the link
  itself is built from KIWIX_PUBLIC_PORT (kiwix-serve's newly
  published host port) plus whatever host the visitor already used to
  reach maptool — works regardless of which hostname/IP that is.
- The Save button went white on hover in GTK-themed Chrome: the
  browser's native button skin was painting over the custom
  background because appearance wasn't reset.
- The map preview marker for a pending note always showed a plain red
  pin no matter which icon was picked in the form. NoteSaveForm now
  reports icon changes via onIconChange so NotesControl can update the
  marker's glyph live.
- Saving a note (or a Measure "Save as note") left the new/grown
  .geojson file unchecked and invisible until manually toggled on.
  notifyGpxStoreChanged now carries the affected path so
  GpxPanelControl auto-shows it.
This commit is contained in:
randy committed 2026-09-09 15:11:50 +02:00
1 parent 4f022ec43a
commit 9997a8a8c0
7 files changed
+103 -17

No files matched your search

+28
View File
@@ -930,6 +930,29 @@ html, body {
background: #dbe9ff;
}
/* The pending-note marker shown on the map while the save form is open —
updates live to whatever icon is picked (see notes.js's onIconChange). A
plain round badge (not a drop-pin shape) since MapLibre anchors a custom
marker element at its center, and a pin's usual tip-points-at-the-spot
shape needs a bottom anchor to line up correctly. */
.notes-panel__preview-marker {
width: 26px;
height: 26px;
border-radius: 50%;
background: #c0392b;
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
.notes-panel__preview-marker svg {
width: 15px;
height: 15px;
}
.notes-panel__target,
.notes-panel__new-name {
width: 100%;
@@ -948,6 +971,11 @@ html, body {
}
.notes-panel__actions button {
/* Without this, some browsers (GTK-themed Chrome on Linux in
particular) paint their own native hover/active highlight over a
button's background instead of leaving a custom one alone — the
Save button visibly went white on hover because of exactly this. */
appearance: none;
width: auto;
height: auto;
padding: 4px 10px;
+11 -2
View File
@@ -60,8 +60,17 @@ export class GpxPanelControl {
// notes.js writes directly into the same GPX store (a hand-drawn note
// saved into a .geojson layer) — refresh so a newly created/grown file
// shows up in the tree without a manual reopen.
onGpxStoreChanged(() => this._loadTree());
// shows up in the tree without a manual reopen, and auto-show it (check
// + render) since a note the user just placed being invisible until
// they find and check its layer by hand defeats the point of dropping
// a pin right there on the map.
onGpxStoreChanged((path) => {
if (path) {
this._visible.add(path);
this._reloadFile(path);
}
this._loadTree();
});
}
// Registered once, always white, as plain (non-SDF) images: these used to
+15 -3
View File
@@ -45,9 +45,10 @@ export function generateNoteId() {
// container it's given (each caller owns where that sits in its own panel);
// hidden until open() is called.
export class NoteSaveForm {
constructor(container, { onSaved, onCancel } = {}) {
constructor(container, { onSaved, onCancel, onIconChange } = {}) {
this._onSaved = onSaved;
this._onCancel = onCancel;
this._onIconChange = onIconChange;
this._el = document.createElement("div");
this._el.className = "notes-panel__form";
@@ -85,6 +86,7 @@ export class NoteSaveForm {
button.addEventListener("click", () => {
this._selectedIcon = button.dataset.icon;
this._iconPicker.querySelectorAll(".notes-panel__icon-option").forEach((b) => b.classList.toggle("active", b === button));
this._onIconChange?.(this._selectedIcon);
});
});
this._target.addEventListener("change", () => {
@@ -106,6 +108,7 @@ export class NoteSaveForm {
this._body.value = "";
this._selectedIcon = "";
this._iconPicker.querySelectorAll(".notes-panel__icon-option").forEach((b, i) => b.classList.toggle("active", i === 0));
this._onIconChange?.(this._selectedIcon);
// An icon only makes sense on a single point — Measure's "Save as
// note" can hand this a LineString/Polygon, which has no one spot for
// a badge glyph to sit at.
@@ -173,7 +176,8 @@ export class NoteSaveForm {
try {
const response = await fetch("/api/gpx/notes", { method: "POST", body: requestBody });
if (!response.ok) throw new Error(`save failed: ${response.status}`);
notifyGpxStoreChanged();
const { path } = await response.json();
notifyGpxStoreChanged(path);
this.close();
this._onSaved?.();
} catch (err) {
@@ -219,6 +223,11 @@ export class NotesControl {
this._hint.hidden = false;
this._removeMarker();
},
// Keeps the map preview in sync with the icon picker — it used to
// always show a plain red pin no matter which icon got picked.
onIconChange: (icon) => {
if (this._marker) this._marker.getElement().innerHTML = icon && POI_ICONS[icon] ? POI_ICONS[icon] : NOTE_PIN_ICON;
},
});
this._button.addEventListener("click", () => this._setActive(!this._active));
@@ -226,7 +235,10 @@ export class NotesControl {
this._onClick = (event) => {
if (!this._active || this._form.isOpen) return;
this._hint.hidden = true;
this._marker = new Marker({ color: "#c0392b" }).setLngLat(event.lngLat).addTo(this._map);
const el = document.createElement("div");
el.className = "notes-panel__preview-marker";
el.innerHTML = NOTE_PIN_ICON;
this._marker = new Marker({ element: el }).setLngLat(event.lngLat).addTo(this._map);
this._form.open({ type: "Point", coordinates: [event.lngLat.lng, event.lngLat.lat] });
};
map.on("click", this._onClick);
+7 -4
View File
@@ -18,13 +18,16 @@ export function onlyOneSidePanelOpen(panel, onClosedByOther) {
// Lets a control that writes into the GPX store (e.g. notes.js saving a
// drawn point) tell GpxPanelControl its tree is stale, without either one
// holding a reference to the other.
// holding a reference to the other. path (the .geojson file just written
// to) lets the listener also auto-show that file instead of just refreshing
// the tree — the point/shape just saved is otherwise invisible until the
// user finds and checks it by hand.
const STORE_CHANGED_EVENT = "gpx-store-changed";
export function notifyGpxStoreChanged() {
document.dispatchEvent(new CustomEvent(STORE_CHANGED_EVENT));
export function notifyGpxStoreChanged(path) {
document.dispatchEvent(new CustomEvent(STORE_CHANGED_EVENT, { detail: path }));
}
export function onGpxStoreChanged(callback) {
document.addEventListener(STORE_CHANGED_EVENT, callback);
document.addEventListener(STORE_CHANGED_EVENT, (event) => callback(event.detail));
}