diff --git a/src/components/LayerControl.jsx b/src/components/LayerControl.jsx index c67b5ae..ee41ccb 100644 --- a/src/components/LayerControl.jsx +++ b/src/components/LayerControl.jsx @@ -1,222 +1,227 @@ -import { useState, useEffect, useRef } from 'react' -import { Layers, Trees, Mountain } from 'lucide-react' -import { hasFeature, getConfig } from '../config' - -const STORAGE_KEY = 'navi-layer-prefs' - -function loadPrefs() { - try { - const raw = localStorage.getItem(STORAGE_KEY) - if (raw) return JSON.parse(raw) - } catch {} - return null -} - -function savePrefs(prefs) { - localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs)) -} - -export default function LayerControl({ mapRef }) { - const [open, setOpen] = useState(false) - const [hillshade, setHillshade] = useState(false) - const [traffic, setTraffic] = useState(false) - const [publicLands, setPublicLands] = useState(false) - const [contours, setContours] = useState(false) - const [contoursTest, setContoursTest] = useState(false) - const [contoursTest10ft, setContoursTest10ft] = useState(false) +import { useState, useEffect, useRef } from 'react' +import { Layers, Map, Satellite, Globe } from 'lucide-react' +import { hasFeature, getConfig } from '../config' +import { useStore } from '../store' + +const STORAGE_KEY = 'navi-layer-prefs' + +function loadPrefs() { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (raw) return JSON.parse(raw) + } catch {} + return null +} + +function savePrefs(prefs) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs)) +} + +export default function LayerControl({ mapRef }) { + const [open, setOpen] = useState(false) + const [hillshade, setHillshade] = useState(false) + const [traffic, setTraffic] = useState(false) + const [publicLands, setPublicLands] = useState(false) + const [contours, setContours] = useState(false) + const [contoursTest, setContoursTest] = useState(false) + const [contoursTest10ft, setContoursTest10ft] = useState(false) const [usfsTrails, setUsfsTrails] = useState(false) - const [blmTrails, setBlmTrails] = useState(false) - const panelRef = useRef(null) - - // Initialize from localStorage or defaults on mount - useEffect(() => { - const saved = loadPrefs() - const hsAvailable = hasFeature('has_hillshade') - const trAvailable = hasFeature('has_traffic_overlay') - const plAvailable = hasFeature('has_public_lands_layer') - const ctAvailable = hasFeature('has_contours') - const ctTestAvailable = hasFeature('has_contours_test') - const ctTest10ftAvailable = hasFeature('has_contours_test_10ft') + const [blmTrails, setBlmTrails] = useState(false) + const panelRef = useRef(null) + + // View mode: map | satellite | hybrid + const viewMode = useStore((s) => s.viewMode) + const setViewMode = useStore((s) => s.setViewMode) + + // Initialize from localStorage or defaults on mount + useEffect(() => { + const saved = loadPrefs() + const hsAvailable = hasFeature('has_hillshade') + const trAvailable = hasFeature('has_traffic_overlay') + const plAvailable = hasFeature('has_public_lands_layer') + const ctAvailable = hasFeature('has_contours') + const ctTestAvailable = hasFeature('has_contours_test') + const ctTest10ftAvailable = hasFeature('has_contours_test_10ft') const usfsAvailable = hasFeature('has_usfs_trails') - const blmAvailable = hasFeature('has_blm_trails') - - if (saved) { - setHillshade(hsAvailable && (saved.hillshade ?? true)) - setTraffic(trAvailable && (saved.traffic ?? false)) - setPublicLands(plAvailable && (saved.publicLands ?? false)) - setContours(ctAvailable && (saved.contours ?? false)) - setContoursTest(ctTestAvailable && (saved.contoursTest ?? false)) - setContoursTest10ft(ctTest10ftAvailable && (saved.contoursTest10ft ?? false)) + const blmAvailable = hasFeature('has_blm_trails') + + if (saved) { + setHillshade(hsAvailable && (saved.hillshade ?? true)) + setTraffic(trAvailable && (saved.traffic ?? false)) + setPublicLands(plAvailable && (saved.publicLands ?? false)) + setContours(ctAvailable && (saved.contours ?? false)) + setContoursTest(ctTestAvailable && (saved.contoursTest ?? false)) + setContoursTest10ft(ctTest10ftAvailable && (saved.contoursTest10ft ?? false)) setUsfsTrails(usfsAvailable && (saved.usfsTrails ?? false)) - setBlmTrails(blmAvailable && (saved.blmTrails ?? false)) - } else { - // Defaults: hillshade ON if available, others OFF - setHillshade(hsAvailable) - setTraffic(false) - setPublicLands(false) - setContours(false) - setContoursTest(false) - setContoursTest10ft(false) - setUsfsTrails(false) - } - }, []) - - // Apply layers when prefs change - useEffect(() => { - const mapView = mapRef?.current - if (!mapView) return - const map = mapView.getMap?.() - if (!map) return - - const apply = () => { - if (hillshade && hasFeature('has_hillshade')) { - mapView.addHillshadeLayer?.() - } else { - mapView.removeHillshadeLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) - return () => map.off('style.load', apply) - }, [hillshade, mapRef]) - - useEffect(() => { - const mapView = mapRef?.current - if (!mapView) return - const map = mapView.getMap?.() - if (!map) return - - const apply = () => { - if (traffic && hasFeature('has_traffic_overlay')) { - mapView.addTrafficLayer?.() - } else { - mapView.removeTrafficLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) - return () => map.off('style.load', apply) - }, [traffic, mapRef]) - - useEffect(() => { - const mapView = mapRef?.current - if (!mapView) return - const map = mapView.getMap?.() - if (!map) return - - const apply = () => { - if (publicLands && hasFeature('has_public_lands_layer')) { - mapView.addPublicLandsLayer?.() - } else { - mapView.removePublicLandsLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) - return () => map.off('style.load', apply) - }, [publicLands, mapRef]) - - useEffect(() => { - const mapView = mapRef?.current - if (!mapView) return - const map = mapView.getMap?.() - if (!map) return - - const apply = () => { - if (contours && hasFeature('has_contours')) { - mapView.addContoursLayer?.() - } else { - mapView.removeContoursLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) - return () => map.off('style.load', apply) - }, [contours, mapRef]) - - useEffect(() => { - const mapView = mapRef?.current - if (!mapView) return - const map = mapView.getMap?.() - if (!map) return - - const apply = () => { - if (contoursTest && hasFeature('has_contours_test')) { - mapView.addContoursTestLayer?.() - } else { - mapView.removeContoursTestLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) - return () => map.off('style.load', apply) - }, [contoursTest, mapRef]) - - // Apply contoursTest10ft layer - useEffect(() => { - const map = mapRef.current?.getMap?.() - if (!map) return - - const apply = () => { - if (contoursTest10ft && hasFeature('has_contours_test_10ft')) { - mapRef.current?.addContoursTest10ftLayer?.() - } else { - mapRef.current?.removeContoursTest10ftLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - }, [contoursTest10ft, mapRef]) - - // Apply usfsTrails layer - useEffect(() => { - const map = mapRef.current?.getMap?.() - if (!map) return - - const apply = () => { - if (usfsTrails && hasFeature('has_usfs_trails')) { - mapRef.current?.addUsfsTrailsLayer?.() - } else { - mapRef.current?.removeUsfsTrailsLayer?.() - } - } - - if (map.isStyleLoaded()) { - apply() - } else { - map.once('style.load', apply) - } - savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) - }, [usfsTrails, mapRef]) + setBlmTrails(blmAvailable && (saved.blmTrails ?? false)) + } else { + // Defaults: hillshade ON if available, others OFF + setHillshade(hsAvailable) + setTraffic(false) + setPublicLands(false) + setContours(false) + setContoursTest(false) + setContoursTest10ft(false) + setUsfsTrails(false) + } + }, []) + + // Apply layers when prefs change + useEffect(() => { + const mapView = mapRef?.current + if (!mapView) return + const map = mapView.getMap?.() + if (!map) return + + const apply = () => { + if (hillshade && hasFeature('has_hillshade')) { + mapView.addHillshadeLayer?.() + } else { + mapView.removeHillshadeLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) + return () => map.off('style.load', apply) + }, [hillshade, mapRef]) + + useEffect(() => { + const mapView = mapRef?.current + if (!mapView) return + const map = mapView.getMap?.() + if (!map) return + + const apply = () => { + if (traffic && hasFeature('has_traffic_overlay')) { + mapView.addTrafficLayer?.() + } else { + mapView.removeTrafficLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) + return () => map.off('style.load', apply) + }, [traffic, mapRef]) + + useEffect(() => { + const mapView = mapRef?.current + if (!mapView) return + const map = mapView.getMap?.() + if (!map) return + + const apply = () => { + if (publicLands && hasFeature('has_public_lands_layer')) { + mapView.addPublicLandsLayer?.() + } else { + mapView.removePublicLandsLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) + return () => map.off('style.load', apply) + }, [publicLands, mapRef]) + + useEffect(() => { + const mapView = mapRef?.current + if (!mapView) return + const map = mapView.getMap?.() + if (!map) return + + const apply = () => { + if (contours && hasFeature('has_contours')) { + mapView.addContoursLayer?.() + } else { + mapView.removeContoursLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) + return () => map.off('style.load', apply) + }, [contours, mapRef]) + + useEffect(() => { + const mapView = mapRef?.current + if (!mapView) return + const map = mapView.getMap?.() + if (!map) return + + const apply = () => { + if (contoursTest && hasFeature('has_contours_test')) { + mapView.addContoursTestLayer?.() + } else { + mapView.removeContoursTestLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) + return () => map.off('style.load', apply) + }, [contoursTest, mapRef]) + + // Apply contoursTest10ft layer + useEffect(() => { + const map = mapRef.current?.getMap?.() + if (!map) return + + const apply = () => { + if (contoursTest10ft && hasFeature('has_contours_test_10ft')) { + mapRef.current?.addContoursTest10ftLayer?.() + } else { + mapRef.current?.removeContoursTest10ftLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + }, [contoursTest10ft, mapRef]) + + // Apply usfsTrails layer + useEffect(() => { + const map = mapRef.current?.getMap?.() + if (!map) return + + const apply = () => { + if (usfsTrails && hasFeature('has_usfs_trails')) { + mapRef.current?.addUsfsTrailsLayer?.() + } else { + mapRef.current?.removeUsfsTrailsLayer?.() + } + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) + }, [usfsTrails, mapRef]) // Apply blmTrails layer useEffect(() => { @@ -238,129 +243,176 @@ export default function LayerControl({ mapRef }) { } savePrefs({ hillshade, traffic, publicLands, contours, contoursTest, contoursTest10ft, usfsTrails, blmTrails }) }, [blmTrails, mapRef]) - - // Close on outside click - useEffect(() => { - if (!open) return - function handleClick(e) { - if (panelRef.current && !panelRef.current.contains(e.target)) { - setOpen(false) - } - } - document.addEventListener('pointerdown', handleClick) - return () => document.removeEventListener('pointerdown', handleClick) - }, [open]) - - const showHillshade = hasFeature('has_hillshade') - const showTraffic = hasFeature('has_traffic_overlay') - const showPublicLands = hasFeature('has_public_lands_layer') - const showContours = hasFeature('has_contours') - const showContoursTest = hasFeature('has_contours_test') - const showContoursTest10ft = hasFeature('has_contours_test_10ft') + + // Apply view mode changes + useEffect(() => { + const mapView = mapRef?.current + if (!mapView) return + const map = mapView.getMap?.() + if (!map) return + + const apply = () => { + mapView.setViewMode?.(viewMode) + } + + if (map.isStyleLoaded()) { + apply() + } else { + map.once('style.load', apply) + } + return () => map.off('style.load', apply) + }, [viewMode, mapRef]) + + // Close on outside click + useEffect(() => { + if (!open) return + function handleClick(e) { + if (panelRef.current && !panelRef.current.contains(e.target)) { + setOpen(false) + } + } + document.addEventListener('pointerdown', handleClick) + return () => document.removeEventListener('pointerdown', handleClick) + }, [open]) + + const showHillshade = hasFeature('has_hillshade') + const showTraffic = hasFeature('has_traffic_overlay') + const showPublicLands = hasFeature('has_public_lands_layer') + const showContours = hasFeature('has_contours') + const showContoursTest = hasFeature('has_contours_test') + const showContoursTest10ft = hasFeature('has_contours_test_10ft') const showUsfsTrails = hasFeature('has_usfs_trails') - const showBlmTrails = hasFeature('has_blm_trails') - - // Don't render if no overlay features available - if (!showHillshade && !showTraffic && !showPublicLands && !showContours && !showContoursTest && !showContoursTest10ft && !showUsfsTrails && !showBlmTrails) return null - - return ( -