chore(collections/map): bring back colors

This commit is contained in:
Elian Doran
2026-02-15 19:43:05 +02:00
parent 8d0c61ea37
commit 60facceef1

View File

@@ -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 = `<svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">` +
`<path d="M12.5 0C5.6 0 0 5.6 0 12.5C0 21.9 12.5 41 12.5 41S25 21.9 25 12.5C25 5.6 19.4 0 12.5 0Z" fill="#2A81CB" />` +
`<circle cx="12.5" cy="12.5" r="8" fill="white" />` +
`</svg>`;
export const MARKER_SVG = buildMarkerIcon();
function buildMarkerIcon(color = DEFAULT_MARKER_COLOR) {
return `\
<svg width="25" height="41" viewBox="0 0 25 41" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5 0C5.6 0 0 5.6 0 12.5C0 21.9 12.5 41 12.5 41S25 21.9 25 12.5C25 5.6 19.4 0 12.5 0Z" fill="${color}" />
<circle cx="12.5" cy="12.5" r="8" fill="white" />
</svg>
`;
}
export function useMarkerData(note: FNote | null | undefined, apiRef: MutableRef<maplibregl.Map>) {
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<string, string>();
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<HTMLImageElement>(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;
});
}