Skip to content

Commit

Permalink
feat: preds overlay. (#196)
Browse files Browse the repository at this point in the history
* feat: pred overlay setting.
  • Loading branch information
morganney committed Feb 2, 2024
1 parent 0d6d9cb commit 1996368
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 39 deletions.
1 change: 1 addition & 0 deletions packages/common/src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ type PredictionFormat = 'time' | 'minutes'

interface RiderSettings {
predsFormat?: PredictionFormat
predsPersistentOverlay?: boolean
vehicleVisible?: boolean
vehicleSpeedUnit?: SpeedUnit
vehicleColorPredicted?: boolean
Expand Down
7 changes: 6 additions & 1 deletion packages/ui/src/components/formItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ const getJustifyContent = ({ direction, justifyContent }: FormItemProps) => {
const Label = styled.label<LabelProps>`
display: flex;
flex-direction: ${getFlexDirection};
gap: 4px;
gap: 5px;
font-size: ${({ fontSize }) => fontSize};
align-items: center;
span:first-child {
font-weight: ${({ fontWeight }) => fontWeight ?? 600};
Expand All @@ -94,6 +95,10 @@ const Wrap = styled.div<FormItemProps>`
gap: ${getGap};
font-size: ${({ fontSize }) => fontSize};
input[type='radio'] {
margin: 0;
}
> div:last-child {
display: flex;
align-items: center;
Expand Down
50 changes: 27 additions & 23 deletions packages/ui/src/home.tsx → packages/ui/src/components/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,27 @@ import { toast } from '@busmap/components/toast'
import { PB50T, PB90T, PB10T } from '@busmap/components/colors'
import styled from 'styled-components'

import { useGlobals } from './globals.js'
import { usePredictions } from './contexts/predictions.js'
import { useVehiclesDispatch } from './contexts/vehicles.js'
import { useTheme } from './modules/settings/contexts/theme.js'
import { Location } from './modules/location/components/location.js'
import { Settings } from './modules/settings/components/settings.js'
import { Favorites } from './modules/favorites/components/favorites.js'
import { SignIn } from './components/signIn.js'
import { Profile } from './components/profile.js'
import { BusmapPage } from './components/busmap.js'
import { BusSelector } from './components/busSelector.js'
import { Loading } from './components/loading.js'
import { Predictions } from './components/predictions.js'
import { ErrorAgencies } from './components/error/agencies.js'
import { EmptyMap } from './components/emptyMap.js'
import { getAll as getAllAgencies } from './api/rb/agency.js'
import { getAll as getAllVehicles } from './api/rb/vehicles.js'
import { getForStop } from './api/rb/predictions.js'
import { useHomeStop } from './hooks/useHomeStop.js'
import { SignIn } from './signIn.js'
import { Profile } from './profile.js'
import { BusmapPage } from './busmap.js'
import { BusSelector } from './busSelector.js'
import { Loading } from './loading.js'
import { Predictions } from './predictions.js'
import { PredictionsOverlay } from './predictionsOverlay.js'
import { ErrorAgencies } from './error/agencies.js'
import { EmptyMap } from './emptyMap.js'

import { useGlobals } from '../globals.js'
import { usePredictions } from '../contexts/predictions.js'
import { useVehiclesDispatch } from '../contexts/vehicles.js'
import { useTheme } from '../modules/settings/contexts/theme.js'
import { Location } from '../modules/location/components/location.js'
import { Settings } from '../modules/settings/components/settings.js'
import { Favorites } from '../modules/favorites/components/favorites.js'
import { getAll as getAllAgencies } from '../api/rb/agency.js'
import { getAll as getAllVehicles } from '../api/rb/vehicles.js'
import { getForStop } from '../api/rb/predictions.js'
import { useHomeStop } from '../hooks/useHomeStop.js'

import type { FC } from 'react'
import type { Mode } from '@busmap/common/types/settings'
Expand Down Expand Up @@ -56,13 +58,14 @@ const reducer = (state: HomeState, action: HomeAction) => {
const Aside = styled.aside<{ mode: Mode; collapsed: boolean }>`
position: fixed;
top: 0;
left: 49px;
z-index: 999;
left: 48px;
z-index: 9999;
height: 100%;
width: calc(100% - 49px);
width: calc(100% - 48px);
max-width: 385px;
background: ${({ mode }) => (mode === 'light' ? '#ffffffcc' : `${PB10T}cc`)};
transform: ${({ collapsed }) => (!collapsed ? 'translateX(0)' : 'translateX(-100%)')};
transform: ${({ collapsed }) =>
!collapsed ? 'translateX(0)' : 'translateX(calc(-100% - 48px))'};
transition: transform 0.25s ease;
@media (width >= 431px) and (height >= 536px) {
Expand Down Expand Up @@ -248,6 +251,7 @@ const Home: FC = () => {
<Loading text="Loading agencies" />
)}
</Aside>
<PredictionsOverlay preds={preds} stop={stop} />
{!homeStop && <EmptyMap />}
</>
)
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const Nav = styled.nav<{ mode: Mode; isSignedIn: boolean }>`
position: fixed;
left: 0;
height: 100%;
z-index: 9999;
z-index: 99999;
background: ${({ mode }) => (mode === 'light' ? PB90T : DARK_MODE_FIELD)};
ul {
Expand Down
169 changes: 169 additions & 0 deletions packages/ui/src/components/predictionsOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import styled from 'styled-components'
import { useCallback } from 'react'
import { latLng } from 'leaflet'
import { MapMarked } from '@busmap/components/icons/mapMarked'
import { PB20T } from '@busmap/components/colors'

import { Time } from './predictionFormats/time.js'
import { Minutes } from './predictionFormats/minutes.js'

import { useGlobals } from '../globals.js'
import { useMap } from '../contexts/map.js'
import { useVehicles } from '../contexts/vehicles.js'
import { useTheme } from '../modules/settings/contexts/theme.js'
import { usePredictionsSettings } from '../modules/settings/contexts/predictions.js'
import { useVehicleSettings } from '../modules/settings/contexts/vehicle.js'
import { PredictedVehiclesColors, blinkStyles } from '../common.js'

import type { FC } from 'react'
import type { Prediction, Stop } from '@core/types'
import type { Mode } from '@busmap/common/types/settings'

interface PredictionsOverlayProps {
preds?: Prediction[]
stop?: Stop
}

const Section = styled.section<{ $mode: Mode }>`
position: fixed;
right: 8px;
top: 8px;
z-index: 999;
display: flex;
flex-direction: column;
gap: 8px;
min-width: 100px;
max-width: 132px;
h3 {
margin: 0;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
color: ${({ $mode }) => ($mode === 'light' ? 'black' : 'white')};
}
@media (width > 431px) {
right: 24px;
top: 24px;
max-width: 175px;
}
`
const Preds = styled.ul`
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 12px;
`
const Pred = styled.li<{ markPredictedVehicles: boolean; $mode: Mode }>`
button {
color: ${({ markPredictedVehicles }) => (markPredictedVehicles ? 'white' : PB20T)};
text-shadow: ${({ markPredictedVehicles }) =>
markPredictedVehicles ? `0 0 2px ${PB20T}` : 'none'};
}
&:last-child button {
background-color: ${({ markPredictedVehicles }) =>
markPredictedVehicles ? PredictedVehiclesColors.red : '#ffffffcc'};
}
&:first-child button {
background-color: ${({ markPredictedVehicles }) =>
markPredictedVehicles ? PredictedVehiclesColors.green : '#ffffffcc'};
}
&:nth-child(2) button {
background-color: ${({ markPredictedVehicles }) =>
markPredictedVehicles ? PredictedVehiclesColors.yellow : '#ffffffcc'};
}
`
const Button = styled.button`
padding: 4px 8px;
margin: 0;
cursor: pointer;
border: none;
font-size: 1.6rem;
font-weight: bold;
min-height: 32px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 3px;
em {
${blinkStyles};
}
`
const PredictionsOverlay: FC<PredictionsOverlayProps> = ({ preds, stop }) => {
const map = useMap()
const { collapsed, dispatch } = useGlobals()
const { mode } = useTheme()
const vehicles = useVehicles()
const { format, persistentOverlay } = usePredictionsSettings()
const { markPredictedVehicles } = useVehicleSettings()
const onClickPred = useCallback(
(vid: string) => {
if (Array.isArray(vehicles)) {
const vehicle = vehicles.find(({ id }) => id === vid)

if (vehicle) {
dispatch({ type: 'predForVeh', value: vehicle })
}
}
},
[dispatch, vehicles]
)
const onClickTitle = useCallback(() => {
if (map && stop) {
const { lat, lon } = stop
const latLon = latLng(lat, lon)

map.setView(latLon, Math.max(map.getZoom(), 16))
}
}, [stop, map])

if ((!collapsed && !persistentOverlay) || !preds?.length || !stop) {
return null
}

const values = preds[0].values.slice(0, 3)
const title = values[0].isDeparture ? 'Departures' : 'Arrivals'
const event = values[0].isDeparture ? 'Departing' : 'Arriving'
const Format = format === 'minutes' ? Minutes : Time

return (
<Section $mode={mode}>
<h3>
<span>{title}</span>
<MapMarked onClick={onClickTitle} color={mode === 'light' ? 'black' : 'white'} />
</h3>
<Preds>
{values.map(({ minutes, epochTime, affectedByLayover, vehicle }) => (
<Pred
key={`${epochTime}-${vehicle.id}`}
$mode={mode}
markPredictedVehicles={markPredictedVehicles}>
<Button onClick={() => onClickPred(vehicle.id)}>
{minutes === 0 ? (
<em>{event}</em>
) : (
<Format
minutes={minutes}
epochTime={epochTime}
affectedByLayover={affectedByLayover}
/>
)}
</Button>
</Pred>
))}
</Preds>
</Section>
)
}

export { PredictionsOverlay }
24 changes: 24 additions & 0 deletions packages/ui/src/contexts/storage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ interface PredsFormatUpdate {
type: 'predsFormat'
value?: PredictionFormat
}
interface PredsPersistentOverlayUpdate {
type: 'predsPersistentOverlay'
value: boolean
}
interface VehicleSpeedUnitUpdate {
type: 'vehicleSpeedUnit'
value?: SpeedUnit
Expand Down Expand Up @@ -57,6 +61,7 @@ interface StorageSettingsChanged {
}
type StorageAction =
| PredsFormatUpdate
| PredsPersistentOverlayUpdate
| VehicleVisible
| VehicleSpeedUnitUpdate
| VehicleColorPredictedUpdate
Expand Down Expand Up @@ -97,6 +102,8 @@ const reducer = (state: StorageState, action: StorageAction) => {
}
case 'predsFormat':
return { ...state, predsFormat: action.value }
case 'predsPersistentOverlay':
return { ...state, predsPersistentOverlay: action.value }
case 'vehicleVisible':
return { ...state, vehicleVisible: action.value }
case 'vehicleSpeedUnit':
Expand All @@ -116,6 +123,7 @@ const KEYS = {
vehicleSpeedUnit: 'busmap-vehicleSpeedUnit',
vehicleColorPredicted: 'busmap-vehicleColorPredicted',
predsFormat: 'busmap-predsFormat',
predsPersistentOverlay: 'busmap-predsPersistentOverlay',
favorites: 'busmap-favorites'
}
const initStorageState = { favorites: [] }
Expand All @@ -127,6 +135,7 @@ const init = (state: StorageState): StorageState => {
const vehicleSpeedUnit = localStorage.getItem(KEYS.vehicleSpeedUnit)
const vehicleColorPredicted = localStorage.getItem(KEYS.vehicleColorPredicted)
const predsFormat = localStorage.getItem(KEYS.predsFormat)
const predsPersistentOverlay = localStorage.getItem(KEYS.predsPersistentOverlay)
const favoritesJson = localStorage.getItem(KEYS.favorites)

if (isAMode(themeMode)) {
Expand All @@ -137,6 +146,10 @@ const init = (state: StorageState): StorageState => {
state.predsFormat = predsFormat
}

if (predsPersistentOverlay !== null) {
state.predsPersistentOverlay = predsPersistentOverlay !== 'false'
}

if (isASpeedUnit(vehicleSpeedUnit)) {
state.vehicleSpeedUnit = vehicleSpeedUnit
}
Expand Down Expand Up @@ -191,6 +204,17 @@ const StorageProvider: FC<{ children: ReactNode }> = ({ children }) => {
}
}, [storage.predsFormat])

useEffect(() => {
if (storage.predsPersistentOverlay !== undefined) {
localStorage.setItem(
KEYS.predsPersistentOverlay,
storage.predsPersistentOverlay.toString()
)
} else {
localStorage.removeItem(KEYS.predsPersistentOverlay)
}
}, [storage.predsPersistentOverlay])

useEffect(() => {
if (storage.vehicleSpeedUnit) {
localStorage.setItem(KEYS.vehicleSpeedUnit, storage.vehicleSpeedUnit)
Expand Down
13 changes: 5 additions & 8 deletions packages/ui/src/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,20 @@ const Layout: FC<LayoutProps> = ({ children }) => {
}
}, 100)
}
const loadedListener = () => {
document.body.classList.add('busmap-loaded')
}

if (map) {
map.on('load', loadedListener)
map.on('move', moveListener)
map.on('moveend', moveEndListener)
orientationMql.addEventListener('change', orientationListener)
}

return () => {
if (map) {
map.off('load', loadedListener)
map.off('move', moveListener)
map.off('moveend', moveEndListener)
orientationMql.removeEventListener('change', orientationListener)
Expand All @@ -63,14 +68,6 @@ const Layout: FC<LayoutProps> = ({ children }) => {
document.body.classList.add(`busmap-${mode}`)
}, [mode])

useLayoutEffect(() => {
if (map) {
map.on('load', () => {
document.body.classList.add('busmap-loaded')
})
}
}, [map])

useRouteLayer({ routeLayer, map, popup })
useVehiclesLayer({ vehiclesLayer })
useZoomSelectedStop({ map })
Expand Down
Loading

0 comments on commit 1996368

Please sign in to comment.