aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/contexts/MapContext.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/frontend/app/contexts/MapContext.tsx')
-rw-r--r--src/frontend/app/contexts/MapContext.tsx159
1 files changed, 116 insertions, 43 deletions
diff --git a/src/frontend/app/contexts/MapContext.tsx b/src/frontend/app/contexts/MapContext.tsx
index f888f34..75851f4 100644
--- a/src/frontend/app/contexts/MapContext.tsx
+++ b/src/frontend/app/contexts/MapContext.tsx
@@ -1,8 +1,10 @@
import { type LngLatLike } from "maplibre-gl";
import {
createContext,
+ useCallback,
useContext,
useEffect,
+ useRef,
useState,
type ReactNode,
} from "react";
@@ -18,6 +20,7 @@ interface MapContextProps {
setUserLocation: (location: LngLatLike | null) => void;
setLocationPermission: (hasPermission: boolean) => void;
updateMapState: (center: LngLatLike, zoom: number, path: string) => void;
+ requestLocation: () => void;
}
const MapContext = createContext<MapContextProps | undefined>(undefined);
@@ -28,9 +31,6 @@ export const MapProvider = ({ children }: { children: ReactNode }) => {
if (savedMapState) {
try {
const parsed = JSON.parse(savedMapState);
- // Validate that the saved center is valid if needed, or just trust it.
- // We might want to ensure we have a fallback if the region changed while the app was closed?
- // But for now, let's stick to the existing logic.
return {
paths: parsed.paths || {},
userLocation: parsed.userLocation || null,
@@ -47,58 +47,130 @@ export const MapProvider = ({ children }: { children: ReactNode }) => {
};
});
- const setUserLocation = (userLocation: LngLatLike | null) => {
+ const watchIdRef = useRef<number | null>(null);
+
+ const setUserLocation = useCallback((userLocation: LngLatLike | null) => {
setMapState((prev) => {
const newState = { ...prev, userLocation };
localStorage.setItem("mapState", JSON.stringify(newState));
return newState;
});
- };
+ }, []);
- const setLocationPermission = (hasLocationPermission: boolean) => {
- setMapState((prev) => {
- const newState = { ...prev, hasLocationPermission };
- localStorage.setItem("mapState", JSON.stringify(newState));
- return newState;
- });
- };
+ const setLocationPermission = useCallback(
+ (hasLocationPermission: boolean) => {
+ setMapState((prev) => {
+ const newState = { ...prev, hasLocationPermission };
+ localStorage.setItem("mapState", JSON.stringify(newState));
+ return newState;
+ });
+ },
+ []
+ );
- const updateMapState = (center: LngLatLike, zoom: number, path: string) => {
- setMapState((prev) => {
- const newState = {
- ...prev,
- paths: {
- ...prev.paths,
- [path]: { center, zoom },
- },
- };
- localStorage.setItem("mapState", JSON.stringify(newState));
- return newState;
- });
- };
+ const updateMapState = useCallback(
+ (center: LngLatLike, zoom: number, path: string) => {
+ setMapState((prev) => {
+ const newState = {
+ ...prev,
+ paths: {
+ ...prev.paths,
+ [path]: { center, zoom },
+ },
+ };
+ localStorage.setItem("mapState", JSON.stringify(newState));
+ return newState;
+ });
+ },
+ []
+ );
+
+ const startWatching = useCallback(() => {
+ if (!navigator.geolocation || watchIdRef.current !== null) return;
+ watchIdRef.current = navigator.geolocation.watchPosition(
+ (position) => {
+ const { latitude, longitude } = position.coords;
+ setUserLocation([latitude, longitude]);
+ setLocationPermission(true);
+ },
+ (error) => {
+ if (error.code === GeolocationPositionError.PERMISSION_DENIED) {
+ setLocationPermission(false);
+ }
+ },
+ { enableHighAccuracy: false, maximumAge: 30000, timeout: 15000 }
+ );
+ }, [setUserLocation, setLocationPermission]);
+
+ const requestLocation = useCallback(() => {
+ if (typeof window === "undefined" || !("geolocation" in navigator)) return;
+ navigator.geolocation.getCurrentPosition(
+ (pos) => {
+ setUserLocation([pos.coords.latitude, pos.coords.longitude]);
+ setLocationPermission(true);
+ startWatching();
+ },
+ () => {
+ setLocationPermission(false);
+ },
+ { enableHighAccuracy: false, maximumAge: 60000, timeout: 10000 }
+ );
+ }, [setUserLocation, setLocationPermission, startWatching]);
- // Try to get user location on load if permission was granted
+ const hasPermissionRef = useRef(mapState.hasLocationPermission);
+
+ // On mount: subscribe to permission changes and auto-start watching if already granted
useEffect(() => {
- if (mapState.hasLocationPermission && !mapState.userLocation) {
- if (navigator.geolocation) {
- navigator.geolocation.getCurrentPosition(
- (position) => {
- const { latitude, longitude } = position.coords;
- setUserLocation([latitude, longitude]);
- },
- (error) => {
- console.error("Error getting location:", error);
+ if (typeof window === "undefined" || !("geolocation" in navigator)) return;
+
+ let permissionStatus: PermissionStatus | null = null;
+
+ const onPermChange = () => {
+ if (permissionStatus?.state === "granted") {
+ setLocationPermission(true);
+ startWatching();
+ } else if (permissionStatus?.state === "denied") {
+ setLocationPermission(false);
+ if (watchIdRef.current !== null) {
+ navigator.geolocation.clearWatch(watchIdRef.current);
+ watchIdRef.current = null;
+ }
+ }
+ };
+
+ const init = async () => {
+ try {
+ if (navigator.permissions?.query) {
+ permissionStatus = await navigator.permissions.query({
+ name: "geolocation",
+ });
+ if (permissionStatus.state === "granted") {
+ setLocationPermission(true);
+ startWatching();
+ } else if (permissionStatus.state === "denied") {
setLocationPermission(false);
- },
- {
- enableHighAccuracy: true,
- maximumAge: Infinity,
- timeout: 10000,
}
- );
+ permissionStatus.addEventListener("change", onPermChange);
+ } else if (hasPermissionRef.current) {
+ startWatching();
+ }
+ } catch {
+ if (hasPermissionRef.current) {
+ startWatching();
+ }
}
- }
- }, [mapState.hasLocationPermission, mapState.userLocation]);
+ };
+
+ init();
+
+ return () => {
+ if (watchIdRef.current !== null) {
+ navigator.geolocation.clearWatch(watchIdRef.current);
+ watchIdRef.current = null;
+ }
+ permissionStatus?.removeEventListener("change", onPermChange);
+ };
+ }, [startWatching, setLocationPermission]);
return (
<MapContext.Provider
@@ -107,6 +179,7 @@ export const MapProvider = ({ children }: { children: ReactNode }) => {
setUserLocation,
setLocationPermission,
updateMapState,
+ requestLocation,
}}
>
{children}