diff options
| author | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-24 19:33:49 +0100 |
|---|---|---|
| committer | Ariel Costas Guerrero <ariel@costas.dev> | 2025-12-24 19:33:49 +0100 |
| commit | cfbb1625e7873264e2ef435cc76fec2b59cf58d8 (patch) | |
| tree | 092e04e7750064f5ed1bf6aa2ea625c87877e2e8 /src/frontend/app/routes | |
| parent | 9ed46bea58dbb81ceada2a957fd1db653fb21e52 (diff) | |
Refactor map components and improve modal functionality
Diffstat (limited to 'src/frontend/app/routes')
| -rw-r--r-- | src/frontend/app/routes/map.tsx | 96 | ||||
| -rw-r--r-- | src/frontend/app/routes/planner.tsx | 20 | ||||
| -rw-r--r-- | src/frontend/app/routes/settings.tsx | 42 | ||||
| -rw-r--r-- | src/frontend/app/routes/stops-$id.tsx | 7 |
4 files changed, 57 insertions, 108 deletions
diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index 517549b..45dd935 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -1,20 +1,17 @@ import StopDataProvider from "../data/StopDataProvider"; import "./map.css"; -import { DEFAULT_STYLE, loadStyle } from "app/maps/styleloader"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import Map, { - GeolocateControl, +import { Layer, - NavigationControl, Source, type MapLayerMouseEvent, type MapRef, - type StyleSpecification, } from "react-map-gl/maplibre"; import { useNavigate } from "react-router"; import { PlannerOverlay } from "~/components/PlannerOverlay"; +import { AppMap } from "~/components/shared/AppMap"; import { StopSummarySheet, type StopSheetProps, @@ -34,14 +31,10 @@ export default function StopMap() { StopSheetProps["stop"] | null >(null); const [isSheetOpen, setIsSheetOpen] = useState(false); - const { mapState, updateMapState, theme } = useApp(); const mapRef = useRef<MapRef>(null); const { searchRoute } = usePlanner(); - // Style state for Map component - const [mapStyle, setMapStyle] = useState<StyleSpecification>(DEFAULT_STYLE); - // Handle click events on clusters and individual stops const onMapClick = (e: MapLayerMouseEvent) => { const features = e.features; @@ -59,63 +52,6 @@ export default function StopMap() { handlePointClick(feature); }; - useEffect(() => { - //const styleName = "carto"; - const styleName = "openfreemap"; - loadStyle(styleName, theme) - .then((style) => setMapStyle(style)) - .catch((error) => console.error("Failed to load map style:", error)); - }, [theme]); - - useEffect(() => { - const handleMapChange = () => { - if (!mapRef.current) return; - const map = mapRef.current.getMap(); - if (!map) return; - const center = map.getCenter(); - const zoom = map.getZoom(); - updateMapState([center.lat, center.lng], zoom); - }; - - const handleStyleImageMissing = (e: any) => { - // Suppress warnings for missing sprite images from base style - // This prevents console noise from OpenFreeMap's missing icons - if (!mapRef.current) return; - const map = mapRef.current.getMap(); - if (!map || map.hasImage(e.id)) return; - - // Log warning for our own icons if they are missing - if (e.id.startsWith("stop-")) { - console.warn(`Missing icon image: ${e.id}`); - } - - // Add a transparent 1x1 placeholder to prevent repeated warnings - map.addImage(e.id, { - width: 1, - height: 1, - data: new Uint8Array(4), - }); - }; - - if (mapRef.current) { - const map = mapRef.current.getMap(); - if (map) { - map.on("moveend", handleMapChange); - map.on("styleimagemissing", handleStyleImageMissing); - } - } - - return () => { - if (mapRef.current) { - const map = mapRef.current.getMap(); - if (map) { - map.off("moveend", handleMapChange); - map.off("styleimagemissing", handleStyleImageMissing); - } - } - }; - }, [mapRef.current]); - const getLatitude = (center: any) => Array.isArray(center) ? center[0] : center.lat; const getLongitude = (center: any) => @@ -166,31 +102,15 @@ export default function StopMap() { cardBackground="bg-white/95 dark:bg-slate-900/90" /> - <Map - mapStyle={mapStyle} - style={{ width: "100%", height: "100%" }} + <AppMap + ref={mapRef} + syncState={true} + showNavigation={true} + showGeolocate={true} interactiveLayerIds={["stops", "stops-label"]} onClick={onMapClick} - minZoom={5} - scrollZoom - pitch={0} - roll={0} - ref={mapRef} - initialViewState={{ - latitude: getLatitude(mapState.center), - longitude: getLongitude(mapState.center), - zoom: mapState.zoom, - }} attributionControl={{ compact: false }} - maxBounds={[APP_CONSTANTS.bounds.sw, APP_CONSTANTS.bounds.ne]} > - <NavigationControl position="bottom-right" /> - <GeolocateControl - position="bottom-right" - trackUserLocation={true} - positionOptions={{ enableHighAccuracy: false }} - /> - <Source id="stops-source" type="vector" @@ -284,7 +204,7 @@ export default function StopMap() { stop={selectedStop} /> )} - </Map> + </AppMap> </div> ); } diff --git a/src/frontend/app/routes/planner.tsx b/src/frontend/app/routes/planner.tsx index 3d0f703..e99cb03 100644 --- a/src/frontend/app/routes/planner.tsx +++ b/src/frontend/app/routes/planner.tsx @@ -3,17 +3,17 @@ import maplibregl, { type StyleSpecification } from "maplibre-gl"; import "maplibre-gl/dist/maplibre-gl.css"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; -import Map, { Layer, Source, type MapRef } from "react-map-gl/maplibre"; +import { Layer, Source, type MapRef } from "react-map-gl/maplibre"; import { useLocation } from "react-router"; import { useApp } from "~/AppContext"; import LineIcon from "~/components/LineIcon"; import { PlannerOverlay } from "~/components/PlannerOverlay"; +import { AppMap } from "~/components/shared/AppMap"; import { APP_CONSTANTS } from "~/config/constants"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { type Itinerary } from "~/data/PlannerApi"; import { usePlanner } from "~/hooks/usePlanner"; -import { DEFAULT_STYLE, loadStyle } from "~/maps/styleloader"; import "../tailwind-full.css"; export interface ConsolidatedCirculation { @@ -371,16 +371,6 @@ const ItineraryDetail = ({ return () => clearTimeout(timer); }, [mapRef.current, itinerary]); - const { theme } = useApp(); - const [mapStyle, setMapStyle] = useState<StyleSpecification>(DEFAULT_STYLE); - - useEffect(() => { - const styleName = "openfreemap"; - loadStyle(styleName, theme, { includeTraffic: false }) - .then((style) => setMapStyle(style)) - .catch((error) => console.error("Failed to load map style:", error)); - }, [theme]); - // Fetch next arrivals for bus legs useEffect(() => { const fetchArrivals = async () => { @@ -420,7 +410,7 @@ const ItineraryDetail = ({ <div className="flex flex-col md:flex-row h-full"> {/* Map Section */} <div className="relative h-2/3 md:h-full md:flex-1"> - <Map + <AppMap ref={mapRef} initialViewState={{ longitude: @@ -431,7 +421,7 @@ const ItineraryDetail = ({ (APP_CONSTANTS.defaultCenter as [number, number])[1], zoom: 13, }} - mapStyle={mapStyle} + showTraffic={false} attributionControl={false} > <Source id="route" type="geojson" data={routeGeoJson as any}> @@ -565,7 +555,7 @@ const ItineraryDetail = ({ }} /> </Source> - </Map> + </AppMap> <button onClick={onClose} diff --git a/src/frontend/app/routes/settings.tsx b/src/frontend/app/routes/settings.tsx index c615844..f51b2e9 100644 --- a/src/frontend/app/routes/settings.tsx +++ b/src/frontend/app/routes/settings.tsx @@ -7,7 +7,16 @@ import "../tailwind-full.css"; export default function Settings() { const { t, i18n } = useTranslation(); usePageTitle(t("navbar.settings", "Ajustes")); - const { theme, setTheme, mapPositionMode, setMapPositionMode } = useApp(); + const { + theme, + setTheme, + mapPositionMode, + setMapPositionMode, + showTraffic, + setShowTraffic, + showCameras, + setShowCameras, + } = useApp(); const THEMES = [ { @@ -83,6 +92,37 @@ export default function Settings() { </select> </section> + {/* Map Layers */} + <section className="mb-8"> + <h2 className="text-xl font-semibold mb-4 text-text"> + {t("about.map_layers", "Capas del mapa")} + </h2> + <div className="space-y-4"> + <label className="flex items-center justify-between p-4 rounded-lg border border-border bg-surface cursor-pointer hover:bg-surface/50 transition-colors"> + <span className="text-text font-medium"> + {t("about.show_traffic", "Mostrar tráfico")} + </span> + <input + type="checkbox" + checked={showTraffic} + onChange={(e) => setShowTraffic(e.target.checked)} + className="w-5 h-5 rounded border-border text-primary focus:ring-primary/50" + /> + </label> + <label className="flex items-center justify-between p-4 rounded-lg border border-border bg-surface cursor-pointer hover:bg-surface/50 transition-colors"> + <span className="text-text font-medium"> + {t("about.show_cameras", "Mostrar cámaras")} + </span> + <input + type="checkbox" + checked={showCameras} + onChange={(e) => setShowCameras(e.target.checked)} + className="w-5 h-5 rounded border-border text-primary focus:ring-primary/50" + /> + </label> + </div> + </section> + {/* Language Selection */} <section> <label diff --git a/src/frontend/app/routes/stops-$id.tsx b/src/frontend/app/routes/stops-$id.tsx index 9147302..7aba9f2 100644 --- a/src/frontend/app/routes/stops-$id.tsx +++ b/src/frontend/app/routes/stops-$id.tsx @@ -8,9 +8,8 @@ import { ArrivalList } from "~/components/arrivals/ArrivalList"; import { ErrorDisplay } from "~/components/ErrorDisplay"; import LineIcon from "~/components/LineIcon"; import { PullToRefresh } from "~/components/PullToRefresh"; -import { StopHelpModal } from "~/components/StopHelpModal"; -import { StopMapModal } from "~/components/StopMapModal"; -import { ConsolidatedCirculationListSkeleton } from "~/components/Stops/ConsolidatedCirculationListSkeleton"; +import { StopHelpModal } from "~/components/stop/StopHelpModal"; +import { StopMapModal } from "~/components/stop/StopMapModal"; import { usePageTitle } from "~/contexts/PageTitleContext"; import { useAutoRefresh } from "~/hooks/useAutoRefresh"; import StopDataProvider from "../data/StopDataProvider"; @@ -163,7 +162,7 @@ export default function Estimates() { <div className="estimates-list-container"> {dataLoading ? ( - <ConsolidatedCirculationListSkeleton /> + <>{/*TODO: New loading skeleton*/}</> ) : dataError ? ( <ErrorDisplay error={dataError} |
