fix(collections/map): tooltips not always available on first reload

This commit is contained in:
Elian Doran
2026-02-16 19:40:45 +02:00
parent c7842c7f05
commit 71e9eae5e6
3 changed files with 62 additions and 61 deletions

View File

@@ -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(`<strong>${feature.properties.name}</strong>`)
.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;
}

View File

@@ -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<HTMLDivElement>(null);
const apiRef = useRef<maplibregl.Map | null>(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 => <NoteWrapper note={note} isReadOnly={isReadOnly} hideLabels={hideLabels} />)} */}
<Tooltips />
</Map>}
<GeoMapTouchBar state={state} map={apiRef.current} />
</div>
@@ -207,40 +207,6 @@ function useLayerData(note: FNote) {
return layerData;
}
function useHoverTooltip(containerRef: RefObject<HTMLDivElement>, mapRef: RefObject<maplibregl.Map | null>) {
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(`<strong>${feature.properties.name}</strong>`)
.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");

View File

@@ -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<maplibregl.Map>(null);
const [ map, setMap ] = useState<maplibregl.Map | null>(null);
const containerRef = useSyncedRef<HTMLDivElement>(_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 (
<div
ref={containerRef}
className={`geo-map-container ${layerData.isDarkTheme ? "dark" : ""}`}
>
<ParentMap.Provider value={mapRef.current}>
<ParentMap.Provider value={map}>
{children}
</ParentMap.Provider>
</div>