Skip to content

Commit

Permalink
feat: favorites preds data. (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
morganney authored Oct 30, 2023
1 parent 30a7661 commit 7c61b84
Show file tree
Hide file tree
Showing 15 changed files with 2,107 additions and 3,493 deletions.
5,085 changes: 1,653 additions & 3,432 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"dependencies": {
"@busmap/components": "^1.0.0-alpha.0",
"leaflet": "^1.9.4",
"lodash.debounce": "^4.0.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-query": "^3.39.3",
Expand Down
29 changes: 29 additions & 0 deletions packages/ui/src/api/rb/tuples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ROOT } from './common.js'

import { transport } from '../transport.js'
import { errors } from '../errors.js'

import type { Prediction } from '../../types.js'

const getForTuples = async (
agencyId?: string,
tuples?: string[],
signal?: AbortSignal
) => {
if (!agencyId || !tuples) {
throw errors.create('GET', 400, 'Bad Request')
}

const preds = await transport.fetch<Prediction[]>(
`${ROOT}/agencies/${agencyId}/tuples/${tuples.join(',')}/predictions`,
{ signal }
)

return preds.map(pred => {
delete pred._links

return pred
})
}

export { getForTuples }
17 changes: 17 additions & 0 deletions packages/ui/src/components/predictionFormats/minutes.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { FC } from 'react'

interface MinutesProps {
affectedByLayover: boolean
minutes: number
}

const Minutes: FC<MinutesProps> = ({ minutes, affectedByLayover }) => {
return (
<time dateTime={`PT${minutes}M`}>
{minutes} min{affectedByLayover && <sup>*</sup>}
</time>
)
}

export { Minutes }
export type { MinutesProps }
23 changes: 23 additions & 0 deletions packages/ui/src/components/predictionFormats/time.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { FC } from 'react'

interface TimeProps {
epochTime: number
affectedByLayover: boolean
minutes: number
}

const Time: FC<TimeProps> = ({ epochTime, affectedByLayover }) => {
const date = new Date(epochTime)
const dateTime = date.toISOString()
const time = date.toLocaleTimeString([], { timeStyle: 'short' })

return (
<time dateTime={dateTime}>
{time}
{affectedByLayover && <sup>*</sup>}
</time>
)
}

export { Time }
export type { TimeProps }
12 changes: 5 additions & 7 deletions packages/ui/src/components/predictions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,15 @@ const Time: FC<FormatProps> = ({ epochTime, affectedByLayover }) => {
const time = date.toLocaleTimeString([], { timeStyle: 'short' })

return (
<time key={epochTime} dateTime={dateTime}>
<time dateTime={dateTime}>
{time}
{affectedByLayover && <sup>*</sup>}
</time>
)
}
const Minutes: FC<FormatProps> = ({ epochTime, minutes, affectedByLayover }) => {
const Minutes: FC<FormatProps> = ({ minutes, affectedByLayover }) => {
return (
<time key={epochTime} dateTime={`PT${minutes}M`}>
<time dateTime={`PT${minutes}M`}>
{minutes} min{affectedByLayover && <sup>*</sup>}
</time>
)
Expand Down Expand Up @@ -229,21 +229,19 @@ const Predictions: FC<PredictionsProps> = ({ preds, stop, messages, timestamp })
<List markPredictedVehicles={markPredictedVehicles} mode={mode}>
{values.map(
({ minutes, epochTime, direction, affectedByLayover, vehicle }) => (
<li key={epochTime}>
<li key={`${epochTime}-${vehicle.id}`}>
{minutes === 0 ? (
<em key={epochTime}>{event}</em>
<em>{event}</em>
) : markPredictedVehicles ? (
<VehicleLocator vehicleId={vehicle.id}>
<Format
key={epochTime}
minutes={minutes}
epochTime={epochTime}
affectedByLayover={affectedByLayover}
/>
</VehicleLocator>
) : (
<Format
key={epochTime}
minutes={minutes}
epochTime={epochTime}
affectedByLayover={affectedByLayover}
Expand Down
28 changes: 9 additions & 19 deletions packages/ui/src/contexts/storage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createContext, useContext, useEffect, useReducer, useMemo } from 'react'

import { same } from '../modules/favorites/util.js'
import { isAMode, isASpeedUnit, isAPredictionFormat } from '../modules/settings/util.js'

import type { FC, ReactNode, Dispatch } from 'react'
Expand All @@ -11,7 +12,7 @@ interface StorageState {
vehicleSpeedUnit?: SpeedUnit
vehicleColorPredicted?: boolean
themeMode?: Mode
favorites?: Favorite[]
favorites: Favorite[]
}
interface PredsFormatUpdate {
type: 'predsFormat'
Expand Down Expand Up @@ -61,17 +62,9 @@ const reducer = (state: StorageState, action: StorageAction) => {
}
case 'favoriteRemove': {
if (Array.isArray(state.favorites)) {
const { value } = action
const { route, direction, stop } = value
const toRemove = `${route.id}${direction.id}${stop.id}`

return {
...state,
favorites: state.favorites.filter(fav => {
const thisFav = `${fav.route.id}${fav.direction.id}${fav.stop.id}`

return toRemove !== thisFav
})
favorites: state.favorites.filter(fav => !same(fav, action.value))
}
}

Expand All @@ -94,10 +87,10 @@ const KEYS = {
predsFormat: 'busmap-predsFormat',
favorites: 'busmap-favorites'
}
const initStorageState = { favorites: [] }
const StorageDispatch = createContext<Dispatch<StorageAction>>(() => {})
const Storage = createContext<StorageState>({})
const init = (): StorageState => {
const state: StorageState = {}
const Storage = createContext<StorageState>(initStorageState)
const init = (state: StorageState): StorageState => {
const themeMode = localStorage.getItem(KEYS.themeMode)
const vehicleSpeedUnit = localStorage.getItem(KEYS.vehicleSpeedUnit)
const vehicleColorPredicted = localStorage.getItem(KEYS.vehicleColorPredicted)
Expand All @@ -121,23 +114,21 @@ const init = (): StorageState => {
}

if (favoritesJson) {
let favorites: Favorite[] | null = null
let favorites: Favorite[] = []

try {
favorites = JSON.parse(favoritesJson) as Favorite[]
} catch (err) {
// Ignore
}

if (favorites) {
state.favorites = favorites
}
state.favorites = favorites
}

return state
}
const StorageProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [storage, dispatch] = useReducer(reducer, {}, init)
const [storage, dispatch] = useReducer(reducer, initStorageState, init)
const context = useMemo(() => storage, [storage])

useEffect(() => {
Expand Down Expand Up @@ -197,4 +188,3 @@ const useStorageDispatch = () => {
}

export { StorageProvider, useStorage, useStorageDispatch }
export type { Favorite }
2 changes: 1 addition & 1 deletion packages/ui/src/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const Home: FC<HomeProps> = () => {
{
enabled: Boolean(agency) && Boolean(route),
refetchOnWindowFocus: false,
refetchInterval: 5_000,
refetchInterval: 10_000,
onSuccess(data) {
vehiclesDispatch({ type: 'set', value: data })

Expand Down
24 changes: 12 additions & 12 deletions packages/ui/src/modules/favorites/components/favoriteStop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { SY30T } from '@busmap/components/colors'

import { useGlobals } from '../../../globals.js'
import { useStorage, useStorageDispatch } from '../../../contexts/storage.js'
import { same } from '../util.js'

import type { FC } from 'react'
import type { Favorite } from '../../../contexts/storage.js'
import type { Favorite } from '../types.js'

const Tip = styled(Tooltip)`
display: flex;
Expand All @@ -19,35 +20,34 @@ const Button = styled.button`
margin: 0;
background: none;
`
const worker = new Worker(new URL('../worker.ts', import.meta.url), {
type: 'module'
})
const FavoriteStop: FC = () => {
const storage = useStorage()
const storageDispatch = useStorageDispatch()
const { agency, route, direction, stop } = useGlobals()
const favorite = useMemo(() => {
return storage.favorites?.find(fav => {
return (
`${fav.route.id}${fav.direction.id}${fav.stop.id}` ===
`${route?.id}${direction?.id}${stop?.id}`
)
if (route && direction && stop && agency) {
return same(fav, { agency, route, direction, stop })
}
})
}, [storage.favorites, stop, route, direction])
}, [storage.favorites, agency, stop, route, direction])
const onClick = useCallback(() => {
if (favorite) {
storageDispatch({ type: 'favoriteRemove', value: favorite })
worker.postMessage({ action: 'stop', favorite })
} else if (agency && route && direction && stop) {
const add: Favorite = {
stop: stop,
agency: { id: agency.id, title: agency.title, region: agency.region },
route: { id: route.id, title: route.title ?? route.shortTitle },
route: {
id: route.id,
title: route.title ?? route.shortTitle,
color: route.color,
textColor: route.textColor
},
direction: { id: direction.id, title: direction.title ?? direction.shortTitle }
}

storageDispatch({ type: 'favoriteAdd', value: add })
worker.postMessage({ action: 'start', favorite: add })
}
}, [storageDispatch, agency, route, direction, stop, favorite])

Expand Down
Loading

0 comments on commit 7c61b84

Please sign in to comment.