From 71e9eae5e6b9a8325427214fdbb87b7a0a4ec78f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 16 Feb 2026 19:40:45 +0200 Subject: [PATCH] fix(collections/map): tooltips not always available on first reload --- .../widgets/collections/geomap/Tooltips.tsx | 42 ++++++++++++++++++ .../src/widgets/collections/geomap/index.tsx | 38 +--------------- .../src/widgets/collections/geomap/map.tsx | 43 ++++++++----------- 3 files changed, 62 insertions(+), 61 deletions(-) create mode 100644 apps/client/src/widgets/collections/geomap/Tooltips.tsx diff --git a/apps/client/src/widgets/collections/geomap/Tooltips.tsx b/apps/client/src/widgets/collections/geomap/Tooltips.tsx new file mode 100644 index 0000000000..329a00f5bd --- /dev/null +++ b/apps/client/src/widgets/collections/geomap/Tooltips.tsx @@ -0,0 +1,42 @@ +import { MapMouseEvent, Popup } from "maplibre-gl"; +import { useContext, useEffect } from "preact/hooks"; + +import { ParentMap } from "./map"; +import { MARKER_LAYER } from "./marker_data"; + +export default function Tooltips() { + const map = useContext(ParentMap); + + useEffect(() => { + if (!map) return; + + const tooltip = new Popup({ + closeButton: false, + closeOnClick: false, + offset: 12, + className: "marker-tooltip" + }); + + function onMouseEnter(e: MapMouseEvent) { + const feature = e.features[0]; + tooltip + .setLngLat(feature.geometry.coordinates) + .setHTML(`${feature.properties.name}`) + .addTo(map); + } + + function onMouseLeave() { + tooltip.remove(); + } + + map.on("mouseenter", MARKER_LAYER, onMouseEnter); + map.on("mouseleave", MARKER_LAYER, onMouseLeave); + + return () => { + map.off("mouseenter", MARKER_LAYER, onMouseEnter); + map.off("mouseleave", MARKER_LAYER, onMouseLeave); + }; + }, [ map ]); + + return null; +} diff --git a/apps/client/src/widgets/collections/geomap/index.tsx b/apps/client/src/widgets/collections/geomap/index.tsx index deb326ce4e..7076c08274 100644 --- a/apps/client/src/widgets/collections/geomap/index.tsx +++ b/apps/client/src/widgets/collections/geomap/index.tsx @@ -25,6 +25,7 @@ import Map, { GeoMouseEvent } from "./map"; import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, MapLayer } from "./map_layer"; import Marker, { GpxTrack } from "./marker"; import { MARKER_LAYER, MARKER_SVG, useMarkerData } from "./marker_data"; +import Tooltips from "./Tooltips"; const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; const DEFAULT_ZOOM = 2; @@ -119,7 +120,6 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM const containerRef = useRef(null); const apiRef = useRef(null); useMarkerData(note, apiRef); - useHoverTooltip(containerRef, apiRef); useNoteTreeDrag(containerRef, { dragEnabled: !isReadOnly, dragNotEnabledMessage: { @@ -178,7 +178,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM onContextMenu={onContextMenu} scale={hasScale} > - {/* {notes.map(note => )} */} + } @@ -207,40 +207,6 @@ function useLayerData(note: FNote) { return layerData; } -function useHoverTooltip(containerRef: RefObject, mapRef: RefObject) { - useEffect(() => { - const map = mapRef.current; - if (!map) return; - - const tooltip = new Popup({ - closeButton: false, - closeOnClick: false, - offset: 12, - className: "marker-tooltip" - }); - - function onMouseEnter(e: MapMouseEvent) { - const feature = e.features[0]; - tooltip - .setLngLat(feature.geometry.coordinates) - .setHTML(`${feature.properties.name}`) - .addTo(map); - } - - function onMouseLeave() { - tooltip.remove(); - } - - map.on("mouseenter", MARKER_LAYER, onMouseEnter); - map.on("mouseleave", MARKER_LAYER, onMouseLeave); - - return () => { - map.off("mouseenter", MARKER_LAYER, onMouseEnter); - map.off("mouseleave", MARKER_LAYER, onMouseLeave); - }; - }, [ mapRef ]); -} - function ToggleReadOnlyButton({ note }: { note: FNote }) { const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); diff --git a/apps/client/src/widgets/collections/geomap/map.tsx b/apps/client/src/widgets/collections/geomap/map.tsx index a79031e827..53d90e41ba 100644 --- a/apps/client/src/widgets/collections/geomap/map.tsx +++ b/apps/client/src/widgets/collections/geomap/map.tsx @@ -2,7 +2,7 @@ import "maplibre-gl/dist/maplibre-gl.css"; import maplibregl, { NavigationControl, type Point } from "maplibre-gl"; import { ComponentChildren, createContext, RefObject } from "preact"; -import { useEffect, useImperativeHandle, useRef } from "preact/hooks"; +import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks"; import { useElementSize, useSyncedRef } from "../../react/hooks"; import { MapLayer } from "./map_layer"; @@ -38,10 +38,10 @@ function toMapLibreEvent(e: maplibregl.MapMouseEvent): GeoMouseEvent { } export default function Map({ coordinates, zoom, layerData, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) { - const mapRef = useRef(null); + const [ map, setMap ] = useState(null); const containerRef = useSyncedRef(_containerRef); - useImperativeHandle(apiRef ?? null, () => mapRef.current); + useImperativeHandle(apiRef ?? null, () => map); // Initialize the map. useEffect(() => { @@ -93,7 +93,7 @@ export default function Map({ coordinates, zoom, layerData, viewportChanged, chi showZoom: true }), "top-left"); - mapRef.current = mapInstance; + setMap(mapInstance); // Load async vector style if needed. if (layerData.type === "vector" && typeof layerData.style !== "string") { @@ -104,13 +104,12 @@ export default function Map({ coordinates, zoom, layerData, viewportChanged, chi return () => { mapInstance.remove(); - mapRef.current = null; + setMap(null); }; }, []); // React to layer changes. useEffect(() => { - const map = mapRef.current; if (!map) return; if (layerData.type === "vector") { @@ -141,23 +140,21 @@ export default function Map({ coordinates, zoom, layerData, viewportChanged, chi ] }); } - }, [ layerData ]); + }, [ map, layerData ]); // React to coordinate changes. useEffect(() => { - if (!mapRef.current) return; - + if (!map) return; const center = Array.isArray(coordinates) ? [coordinates[1], coordinates[0]] as [number, number] : [coordinates.lng, coordinates.lat] as [number, number]; - mapRef.current.setCenter(center); - mapRef.current.setZoom(zoom); - }, [ coordinates, zoom ]); + map.setCenter(center); + map.setZoom(zoom); + }, [ map, coordinates, zoom ]); // Viewport callback. useEffect(() => { - const map = mapRef.current; if (!map) return; const updateFn = () => { @@ -169,19 +166,17 @@ export default function Map({ coordinates, zoom, layerData, viewportChanged, chi return () => { map.off("moveend", updateFn); }; - }, [ viewportChanged ]); + }, [ map, viewportChanged ]); useEffect(() => { - const map = mapRef.current; if (!onClick || !map) return; const handler = (e: maplibregl.MapMouseEvent) => onClick(toMapLibreEvent(e)); map.on("click", handler); return () => { map.off("click", handler); }; - }, [ onClick ]); + }, [ map, onClick ]); useEffect(() => { - const map = mapRef.current; if (!onContextMenu || !map) return; const handler = (e: maplibregl.MapMouseEvent) => { @@ -190,37 +185,35 @@ export default function Map({ coordinates, zoom, layerData, viewportChanged, chi }; map.on("contextmenu", handler); return () => { map.off("contextmenu", handler); }; - }, [ onContextMenu ]); + }, [ map, onContextMenu ]); useEffect(() => { - const map = mapRef.current; if (!onZoom || !map) return; map.on("zoom", onZoom); return () => { map.off("zoom", onZoom); }; - }, [ onZoom ]); + }, [ map, onZoom ]); // Scale useEffect(() => { - const map = mapRef.current; if (!scale || !map) return; const scaleControl = new maplibregl.ScaleControl(); map.addControl(scaleControl); return () => { map.removeControl(scaleControl); }; - }, [ scale ]); + }, [ map, scale ]); // Adapt to container size changes. const size = useElementSize(containerRef); useEffect(() => { - mapRef.current?.resize(); - }, [ size?.width, size?.height ]); + map?.resize(); + }, [ map, size?.width, size?.height ]); return (
- + {children}