diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-11-19 15:04:55 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-11-19 15:05:34 +0100 |
| commit | d51169f6411b68a226d76d2d39826904de484929 (patch) | |
| tree | 4d8a403dfcc5b17671a92b8cc1e5d71d20ed9537 /src/frontend/app/contexts/MapContext.tsx | |
| parent | d434204860fc0409ad6343e815d0057b97ce3573 (diff) | |
feat: Add About and Favourites pages, update routing and context management
- Added new routes for About and Favourites pages.
- Implemented About page with version information and credits.
- Created Favourites page with a placeholder message for empty favourites.
- Refactored RegionConfig import paths for consistency.
- Introduced PageTitleContext to manage page titles dynamically.
- Updated various components to utilize the new context for setting page titles.
- Enhanced AppShell layout with a responsive Drawer for navigation.
- Added CSS styles for new components and pages.
- Integrated commit hash display in the About page for version tracking.
Diffstat (limited to 'src/frontend/app/contexts/MapContext.tsx')
| -rw-r--r-- | src/frontend/app/contexts/MapContext.tsx | 151 |
1 files changed, 151 insertions, 0 deletions
diff --git a/src/frontend/app/contexts/MapContext.tsx b/src/frontend/app/contexts/MapContext.tsx new file mode 100644 index 0000000..b47b67f --- /dev/null +++ b/src/frontend/app/contexts/MapContext.tsx @@ -0,0 +1,151 @@ +import { type LngLatLike } from "maplibre-gl"; +import { + createContext, + useContext, + useEffect, + useState, + type ReactNode, +} from "react"; +import { getRegionConfig } from "../config/RegionConfig"; +import { useSettings } from "./SettingsContext"; + +interface MapState { + center: LngLatLike; + zoom: number; + userLocation: LngLatLike | null; + hasLocationPermission: boolean; +} + +interface MapContextProps { + mapState: MapState; + setMapCenter: (center: LngLatLike) => void; + setMapZoom: (zoom: number) => void; + setUserLocation: (location: LngLatLike | null) => void; + setLocationPermission: (hasPermission: boolean) => void; + updateMapState: (center: LngLatLike, zoom: number) => void; +} + +const MapContext = createContext<MapContextProps | undefined>(undefined); + +export const MapProvider = ({ children }: { children: ReactNode }) => { + const { region } = useSettings(); + const [prevRegion, setPrevRegion] = useState(region); + + const [mapState, setMapState] = useState<MapState>(() => { + const savedMapState = localStorage.getItem("mapState"); + 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. + const regionConfig = getRegionConfig(region); + return { + center: parsed.center || regionConfig.defaultCenter, + zoom: parsed.zoom || regionConfig.defaultZoom, + userLocation: parsed.userLocation || null, + hasLocationPermission: parsed.hasLocationPermission || false, + }; + } catch (e) { + console.error("Error parsing saved map state", e); + } + } + const regionConfig = getRegionConfig(region); + return { + center: regionConfig.defaultCenter, + zoom: regionConfig.defaultZoom, + userLocation: null, + hasLocationPermission: false, + }; + }); + + const setMapCenter = (center: LngLatLike) => { + setMapState((prev) => { + const newState = { ...prev, center }; + localStorage.setItem("mapState", JSON.stringify(newState)); + return newState; + }); + }; + + const setMapZoom = (zoom: number) => { + setMapState((prev) => { + const newState = { ...prev, zoom }; + localStorage.setItem("mapState", JSON.stringify(newState)); + return newState; + }); + }; + + const setUserLocation = (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 updateMapState = (center: LngLatLike, zoom: number) => { + setMapState((prev) => { + const newState = { ...prev, center, zoom }; + localStorage.setItem("mapState", JSON.stringify(newState)); + return newState; + }); + }; + + // Sync map state when region changes + useEffect(() => { + if (region !== prevRegion) { + const regionConfig = getRegionConfig(region); + updateMapState(regionConfig.defaultCenter, regionConfig.defaultZoom); + setPrevRegion(region); + } + }, [region, prevRegion]); + + // Try to get user location on load if permission was 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); + setLocationPermission(false); + } + ); + } + } + }, [mapState.hasLocationPermission, mapState.userLocation]); + + return ( + <MapContext.Provider + value={{ + mapState, + setMapCenter, + setMapZoom, + setUserLocation, + setLocationPermission, + updateMapState, + }} + > + {children} + </MapContext.Provider> + ); +}; + +export const useMap = () => { + const context = useContext(MapContext); + if (!context) { + throw new Error("useMap must be used within a MapProvider"); + } + return context; +}; |
