@@ -122,35 +122,17 @@ export default function App() {
122122 useAreasLayer ( map , settings . drawAreasEnabled , query . customModelStr , query . customModelEnabled )
123123 useRoutingGraphLayer ( map , mapOptions . routingGraphEnabled )
124124 useUrbanDensityLayer ( map , mapOptions . urbanDensityEnabled )
125- const [ showPaths , setShowPaths ] = useState ( true )
125+ type PathDisplayMode = 'normal' | 'incline' | 'hidden'
126+ const [ pathDisplayMode , setPathDisplayMode ] = useState < PathDisplayMode > ( 'normal' )
127+ const showPaths = pathDisplayMode !== 'hidden'
128+ const inclineOnMap = pathDisplayMode === 'incline'
126129 usePathsLayer ( map , route . routingResult . paths , route . selectedPath , query . queryPoints , showPaths )
127130 useQueryPointsLayer ( map , query . queryPoints )
128131 const [ activeDetail , setActiveDetail ] = useState < ChartPathDetail | null > ( null )
129132 usePathDetailsLayer ( map , pathDetails , activeDetail , showPaths )
130133 usePOIsLayer ( map , pois )
131134 useCurrentLocationLayer ( map , currentLocation )
132135
133- useEffect ( ( ) => {
134- const handleKeyDown = ( e : KeyboardEvent ) => {
135- if ( e . key === 'h' ) setShowPaths ( false )
136- }
137- const handleKeyUp = ( e : KeyboardEvent ) => {
138- if ( e . key === 'h' ) setShowPaths ( true )
139- }
140-
141- const viewport = map . getViewport ( )
142- if ( ! viewport ) return
143-
144- viewport . tabIndex = - 1 // Make element focusable but not in tab order
145-
146- window . addEventListener ( 'keydown' , handleKeyDown )
147- window . addEventListener ( 'keyup' , handleKeyUp )
148- return ( ) => {
149- window . removeEventListener ( 'keydown' , handleKeyDown )
150- window . removeEventListener ( 'keyup' , handleKeyUp )
151- }
152- } , [ ] )
153-
154136 const isSmallScreen = useMediaQuery ( { query : '(max-width: 44rem)' } )
155137 return (
156138 < SettingsContext . Provider value = { settings } >
@@ -174,6 +156,8 @@ export default function App() {
174156 drawAreas = { settings . drawAreasEnabled }
175157 currentLocation = { currentLocation }
176158 onActiveDetailChanged = { setActiveDetail }
159+ pathDisplayMode = { pathDisplayMode }
160+ onCyclePathDisplay = { ( ) => setPathDisplayMode ( m => m === 'normal' ? 'incline' : m === 'incline' ? 'hidden' : 'normal' ) }
177161 />
178162 ) : (
179163 < LargeScreenLayout
@@ -186,13 +170,39 @@ export default function App() {
186170 drawAreas = { settings . drawAreasEnabled }
187171 currentLocation = { currentLocation }
188172 onActiveDetailChanged = { setActiveDetail }
173+ pathDisplayMode = { pathDisplayMode }
174+ onCyclePathDisplay = { ( ) => setPathDisplayMode ( m => m === 'normal' ? 'incline' : m === 'incline' ? 'hidden' : 'normal' ) }
189175 />
190176 ) }
191177 </ div >
192178 </ SettingsContext . Provider >
193179 )
194180}
195181
182+ function InclineIcon ( { mode } : { mode : 'normal' | 'incline' | 'hidden' } ) {
183+ if ( mode === 'incline' ) return (
184+ < svg viewBox = "0 0 14 14" fill = "none" >
185+ < polyline points = "3,11 5.5,5 8,9 11,3" stroke = "#2E7D32" strokeWidth = "1.2" fill = "none" />
186+ < circle cx = "3" cy = "11" r = "1.5" fill = "#2E7D32" />
187+ < circle cx = "11" cy = "3" r = "1.5" fill = "#F44336" />
188+ </ svg >
189+ )
190+ if ( mode === 'hidden' ) return (
191+ < svg viewBox = "0 0 14 14" fill = "none" >
192+ < polyline points = "3,11 5.5,5 8,9 11,3" stroke = "gray" strokeWidth = "1.2" fill = "none" opacity = "0.3" />
193+ < circle cx = "3" cy = "11" r = "1.5" fill = "gray" />
194+ < circle cx = "11" cy = "3" r = "1.5" fill = "gray" />
195+ </ svg >
196+ )
197+ return (
198+ < svg viewBox = "0 0 14 14" fill = "none" >
199+ < polyline points = "3,11 5.5,5 8,9 11,3" stroke = "gray" strokeWidth = "1.2" fill = "none" />
200+ < circle cx = "3" cy = "11" r = "1.5" fill = "gray" />
201+ < circle cx = "11" cy = "3" r = "1.5" fill = "gray" />
202+ </ svg >
203+ )
204+ }
205+
196206interface LayoutProps {
197207 query : QueryStoreState
198208 route : RouteStoreState
@@ -203,6 +213,8 @@ interface LayoutProps {
203213 encodedValues : object [ ]
204214 drawAreas : boolean
205215 onActiveDetailChanged : ( detail : ChartPathDetail | null ) => void
216+ pathDisplayMode : 'normal' | 'incline' | 'hidden'
217+ onCyclePathDisplay : ( ) => void
206218}
207219
208220function LargeScreenLayout ( {
@@ -215,7 +227,10 @@ function LargeScreenLayout({
215227 drawAreas,
216228 currentLocation,
217229 onActiveDetailChanged,
230+ pathDisplayMode,
231+ onCyclePathDisplay,
218232} : LayoutProps ) {
233+ const inclineOnMap = pathDisplayMode === 'incline'
219234 const [ showSidebar , setShowSidebar ] = useState ( true )
220235 const [ showCustomModelBox , setShowCustomModelBox ] = useState ( false )
221236 const [ elevationState , setElevationState ] = useState < 'compact' | 'expanded' | 'closed' > ( 'closed' )
@@ -285,6 +300,15 @@ function LargeScreenLayout({
285300 < div className = { styles . onMapRightSide } >
286301 < MapOptions { ...mapOptions } />
287302 < LocationButton currentLocation = { currentLocation } />
303+ { hasRoute && (
304+ < div
305+ className = { styles . inclineButton + ( pathDisplayMode === 'incline' ? ' ' + styles . inclineButtonActive : '' ) }
306+ onClick = { onCyclePathDisplay }
307+ title = { pathDisplayMode === 'normal' ? 'Show incline on map' : pathDisplayMode === 'incline' ? 'Hide path' : 'Show path' }
308+ >
309+ < InclineIcon mode = { pathDisplayMode } />
310+ </ div >
311+ ) }
288312 </ div >
289313 < div className = { styles . map } >
290314 < MapComponent map = { map } />
@@ -313,6 +337,7 @@ function LargeScreenLayout({
313337 onToggleExpanded = { ( ) => setElevationState ( s => s === 'expanded' ? 'compact' : 'expanded' ) }
314338 onClose = { ( ) => setElevationState ( 'closed' ) }
315339 onActiveDetailChanged = { onActiveDetailChanged }
340+ inclineOnMap = { inclineOnMap }
316341 />
317342 </ div >
318343 </ >
@@ -329,7 +354,10 @@ function SmallScreenLayout({
329354 drawAreas,
330355 currentLocation,
331356 onActiveDetailChanged,
357+ pathDisplayMode,
358+ onCyclePathDisplay,
332359} : LayoutProps ) {
360+ const inclineOnMap = pathDisplayMode === 'incline'
333361 const hasPath = route . selectedPath . points . coordinates . length > 0
334362 const elevationWidget = hasPath ? (
335363 < ElevationInfoBar
@@ -339,6 +367,7 @@ function SmallScreenLayout({
339367 isExpanded = { false }
340368 onToggleExpanded = { ( ) => { } }
341369 onActiveDetailChanged = { onActiveDetailChanged }
370+ inclineOnMap = { inclineOnMap }
342371 />
343372 ) : undefined
344373 return (
@@ -360,6 +389,15 @@ function SmallScreenLayout({
360389 < div className = { styles . onMapRightSide } >
361390 < MapOptions { ...mapOptions } />
362391 < LocationButton currentLocation = { currentLocation } />
392+ { hasPath && (
393+ < div
394+ className = { styles . inclineButton + ( pathDisplayMode === 'incline' ? ' ' + styles . inclineButtonActive : '' ) }
395+ onClick = { onCyclePathDisplay }
396+ title = { pathDisplayMode === 'normal' ? 'Show incline on map' : pathDisplayMode === 'incline' ? 'Hide path' : 'Show path' }
397+ >
398+ < InclineIcon mode = { pathDisplayMode } />
399+ </ div >
400+ ) }
363401 </ div >
364402 </ div >
365403
0 commit comments