diff --git a/assets/src/apps/v2/bus_eink.tsx b/assets/src/apps/v2/bus_eink.tsx index 5809de87a..8c5f8e30c 100644 --- a/assets/src/apps/v2/bus_eink.tsx +++ b/assets/src/apps/v2/bus_eink.tsx @@ -22,7 +22,7 @@ import OneMedium from "Components/v2/eink/flex/one_medium"; import Placeholder from "Components/v2/placeholder"; import NormalHeader from "Components/v2/eink/bus_normal_header"; import FareInfoFooter from "Components/v2/eink/fare_info_footer"; -import NormalDepartures from "Components/v2/departures/normal_departures"; +import Departures from "Components/v2/departures"; import EvergreenContent from "Components/v2/evergreen_content"; import { LOADING_LAYOUT, @@ -55,7 +55,7 @@ const TYPE_TO_COMPONENT = { placeholder: Placeholder, fare_info_footer: FareInfoFooter, normal_header: NormalHeader, - departures: NormalDepartures, + departures: Departures, alert: MediumFlexAlert, full_body_alert: FullBodyTopScreenAlert, evergreen_content: EvergreenContent, diff --git a/assets/src/apps/v2/bus_shelter.tsx b/assets/src/apps/v2/bus_shelter.tsx index 07112ec15..f3ee9f739 100644 --- a/assets/src/apps/v2/bus_shelter.tsx +++ b/assets/src/apps/v2/bus_shelter.tsx @@ -34,7 +34,7 @@ import TwoMedium from "Components/v2/bus_shelter/flex/two_medium"; import Placeholder from "Components/v2/placeholder"; import LinkFooter from "Components/v2/bus_shelter/link_footer"; import NormalHeader from "Components/v2/lcd/normal_header"; -import NormalDepartures from "Components/v2/departures/normal_departures"; +import Departures from "Components/v2/departures"; import LcdSubwayStatus from "Components/v2/subway_status/lcd_subway_status"; import EvergreenContent from "Components/v2/evergreen_content"; @@ -60,7 +60,7 @@ const TYPE_TO_COMPONENT = { placeholder: Placeholder, link_footer: LinkFooter, normal_header: NormalHeader, - departures: NormalDepartures, + departures: Departures, subway_status: LcdSubwayStatus, alert: FlexZoneAlert, full_body_alert: FullBodyAlert, diff --git a/assets/src/apps/v2/busway.tsx b/assets/src/apps/v2/busway.tsx index f1dc94d9f..19c56e991 100644 --- a/assets/src/apps/v2/busway.tsx +++ b/assets/src/apps/v2/busway.tsx @@ -24,7 +24,7 @@ import TakeoverScreen from "Components/v2/takeover_screen"; import Placeholder from "Components/v2/placeholder"; import NormalHeader from "Components/v2/lcd/normal_header"; -import NormalDepartures from "Components/v2/departures/normal_departures"; +import Departures from "Components/v2/departures"; import NoData from "Components/v2/lcd/no_data"; import DeparturesNoData from "Components/v2/lcd/departures_no_data"; @@ -38,7 +38,7 @@ const TYPE_TO_COMPONENT = { takeover: TakeoverScreen, placeholder: Placeholder, normal_header: NormalHeader, - departures: NormalDepartures, + departures: Departures, no_data: NoData, page_load_no_data: PageLoadNoData, departures_no_data: DeparturesNoData, diff --git a/assets/src/apps/v2/dup.tsx b/assets/src/apps/v2/dup.tsx index 783a3e085..9fe8ceaa0 100644 --- a/assets/src/apps/v2/dup.tsx +++ b/assets/src/apps/v2/dup.tsx @@ -16,7 +16,7 @@ import NormalScreen, { NormalSimulation, } from "Components/v2/dup/normal_screen"; import NormalHeader from "Components/v2/dup/normal_header"; -import NormalDepartures from "Components/v2/dup/departures/normal_departures"; +import Departures from "Components/v2/dup/departures"; import MultiScreenPage from "Components/v2/multi_screen_page"; import Viewport from "Components/v2/dup/viewport"; import EvergreenContent from "Components/v2/evergreen_content"; @@ -54,7 +54,7 @@ const TYPE_TO_COMPONENT = { body_split_one: splitRotationFromPropNames(SplitBody, "one"), body_split_two: splitRotationFromPropNames(SplitBody, "two"), normal_header: NormalHeader, - departures: NormalDepartures, + departures: Departures, evergreen_content: EvergreenContent, partial_alert: PartialAlert, takeover_alert: TakeoverAlert, @@ -73,24 +73,15 @@ const responseMapper: ResponseMapper = (apiResponse) => { case "failure": return { rotation_one: { - full_rotation: { - type: "no_data", - include_header: true, - }, + full_rotation: { type: "no_data" }, type: "rotation_takeover_one", }, rotation_two: { - full_rotation: { - type: "no_data", - include_header: true, - }, + full_rotation: { type: "no_data" }, type: "rotation_takeover_two", }, rotation_zero: { - full_rotation: { - type: "no_data", - include_header: true, - }, + full_rotation: { type: "no_data" }, type: "rotation_takeover_zero", }, type: "screen_normal", diff --git a/assets/src/apps/v2/gl_eink.tsx b/assets/src/apps/v2/gl_eink.tsx index 6f40855f0..2c3d9b632 100644 --- a/assets/src/apps/v2/gl_eink.tsx +++ b/assets/src/apps/v2/gl_eink.tsx @@ -22,7 +22,7 @@ import OneMedium from "Components/v2/eink/flex/one_medium"; import Placeholder from "Components/v2/placeholder"; import FareInfoFooter from "Components/v2/eink/fare_info_footer"; import NormalHeader from "Components/v2/eink/gl_normal_header"; -import NormalDepartures from "Components/v2/departures/normal_departures"; +import Departures from "Components/v2/departures"; import LineMap from "Components/v2/gl_eink_double/line_map"; import EvergreenContent from "Components/v2/evergreen_content"; import NoData from "Components/v2/eink/no_data"; @@ -59,7 +59,7 @@ const TYPE_TO_COMPONENT = { placeholder: Placeholder, fare_info_footer: FareInfoFooter, normal_header: NormalHeader, - departures: NormalDepartures, + departures: Departures, alert: MediumFlexAlert, full_body_alert: FullBodyTopScreenAlert, line_map: LineMap, diff --git a/assets/src/apps/v2/solari_large.tsx b/assets/src/apps/v2/solari_large.tsx index 48f83c540..43605c774 100644 --- a/assets/src/apps/v2/solari_large.tsx +++ b/assets/src/apps/v2/solari_large.tsx @@ -13,7 +13,7 @@ import NormalScreen from "Components/v2/solari_large/normal_screen"; import TakeoverScreen from "Components/v2/takeover_screen"; import Placeholder from "Components/v2/placeholder"; import NormalHeader from "Components/v2/lcd/normal_header"; -import NormalDepartures from "Components/v2/departures/normal_departures"; +import Departures from "Components/v2/departures"; import MultiScreenPage from "Components/v2/multi_screen_page"; const TYPE_TO_COMPONENT = { @@ -21,7 +21,7 @@ const TYPE_TO_COMPONENT = { takeover: TakeoverScreen, placeholder: Placeholder, normal_header: NormalHeader, - departures: NormalDepartures, + departures: Departures, }; const App = (): JSX.Element => { diff --git a/assets/src/components/v2/departures/normal_departures.tsx b/assets/src/components/v2/departures.tsx similarity index 81% rename from assets/src/components/v2/departures/normal_departures.tsx rename to assets/src/components/v2/departures.tsx index 9b2d39f0e..e54ed550d 100644 --- a/assets/src/components/v2/departures/normal_departures.tsx +++ b/assets/src/components/v2/departures.tsx @@ -7,18 +7,18 @@ import React, { } from "react"; import weakKey from "weak-key"; -import NormalSection from "./normal_section"; -import NoticeSection from "./notice_section"; -import { Section, trimSections, toFoldedSection } from "./section"; +import NormalSection from "./departures/normal_section"; +import NoticeSection from "./departures/notice_section"; +import { Section, trimSections, toFoldedSection } from "./departures/section"; import { warn } from "Util/sentry"; import { hasOverflowY } from "Util/util"; -type NormalDepartures = { +type Departures = { sections: Section[]; }; -const NormalDepartures: ComponentType = ({ sections }) => { +const Departures: ComponentType = ({ sections }) => { const ref = useRef(null); const initialSections = useMemo( () => sections.map(toFoldedSection), @@ -57,4 +57,4 @@ const NormalDepartures: ComponentType = ({ sections }) => { ); }; -export default NormalDepartures; +export default Departures; diff --git a/assets/src/components/v2/departures/departure_row.tsx b/assets/src/components/v2/departures/departure_row.tsx index 9c6e4e42d..3f50701ac 100644 --- a/assets/src/components/v2/departures/departure_row.tsx +++ b/assets/src/components/v2/departures/departure_row.tsx @@ -1,11 +1,9 @@ import React, { ComponentType } from "react"; -import RoutePill, { Pill } from "Components/v2/departures/route_pill"; -import Destination from "Components/v2/departures/destination"; -import DepartureTimes, { - TimeWithCrowding, -} from "Components/v2/departures/departure_times"; -import DepartureAlerts from "Components/v2/departures/departure_alerts"; +import RoutePill, { Pill } from "./route_pill"; +import Destination from "./destination"; +import DepartureTimes, { TimeWithCrowding } from "./departure_times"; +import DepartureAlerts from "./departure_alerts"; type DepartureRow = { id: string; diff --git a/assets/src/components/v2/departures/departure_time.tsx b/assets/src/components/v2/departures/departure_time.tsx index 9266d8ca1..bfa8f79b8 100644 --- a/assets/src/components/v2/departures/departure_time.tsx +++ b/assets/src/components/v2/departures/departure_time.tsx @@ -24,7 +24,10 @@ const TimestampDepartureTime = ({ hour, minute }) => { type DepartureTime = | (TextDeparture & { type: "text" }) | (MinutesDeparture & { type: "minutes" }) - | (TimestampDeparture & { type: "timestamp" }); + | (TimestampDeparture & { type: "timestamp" }) + // Note: `overnight` is only produced in the DUP code path, and so is only + // supported in the DUP version of this component. + | { type: "overnight" }; interface TextDeparture { text: string; @@ -35,6 +38,10 @@ interface MinutesDeparture { interface TimestampDeparture { hour: number; minute: number; + // Note: `am_pm` fields are currently only supported by the DUP version of + // this component, but are always present in departures serialization. + am_pm: string; + show_am_pm: boolean; } const DepartureTime: ComponentType = ({ type, ...data }) => { diff --git a/assets/src/components/v2/departures/departure_times.tsx b/assets/src/components/v2/departures/departure_times.tsx index 3f30a1a33..c15ed58ca 100644 --- a/assets/src/components/v2/departures/departure_times.tsx +++ b/assets/src/components/v2/departures/departure_times.tsx @@ -1,13 +1,14 @@ import React, { ComponentType } from "react"; -import DepartureTime from "Components/v2/departures/departure_time"; -import DepartureCrowding, { - CrowdingLevel, -} from "Components/v2/departures/departure_crowding"; +import DepartureTime from "./departure_time"; +import DepartureCrowding, { CrowdingLevel } from "./departure_crowding"; export type TimeWithCrowding = { id: string; time: DepartureTime; + // Note: `scheduled_time` is currently only supported by the DUP version of + // `DepartureTime`, but is always present in departures serialization. + scheduled_time?: DepartureTime; crowding: CrowdingLevel | null; }; diff --git a/assets/src/components/v2/departures/header.tsx b/assets/src/components/v2/departures/header.tsx index 22742ae9d..08c67b2cf 100644 --- a/assets/src/components/v2/departures/header.tsx +++ b/assets/src/components/v2/departures/header.tsx @@ -4,10 +4,10 @@ import Arrow45 from "Images/svgr_bundled/Arrow-45-no-padding.svg"; type CardinalDirection = "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw"; -export interface Props { +type Header = { title: string | null; arrow: CardinalDirection | null; -} +}; const DirectionArrow = ({ arrow }: { arrow: CardinalDirection }) => ( ( /> ); -const Header = ({ title, arrow }: Props) => { +const Header = ({ title, arrow }: Header) => { return (
{(title || arrow) && {title}} diff --git a/assets/src/components/v2/departures/normal_section.tsx b/assets/src/components/v2/departures/normal_section.tsx index 5bd87adf0..6d481bc35 100644 --- a/assets/src/components/v2/departures/normal_section.tsx +++ b/assets/src/components/v2/departures/normal_section.tsx @@ -3,7 +3,7 @@ import weakKey from "weak-key"; import DepartureRow from "./departure_row"; import NoticeRow from "./notice_row"; -import Header, { type Props as HeaderProps } from "./header"; +import Header from "./header"; import LaterDepartures, { MIN_LATER_DEPARTURES } from "./later_departures"; export type Layout = { @@ -19,13 +19,13 @@ export type Row = export type NormalSection = { layout: Layout; - header: HeaderProps; + header: Header; rows: Row[]; }; export type FoldedNormalSection = { layout: Layout; - header: HeaderProps; + header: Header; rows: FoldedRows; }; diff --git a/assets/src/components/v2/dup/departures.tsx b/assets/src/components/v2/dup/departures.tsx new file mode 100644 index 000000000..0f5a289af --- /dev/null +++ b/assets/src/components/v2/dup/departures.tsx @@ -0,0 +1,43 @@ +import React, { ComponentType } from "react"; + +import { type Section as SectionBase } from "Components/v2/departures/section"; +import NormalSection from "./departures/normal_section"; +import NoticeSection from "Components/v2/departures/notice_section"; +import HeadwaySection from "./departures/headway_section"; +import NoDataSection from "./departures/no_data_section"; +import OvernightSection from "./departures/overnight_section"; + +type Section = + | SectionBase + | (HeadwaySection & { type: "headway_section" }) + | (NoDataSection & { type: "no_data_section" }) + | (OvernightSection & { type: "overnight_section" }); + +interface Props { + sections: Section[]; +} + +const Departures: ComponentType = ({ sections }) => { + return ( +
+
+ {sections.map((section, i) => { + switch (section.type) { + case "normal_section": + return ; + case "notice_section": + return ; + case "headway_section": + return ; + case "no_data_section": + return ; + case "overnight_section": + return ; + } + })} +
+
+ ); +}; + +export default Departures; diff --git a/assets/src/components/v2/dup/departures/departure_row.tsx b/assets/src/components/v2/dup/departures/departure_row.tsx index 38379c9ba..8529ffb24 100644 --- a/assets/src/components/v2/dup/departures/departure_row.tsx +++ b/assets/src/components/v2/dup/departures/departure_row.tsx @@ -1,25 +1,24 @@ -import React from "react"; +import React, { ComponentType } from "react"; +import type DepartureRowBase from "Components/v2/departures/departure_row"; import RoutePill from "Components/v2/departures/route_pill"; -import Destination from "Components/v2/dup/departures/destination"; -import DepartureTimes from "Components/v2/dup/departures/departure_times"; +import Destination from "./destination"; +import DepartureTimes from "./departure_times"; -const DepartureRow = ({ +interface Props extends DepartureRowBase { + currentPage: number; +} + +const DepartureRow: ComponentType = ({ headsign, route, times_with_crowding: timesWithCrowding, currentPage, }) => { - const routeText = Number(route.text); return (
- 200 ? "small" : "large", - }} - /> +
diff --git a/assets/src/components/v2/dup/departures/departure_time.tsx b/assets/src/components/v2/dup/departures/departure_time.tsx index 95541f263..d5414bb4b 100644 --- a/assets/src/components/v2/dup/departures/departure_time.tsx +++ b/assets/src/components/v2/dup/departures/departure_time.tsx @@ -1,6 +1,8 @@ -import React from "react"; +import React, { ComponentType } from "react"; import { classWithModifier, classWithModifiers, imagePath } from "Util/util"; +import type DepartureTimeBase from "Components/v2/departures/departure_time"; + const TextDepartureTime = ({ text }) => { return
{text}
; }; @@ -26,9 +28,19 @@ const TimestampDepartureTime = ({ hour, minute, am_pm, show_am_pm }) => { ); }; -const DepartureTime = ({ scheduled_time, time, currentPage }) => { +interface Props { + time: DepartureTimeBase; + scheduled_time?: DepartureTimeBase; + currentPage: number; +} + +const DepartureTime: ComponentType = ({ + scheduled_time, + time, + currentPage, +}) => { let predictedTime; - if (time.type === "icon") { + if (time.type === "overnight") { predictedTime = ( ); @@ -50,11 +62,11 @@ const DepartureTime = ({ scheduled_time, time, currentPage }) => { let scheduledTime; - if (time.type === "text") { + if (scheduled_time.type === "text") { scheduledTime = ; - } else if (time.type === "minutes") { + } else if (scheduled_time.type === "minutes") { scheduledTime = ; - } else if (time.type === "timestamp") { + } else if (scheduled_time.type === "timestamp") { scheduledTime = ; } if (currentPage === 0) { @@ -67,7 +79,7 @@ const DepartureTime = ({ scheduled_time, time, currentPage }) => { return (
diff --git a/assets/src/components/v2/dup/departures/departure_times.tsx b/assets/src/components/v2/dup/departures/departure_times.tsx index 959f4c69a..b02270688 100644 --- a/assets/src/components/v2/dup/departures/departure_times.tsx +++ b/assets/src/components/v2/dup/departures/departure_times.tsx @@ -1,8 +1,17 @@ -import React from "react"; +import React, { ComponentType } from "react"; -import DepartureTime from "Components/v2/dup/departures/departure_time"; +import { type TimeWithCrowding } from "Components/v2/departures/departure_times"; +import DepartureTime from "./departure_time"; -const DepartureTimes = ({ timesWithCrowding, currentPage }) => { +interface Props { + timesWithCrowding: TimeWithCrowding[]; + currentPage: number; +} + +const DepartureTimes: ComponentType = ({ + timesWithCrowding, + currentPage, +}) => { return ( <> {timesWithCrowding.map(({ id, time, scheduled_time }) => ( diff --git a/assets/src/components/v2/dup/departures/destination.tsx b/assets/src/components/v2/dup/departures/destination.tsx index 595540855..f6f8e5edf 100644 --- a/assets/src/components/v2/dup/departures/destination.tsx +++ b/assets/src/components/v2/dup/departures/destination.tsx @@ -1,4 +1,6 @@ -import React, { useLayoutEffect, useRef, useState } from "react"; +import React, { ComponentType, useLayoutEffect, useRef, useState } from "react"; + +import type DestinationBase from "Components/v2/departures/destination"; const LINE_HEIGHT = 138; // px @@ -42,7 +44,11 @@ const RenderedDestination = ({ parts, index1, index2, currentPageIndex }) => { ); }; -const Destination = ({ headsign, currentPage }) => { +interface Props extends DestinationBase { + currentPage: number; +} + +const Destination: ComponentType = ({ headsign, currentPage }) => { const firstLineRef = useRef(null); const secondLineRef = useRef(null); diff --git a/assets/src/components/v2/dup/departures/headway_section.tsx b/assets/src/components/v2/dup/departures/headway_section.tsx index 85316cef5..141e161e8 100644 --- a/assets/src/components/v2/dup/departures/headway_section.tsx +++ b/assets/src/components/v2/dup/departures/headway_section.tsx @@ -1,8 +1,13 @@ -import React from "react"; -import FreeText from "Components/v2/free_text"; +import React, { ComponentType } from "react"; +import FreeText, { FreeTextType } from "Components/v2/free_text"; import { classWithModifier } from "Util/util"; -const HeadwaySection = ({ text, layout }) => { +interface HeadwaySection { + layout: string; + text: FreeTextType; +} + +const HeadwaySection: ComponentType = ({ text, layout }) => { return (
{ +interface NoDataSection { + text: FreeTextType; +} + +const NoDataSection: ComponentType = ({ text }) => { return (
diff --git a/assets/src/components/v2/dup/departures/normal_departures.tsx b/assets/src/components/v2/dup/departures/normal_departures.tsx deleted file mode 100644 index db793f092..000000000 --- a/assets/src/components/v2/dup/departures/normal_departures.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; - -import NormalSection from "Components/v2/dup/departures/normal_section"; -import NoticeSection from "Components/v2/departures/notice_section"; -import HeadwaySection from "Components/v2/dup/departures/headway_section"; -import NoDataSection from "Components/v2/dup/departures/no_data_section"; -import OvernightSection from "./overnight_section"; - -const NormalDepartures = ({ sections }) => { - return ( -
-
- {sections.map(({ type, ...data }, i) => { - switch (type) { - case "normal_section": - return ; - case "notice_section": - return ; - case "headway_section": - return ( - - ); - case "no_data_section": - return ; - case "overnight_section": - return ; - } - - throw new Error(`unimplemented section type: ${type}`); - })} -
-
- ); -}; - -export default NormalDepartures; diff --git a/assets/src/components/v2/dup/departures/normal_section.tsx b/assets/src/components/v2/dup/departures/normal_section.tsx index 0d20dcbf3..6707022ef 100644 --- a/assets/src/components/v2/dup/departures/normal_section.tsx +++ b/assets/src/components/v2/dup/departures/normal_section.tsx @@ -1,10 +1,11 @@ -import React from "react"; +import React, { ComponentType } from "react"; -import DepartureRow from "Components/v2/dup/departures/departure_row"; +import { type NormalSection as Props } from "Components/v2/departures/normal_section"; +import DepartureRow from "./departure_row"; import NoticeRow from "Components/v2/departures/notice_row"; import useCurrentPage from "Hooks/use_current_dup_page"; -const NormalSection = ({ rows }) => { +const NormalSection: ComponentType = ({ rows }) => { if (rows.length == 0) return null; const currentPage = useCurrentPage(); @@ -12,13 +13,12 @@ const NormalSection = ({ rows }) => { return (
{rows.map((row, index) => { - const { id, type, ...data } = row; - if (type === "departure_row") { - return ; - } else if (type === "notice_row") { - return ; + if (row.type === "departure_row") { + return ( + + ); } else { - throw new Error(`unimplemented row type: ${type}`); + return ; } })}
diff --git a/assets/src/components/v2/dup/departures/overnight_section.tsx b/assets/src/components/v2/dup/departures/overnight_section.tsx index 6fb70dfad..d97442760 100644 --- a/assets/src/components/v2/dup/departures/overnight_section.tsx +++ b/assets/src/components/v2/dup/departures/overnight_section.tsx @@ -1,8 +1,12 @@ -import React from "react"; +import React, { ComponentType } from "react"; -import FreeText from "Components/v2/free_text"; +import FreeText, { FreeTextType } from "Components/v2/free_text"; -const OvernightSection = ({ text }) => { +interface OvernightSection { + text: FreeTextType; +} + +const OvernightSection: ComponentType = ({ text }) => { return (
diff --git a/assets/src/components/v2/dup/no_data.tsx b/assets/src/components/v2/dup/no_data.tsx index 20a07cf9d..17b5cf160 100644 --- a/assets/src/components/v2/dup/no_data.tsx +++ b/assets/src/components/v2/dup/no_data.tsx @@ -10,17 +10,14 @@ export const REPLACEMENTS = { Malden: "Malden Center", } as { [key: string]: string }; -interface Props { - include_header?: boolean; -} - -const NoData: ComponentType = ({ include_header }) => { +const NoData: ComponentType = () => { let stationName = useStationName() || "Transit information"; stationName = REPLACEMENTS[stationName] || stationName; return (
- {include_header && } + +
= ({ include_header }) => { Live updates are temporarily unavailable
+
diff --git a/assets/src/components/v2/dup/normal_header.tsx b/assets/src/components/v2/dup/normal_header.tsx index 953657306..8c6a4f66b 100644 --- a/assets/src/components/v2/dup/normal_header.tsx +++ b/assets/src/components/v2/dup/normal_header.tsx @@ -1,7 +1,7 @@ import React from "react"; import DefaultNormalHeader, { Icon } from "Components/v2/normal_header"; -import { DUP_VERSION } from "Components/v2/dup/version"; +import { DUP_VERSION } from "./version"; import { usePlayerName } from "Hooks/outfront"; interface NormalHeaderProps { @@ -9,7 +9,6 @@ interface NormalHeaderProps { time?: string; color?: string; accentPattern?: string; - code?: string; } const NormalHeader = ({ @@ -17,24 +16,18 @@ const NormalHeader = ({ time, color, accentPattern, - code, }: NormalHeaderProps) => { const playerName = usePlayerName(); let version = DUP_VERSION; if (playerName) { version = `${version}-${playerName}`; } - if (code) { - version = `${version}; Maintenance code: ${code}`; - } return (