Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: vehicle speed unit setting. #37

Merged
merged 2 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/ui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RouterProvider } from 'react-router-dom'

import { GlobalsProvider } from './globals.js'
import { VehiclesProvider } from './contexts/vehicles.js'
import { SettingsProvider } from './contexts/settings/index.js'
import { router } from './router.js'

import type { FC } from 'react'
Expand All @@ -19,9 +20,11 @@ const BusMap: FC = () => {
return (
<QueryClientProvider client={queryClient}>
<GlobalsProvider>
<VehiclesProvider>
<RouterProvider router={router} />
</VehiclesProvider>
<SettingsProvider>
<VehiclesProvider>
<RouterProvider router={router} />
</VehiclesProvider>
</SettingsProvider>
</GlobalsProvider>
</QueryClientProvider>
)
Expand Down
7 changes: 1 addition & 6 deletions packages/ui/src/components/busSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const BusSelector = memo(function BusSelector({ agencies }: BusSelectorProps) {
const navigate = useNavigate()
const bookmark = useBusSelectorBookmark()
const [routeName, setRouteName] = useState<RouteName>()
const { dispatch, markPredictedVehicles, agency, route, direction, stop } = useGlobals()
const { dispatch, agency, route, direction, stop } = useGlobals()
const vehiclesDispatch = useVehiclesDispatch()
const stops = useMemo(() => {
if (direction && route) {
Expand Down Expand Up @@ -177,9 +177,6 @@ const BusSelector = memo(function BusSelector({ agencies }: BusSelectorProps) {
)
}
}, [dispatch, vehiclesDispatch, navigate, agency, route, direction])
const onTogglePredictedVehicles = useCallback(() => {
dispatch({ type: 'markPredictedVehicles', value: !markPredictedVehicles })
}, [dispatch, markPredictedVehicles])
const error = getFirstDataError([routesError, routeError])
const isLoading = isRoutesLoading || isRouteLoading

Expand Down Expand Up @@ -232,8 +229,6 @@ const BusSelector = memo(function BusSelector({ agencies }: BusSelectorProps) {
selected={stop}
onClear={onClearStop}
onSelect={onSelectStop}
markPredictedVehicles={markPredictedVehicles}
onTogglePredictedVehicles={onTogglePredictedVehicles}
isDisabled={isLoading || !agency || !route || !direction}
/>
</Form>
Expand Down
16 changes: 8 additions & 8 deletions packages/ui/src/components/predictions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import styled, { keyframes } from 'styled-components'
import { PB50T, PB80T } from '@busmap/components/colors'

import { PredictedVehiclesColors } from '../utils.js'
import { useVehicleSettings } from '../contexts/settings/vehicle.js'

import type { FC } from 'react'
import type { Prediction, Stop } from '../types.js'
Expand All @@ -12,7 +13,6 @@ interface PredictionsProps {
isFetching: boolean
timestamp: number
messages: Prediction['messages']
markPredictedVehicles: boolean
}

const blink = keyframes`
Expand Down Expand Up @@ -115,9 +115,11 @@ const List = styled.ul<{ markPredictedVehicles: boolean }>`
}
`
const AffectedByLayover = styled.details`
display: inline-block;
margin: 0 0 12px 0;
summary:first-of-type {
font-size: 12px;
cursor: pointer;
}
p {
line-height: 1.25;
Expand All @@ -130,10 +132,12 @@ const AffectedByLayover = styled.details`
}
`
const Messages = styled.details`
display: inline-block;
margin: 12px 0 0 0;
summary:first-of-type {
font-size: 12px;
color: blue;
cursor: pointer;
}

ul {
Expand All @@ -158,13 +162,9 @@ const Messages = styled.details`
}
}
`
const Predictions: FC<PredictionsProps> = ({
preds,
stop,
messages,
timestamp,
markPredictedVehicles = true
}) => {
const Predictions: FC<PredictionsProps> = ({ preds, stop, messages, timestamp }) => {
const { markPredictedVehicles } = useVehicleSettings()

if (Array.isArray(preds) && stop) {
if (preds.length) {
const values = preds[0].values.slice(0, 3)
Expand Down
49 changes: 15 additions & 34 deletions packages/ui/src/components/selectors/stops.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,38 @@ import { AutoSuggest } from '@busmap/components/autoSuggest'

import { FormItem } from '../formItem.js'

import type { FC, ChangeEvent } from 'react'
import type { FC } from 'react'
import type { Stop } from '../../types.js'

interface Props {
stops: Stop[]
selected?: Stop
isDisabled?: boolean
markPredictedVehicles: boolean
onClear?: (clearItem: () => void) => void
onSelect: (selected: Stop) => void
onTogglePredictedVehicles: (evt: ChangeEvent<HTMLInputElement>) => void
}
const Stops: FC<Props> = ({
stops,
selected,
onSelect,
onClear,
onTogglePredictedVehicles,
markPredictedVehicles = true,
isDisabled = Boolean(stops)
}) => {
return (
<>
<FormItem label="Stop">
<AutoSuggest
caseInsensitive
inputBoundByItems
size="small"
color="black"
value={selected ?? undefined}
isDisabled={isDisabled}
placeholder={`Stops ... ${stops.length ? `(${stops.length})` : ''}`}
items={stops ?? []}
onClear={onClear ?? true}
onSelect={onSelect}
/>
</FormItem>
<FormItem
label="Color predicted vehicles"
direction="horizontal-rev"
justifyContent="flex-end"
fontWeight="normal"
fontSize="12px"
grow={0}>
<input
type="checkbox"
checked={markPredictedVehicles}
onChange={onTogglePredictedVehicles}
/>
</FormItem>
</>
<FormItem label="Stop">
<AutoSuggest
caseInsensitive
inputBoundByItems
size="small"
color="black"
value={selected ?? undefined}
isDisabled={isDisabled}
placeholder={`Stops ... ${stops.length ? `(${stops.length})` : ''}`}
items={stops ?? []}
onClear={onClear ?? true}
onSelect={onSelect}
/>
</FormItem>
)
}

Expand Down
91 changes: 91 additions & 0 deletions packages/ui/src/components/settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import styled from 'styled-components'
import { useCallback } from 'react'

import { FormItem } from './formItem.js'

import { useSettings } from '../contexts/settings/index.js'

import type { FC, ChangeEvent } from 'react'
import type { SpeedUnit } from '../contexts/settings/index.js'

const Form = styled.form`
fieldset {
display: flex;
flex-direction: column;
gap: 20px;
padding: 10px;
}

fieldset.row {
flex-direction: row;
gap: 10px;
}
legend {
font-size: 14px;
line-height: 1;
}
`
const Settings: FC = () => {
const settings = useSettings()
const onTogglePredictedVehicles = useCallback(() => {
settings.vehicle.dispatch({
type: 'markPredictedVehicles',
value: !settings.vehicle.markPredictedVehicles
})
}, [settings.vehicle])
const onChangeSpeedUnit = useCallback(
(evt: ChangeEvent<HTMLInputElement>) => {
settings.vehicle.dispatch({
type: 'speedUnit',
value: evt.currentTarget.value as SpeedUnit
})
},
[settings.vehicle]
)

return (
<Form
onSubmit={evt => {
evt.preventDefault()
}}>
<fieldset>
<legend>Vehicles</legend>
<FormItem
label="Color predicted"
direction="horizontal-rev"
justifyContent="flex-end"
fontWeight="normal"
grow={0}>
<input
type="checkbox"
checked={settings.vehicle.markPredictedVehicles}
onChange={onTogglePredictedVehicles}
/>
</FormItem>
<fieldset className="row">
<legend>Speed units</legend>
<FormItem label="kph" fontWeight="normal" direction="horizontal">
<input
type="radio"
name="speedUnit"
value="kph"
checked={settings.vehicle.speedUnit === 'kph'}
onChange={onChangeSpeedUnit}
/>
</FormItem>
<FormItem label="mph" fontWeight="normal" direction="horizontal">
<input
type="radio"
name="speedUnit"
value="mph"
checked={settings.vehicle.speedUnit === 'mph'}
onChange={onChangeSpeedUnit}
/>
</FormItem>
</fieldset>
</fieldset>
</Form>
)
}

export { Settings }
14 changes: 14 additions & 0 deletions packages/ui/src/contexts/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { VehicleSettingsProvider, useVehicleSettings } from './vehicle.js'

import type { FC, ReactNode } from 'react'

const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => {
return <VehicleSettingsProvider>{children}</VehicleSettingsProvider>
}
const useSettings = () => {
return {
vehicle: useVehicleSettings()
}
}
export { SettingsProvider, useSettings }
export type { SpeedUnit } from './vehicle.js'
50 changes: 50 additions & 0 deletions packages/ui/src/contexts/settings/vehicle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { createContext, useContext, useReducer, useMemo } from 'react'

import type { FC, ReactNode, Dispatch } from 'react'

type SpeedUnit = 'kph' | 'mph'
interface VehicleSettingsState {
markPredictedVehicles: boolean
speedUnit: SpeedUnit
dispatch: Dispatch<VehicleSettingsAction>
}
interface MarkPredictedVehicles {
type: 'markPredictedVehicles'
value: boolean
}
interface SpeedUnitChanged {
type: 'speedUnit'
value: SpeedUnit
}
type VehicleSettingsAction = MarkPredictedVehicles | SpeedUnitChanged
const defaultState: VehicleSettingsState = {
dispatch: () => {},
speedUnit: 'kph',
markPredictedVehicles: true
}
const VehicleSettings = createContext<VehicleSettingsState>(defaultState)
const reducer = (state: VehicleSettingsState, action: VehicleSettingsAction) => {
switch (action.type) {
case 'speedUnit':
return { ...state, speedUnit: action.value }
case 'markPredictedVehicles':
return { ...state, markPredictedVehicles: action.value }
default:
return { ...defaultState, ...state }
}
}
const VehicleSettingsProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [vehicleSettings, dispatch] = useReducer(reducer, defaultState)
const context = useMemo(
() => ({ ...vehicleSettings, dispatch }),
[vehicleSettings, dispatch]
)

return <VehicleSettings.Provider value={context}>{children}</VehicleSettings.Provider>
}
const useVehicleSettings = () => {
return useContext(VehicleSettings)
}

export { VehicleSettingsProvider, useVehicleSettings }
export type { SpeedUnit }
3 changes: 0 additions & 3 deletions packages/ui/src/globals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ type BusmapState = Omit<BusmapGlobals, 'dispatch'>
const defaultGlobals = {
dispatch: () => {},
locationSettled: false,
markPredictedVehicles: true,
center: { lat: 37.7775, lon: -122.416389 },
bounds: {
sw: {
Expand Down Expand Up @@ -55,8 +54,6 @@ const reducer = (state: BusmapState, action: BusmapAction): BusmapState => {
return { ...state, stop: action.value, predictions: undefined }
case 'locationSettled':
return { ...state, locationSettled: action.value }
case 'markPredictedVehicles':
return { ...state, markPredictedVehicles: action.value }
case 'predictions':
return { ...state, predictions: action.value }
case 'selected':
Expand Down
Loading