diff --git a/__tests__/components/viewers/__snapshots__/nearby-view.js.snap b/__tests__/components/viewers/__snapshots__/nearby-view.js.snap index 1a5af6b06..629be1f50 100644 --- a/__tests__/components/viewers/__snapshots__/nearby-view.js.snap +++ b/__tests__/components/viewers/__snapshots__/nearby-view.js.snap @@ -35,6 +35,7 @@ exports[`components > viewers > nearby view renders nothing on a blank page 1`] fetchNearby={[Function]} homeTimezone="America/Los_Angeles" nearby={Array []} + routeSortComparator={[Function]} setHighlightedLocation={[Function]} setLocation={[Function]} setMainPanelContent={[Function]} @@ -66,7 +67,7 @@ exports[`components > viewers > nearby view renders nothing on a blank page 1`] } >

viewers > nearby view renders nothing on a blank page 1`] } >
    -
    @@ -4078,6 +4076,7 @@ exports[`components > viewers > nearby view renders proper scooter dates 1`] = ` }, ] } + routeSortComparator={[Function]} setHighlightedLocation={[Function]} setLocation={[Function]} setMainPanelContent={[Function]} @@ -4109,7 +4108,7 @@ exports[`components > viewers > nearby view renders proper scooter dates 1`] = ` } >

    viewers > nearby view renders proper scooter dates 1`] = ` } >
      -
    1. viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.realtime" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > viewers > nearby view renders proper scooter dates 1`] = ` title="components.StopTimeCell.scheduled" > viewers > nearby view renders proper scooter dates 1`] = ` > { - const shouldUpdate = this.positionShouldUpdate(position) - if (shouldUpdate) { - receivedPositionResponse({ position }) - } - }, - // On error - (error) => { - console.log('error in watchPosition', error) - }, - // Options - { enableHighAccuracy: true } - ) - } - // If the path changes (e.g., via a back button press) check whether the // main content needs to switch between, for example, a viewer and a search. if (!isEqual(location.pathname, prevProps.location.pathname)) { @@ -192,6 +172,7 @@ class ResponsiveWebapp extends Component { map, matchContentToUrl, parseUrlQueryString, + receivedPositionResponse, setNetworkConnectionLost } = this.props // Add on back button press behavior. @@ -216,6 +197,21 @@ class ResponsiveWebapp extends Component { if (isMobile()) { // Test location availability on load getCurrentPosition(intl) + // Watch for position changing on mobile + navigator.geolocation.watchPosition( + // On success + (position) => { + if (this.positionShouldUpdate(position)) { + receivedPositionResponse({ position }) + } + }, + // On error + (error) => { + console.log('error in watchPosition', error) + }, + // Options + { enableHighAccuracy: true } + ) } // Handle routing to a specific part of the app (e.g. stop viewer) on page // load. (This happens prior to routing request in case special routerId is diff --git a/lib/components/viewers/nearby/nearby-view.tsx b/lib/components/viewers/nearby/nearby-view.tsx index 9c60ff677..be54085ae 100644 --- a/lib/components/viewers/nearby/nearby-view.tsx +++ b/lib/components/viewers/nearby/nearby-view.tsx @@ -2,14 +2,21 @@ import { connect } from 'react-redux' import { FormattedMessage, useIntl } from 'react-intl' import { Location } from '@opentripplanner/types' import { MapRef, useMap } from 'react-map-gl' -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import coreUtils from '@opentripplanner/core-utils' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import * as apiActions from '../../../actions/api' import * as mapActions from '../../../actions/map' import * as uiActions from '../../../actions/ui' import { AppReduxState } from '../../../util/state-types' import { getCurrentServiceWeek } from '../../../util/current-service-week' -import { SetLocationHandler, ZoomToPlaceHandler } from '../../util/types' +import { NearbyViewConfig } from '../../../util/config-types' +import { + PatternStopTime, + SetLocationHandler, + ZoomToPlaceHandler +} from '../../util/types' +import InvisibleA11yLabel from '../../util/invisible-a11y-label' import Loading from '../../narrative/loading' import MobileContainer from '../../mobile/container' import MobileNavigationBar from '../../mobile/navigation-bar' @@ -22,13 +29,12 @@ import { Scrollable } from './styled' import FromToPicker from './from-to-picker' -import InvisibleA11yLabel from '../../util/invisible-a11y-label' import RentalStation from './rental-station' -import Stop from './stop' +import Stop, { fullTimestamp, patternArrayforStops } from './stop' import Vehicle from './vehicle-rent' import VehicleParking from './vehicle-parking' -const AUTO_REFRESH_INTERVAL = 15000 +const AUTO_REFRESH_INTERVAL = 15000000 // TODO: use lonlat package type LatLonObj = { lat: number; lon: number } @@ -49,9 +55,12 @@ type Props = { hideBackButton?: boolean location: string mobile?: boolean + // Todo: type nearby results nearby: any + nearbyViewConfig?: NearbyViewConfig nearbyViewCoords?: LatLonObj radius?: number + routeSortComparator: (a: PatternStopTime, b: PatternStopTime) => number setHighlightedLocation: (location: Location | null) => void setLocation: SetLocationHandler setMainPanelContent: (content: number) => void @@ -118,8 +127,10 @@ function NearbyView({ location, mobile, nearby, + nearbyViewConfig, nearbyViewCoords, radius, + routeSortComparator, setHighlightedLocation, setMainPanelContent, setViewedNearbyCoords, @@ -128,7 +139,6 @@ function NearbyView({ const map = useMap().default const intl = useIntl() const [loading, setLoading] = useState(true) - const firstItemRef = useRef(null) const finalNearbyCoords = useMemo( () => getNearbyCoordsFromUrlOrLocationOrMapCenter( @@ -180,9 +190,11 @@ function NearbyView({ }, [map, setViewedNearbyCoords, setHighlightedLocation]) useEffect(() => { - if (typeof firstItemRef.current?.scrollIntoView === 'function') { - firstItemRef.current?.scrollIntoView({ behavior: 'smooth' }) - } + window.scrollTo({ + behavior: 'smooth', + left: 0, + top: 0 + }) if (finalNearbyCoords) { fetchNearby(finalNearbyCoords, radius, currentServiceWeek) setLoading(true) @@ -223,9 +235,19 @@ function NearbyView({ .flat(Infinity) ) ) + + // If configured, filter out stops that don't have any patterns + const filteredNearby = nearby?.filter((n: any) => { + if (n.place.__typename === 'Stop' && nearbyViewConfig?.hideEmptyStops) { + const patternArray = patternArrayforStops(n.place, routeSortComparator) + return !(patternArray?.length === 0) + } + return true + }) + const nearbyItemList = - nearby?.map && - nearby?.map((n: any) => ( + filteredNearby?.map && + filteredNearby?.map((n: any) => (
    2. { if (!staleData) { setLoading(false) + } else if (staleData) { + // If there's stale data, fetch again + setLoading(true) + finalNearbyCoords && + fetchNearby(finalNearbyCoords, radius, currentServiceWeek) } }, [nearby, staleData]) @@ -285,8 +312,6 @@ function NearbyView({ className="base-color-bg" style={{ marginBottom: 0 }} > - {/* This is used to scroll to top */} -
      {loading && ( @@ -296,7 +321,7 @@ function NearbyView({ !staleData && (nearby.error ? ( intl.formatMessage({ id: 'components.NearbyView.error' }) - ) : nearby.length > 0 ? ( + ) : filteredNearby?.length > 0 ? ( nearbyItemList ) : ( @@ -309,7 +334,8 @@ function NearbyView({ const mapStateToProps = (state: AppReduxState) => { const { config, location, transitIndex, ui } = state.otp - const { map, routeViewer } = config + const { map, nearbyView: nearbyViewConfig, routeViewer } = config + const transitOperators = config?.transitOperators || [] const { nearbyViewCoords } = ui const { nearby } = transitIndex const { entityId } = state.router.location.query @@ -322,6 +348,20 @@ const mapStateToProps = (state: AppReduxState) => { ? getCurrentServiceWeek() : undefined + // TODO: Refine so we don't have this same thing in stops.tsx + // Default sort: departure time + let routeSortComparator = (a: PatternStopTime, b: PatternStopTime) => + fullTimestamp(a.stoptimes?.[0]) - fullTimestamp(b.stoptimes?.[0]) + + if (nearbyViewConfig?.useRouteViewSort) { + routeSortComparator = (a: PatternStopTime, b: PatternStopTime) => + coreUtils.route.makeRouteComparator(transitOperators)( + // @ts-expect-error core-utils types are wrong! + a.pattern.route, + b.pattern.route + ) + } + return { currentPosition, currentServiceWeek, @@ -331,8 +371,10 @@ const mapStateToProps = (state: AppReduxState) => { homeTimezone: config.homeTimezone, location: state.router.location.hash, nearby: nearby?.data, + nearbyViewConfig, nearbyViewCoords, - radius: config.nearbyView?.radius + radius: config.nearbyView?.radius, + routeSortComparator } } diff --git a/lib/components/viewers/nearby/stop.tsx b/lib/components/viewers/nearby/stop.tsx index c98d288c1..973f8ddb3 100644 --- a/lib/components/viewers/nearby/stop.tsx +++ b/lib/components/viewers/nearby/stop.tsx @@ -16,7 +16,7 @@ import StopCardHeader from './stop-card-header' const { getUserTimezone } = coreUtils.time -const fullTimestamp = (stoptime: StopTime) => +export const fullTimestamp = (stoptime: StopTime) => (stoptime.serviceDay || 0) + (stoptime.realtimeDeparture || 0) type Props = { @@ -27,14 +27,11 @@ type Props = { stopData: StopData & { nearbyRoutes?: string[] } } -const Stop = ({ - fromToSlot, - homeTimezone, - nearbyViewConfig, - routeSortComparator, - stopData -}: Props): JSX.Element => { - const patternRows = (stopData.stoptimesForPatterns || []) +export const patternArrayforStops = ( + stopData: StopData & { nearbyRoutes?: string[] }, + routeSortComparator: (a: PatternStopTime, b: PatternStopTime) => number +): Array | undefined => { + return stopData?.stoptimesForPatterns ?.reduce((acc, cur) => { const currentHeadsign = extractHeadsignFromPattern(cur.pattern) const dupe = acc.findIndex((p) => { @@ -65,30 +62,40 @@ const Stop = ({ return acc }, []) .sort(routeSortComparator) - .map((st: any, index: number) => { - const sortedStopTimes = st.stoptimes.sort( - (a: StopTime, b: StopTime) => fullTimestamp(a) - fullTimestamp(b) - ) - if ( - // NearbyRoutes if present is populated with a list of routes that appear - // in the current service period. - stopData.nearbyRoutes && - !stopData.nearbyRoutes.includes(st?.pattern?.route?.gtfsId) - ) { - return <> - } - return ( - - ) - }) +} + +const Stop = ({ + fromToSlot, + homeTimezone, + nearbyViewConfig, + routeSortComparator, + stopData +}: Props): JSX.Element => { + const patternArray = patternArrayforStops(stopData, routeSortComparator) + const patternRows = patternArray?.map((st: any, index: number) => { + const sortedStopTimes = st.stoptimes.sort( + (a: StopTime, b: StopTime) => fullTimestamp(a) - fullTimestamp(b) + ) + if ( + // NearbyRoutes if present is populated with a list of routes that appear + // in the current service period. + stopData.nearbyRoutes && + !stopData.nearbyRoutes.includes(st?.pattern?.route?.gtfsId) + ) { + return <> + } + return ( + + ) + }) const inHomeTimezone = homeTimezone && homeTimezone === getUserTimezone() const timezoneWarning = !inHomeTimezone && ( @@ -96,8 +103,6 @@ const Stop = ({ ) - if (nearbyViewConfig?.hideEmptyStops && patternRows.length === 0) return <> - return ( li:last-of-type { + margin-bottom: 1em; + } + + & > li:first-of-type { + margin-top: 1em; + } + @media (max-width: 768px) { min-height: calc(100vh - 50px); }