From 60facceef15ddcfec8f9846c75a567c582b79e7a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 15 Feb 2026 19:43:05 +0200 Subject: [PATCH] chore(collections/map): bring back colors --- .../widgets/collections/geomap/marker_data.ts | 72 ++++++++++++------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/apps/client/src/widgets/collections/geomap/marker_data.ts b/apps/client/src/widgets/collections/geomap/marker_data.ts index e3271d8f0f..11799f0177 100644 --- a/apps/client/src/widgets/collections/geomap/marker_data.ts +++ b/apps/client/src/widgets/collections/geomap/marker_data.ts @@ -4,24 +4,39 @@ import FNote from "../../../entities/fnote"; import { useChildNotes } from "../../react/hooks"; import { LOCATION_ATTRIBUTE } from "."; +const DEFAULT_MARKER_COLOR = "#2A81CB"; + // SVG marker pin shape (replaces the Leaflet marker PNG). -export const MARKER_SVG = `` + - `` + - `` + - ``; +export const MARKER_SVG = buildMarkerIcon(); + +function buildMarkerIcon(color = DEFAULT_MARKER_COLOR) { + return `\ + + + + + `; +} export function useMarkerData(note: FNote | null | undefined, apiRef: MutableRef) { const childNotes = useChildNotes(note?.noteId); - useEffect(() => { + async function refresh() { const map = apiRef.current as maplibregl.Map | undefined; if (!map) return; - svgToImage(MARKER_SVG, (img) => { - map.addImage("custom-marker", img, { - pixelRatio: window.devicePixelRatio - }); - }); + const iconSvgCache = new Map(); + + function ensureIcon(color: string) { + const key = `marker-${color}`; + + if (!iconSvgCache.has(key)) { + const svg = buildMarkerIcon(color); + iconSvgCache.set(key, svg); + } + + return key; + } const features: maplibregl.GeoJSONFeature[] = []; for (const childNote of childNotes) { @@ -30,6 +45,7 @@ export function useMarkerData(note: FNote | null | undefined, apiRef: MutableRef if (!latLng) continue; latLng.reverse(); + const color = childNote.getLabelValue("color") ?? DEFAULT_MARKER_COLOR; features.push({ type: "Feature", geometry: { @@ -39,10 +55,17 @@ export function useMarkerData(note: FNote | null | undefined, apiRef: MutableRef properties: { id: childNote.noteId, name: childNote.title, + icon: ensureIcon(color) } }); } + // Build all the icons. + await Promise.all(iconSvgCache.entries().map(async ([ key, svg ]) => { + const image = await svgToImage(svg); + map.addImage(key, image); + })); + map.addSource("points", { type: "geojson", data: { @@ -55,30 +78,31 @@ export function useMarkerData(note: FNote | null | undefined, apiRef: MutableRef type: "symbol", source: "points", layout: { - "icon-image": "custom-marker", + "icon-image": [ "get", "icon" ], "icon-size": 1, "icon-anchor": "bottom", "icon-allow-overlap": true } }); + } - return () => { - map.removeLayer("points-layer"); - map.removeSource("points"); - }; + useEffect(() => { + refresh(); }, [ apiRef, childNotes ]); } -function svgToImage(svgString, callback) { - const svgBlob = new Blob([svgString], { type: "image/svg+xml" }); - const url = URL.createObjectURL(svgBlob); +function svgToImage(svgString){ + return new Promise(resolve => { + const svgBlob = new Blob([svgString], { type: "image/svg+xml" }); + const url = URL.createObjectURL(svgBlob); - const img = new Image(); + const img = new Image(); - img.onload = () => { - URL.revokeObjectURL(url); - callback(img); - }; + img.onload = () => { + URL.revokeObjectURL(url); + resolve(img); + }; - img.src = url; + img.src = url; + }); }