Skip to content

Commit

Permalink
refactor: add types for v2 departures widget
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalcora committed May 16, 2024
1 parent e0159f1 commit 9eeafc3
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 123 deletions.
128 changes: 64 additions & 64 deletions assets/src/components/v2/departures/departure_crowding.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
import React from "react";
import React, { ComponentType } from "react";

import { classWithModifier } from "Util/util";

const CrowdingIcon = ({ crowdingLevel }) => {
return (
<svg
className={classWithModifier(
"departure-crowding__icon",
`level-${crowdingLevel}`,
)}
viewBox="0 0 110 92"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.6773 21.3758C21.8612 21.3758 26.8742 16.5907 26.8742 10.6879C26.8742 4.78514 21.8612 0 15.6773 0C9.49348 0 4.48047 4.78514 4.48047 10.6879C4.48047 16.5907 9.49348 21.3758 15.6773 21.3758Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 1 ? "active" : "inactive",
)}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.6756 27.7886C24.333 27.7886 31.3512 34.8068 31.3512 43.4642V60.6589V84.0783C31.3512 88.407 27.8421 91.9161 23.5134 91.9161H7.83781C3.50911 91.9161 0 88.407 0 84.0783V61.1386V43.4642C0 34.8068 7.01821 27.7886 15.6756 27.7886Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 1 ? "active" : "inactive",
)}
/>
<path
d="M54.8668 21.3758C61.0506 21.3758 66.0637 16.5907 66.0637 10.6879C66.0637 4.78514 61.0506 0 54.8668 0C48.6829 0 43.6699 4.78514 43.6699 10.6879C43.6699 16.5907 48.6829 21.3758 54.8668 21.3758Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 2 ? "active" : "inactive",
)}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M54.8651 27.7886C63.5225 27.7886 70.5407 34.8068 70.5407 43.4642V60.6589V84.0783C70.5407 88.407 67.0316 91.9161 62.7029 91.9161H47.0273C42.6986 91.9161 39.1895 88.407 39.1895 84.0783V61.1386V43.4642C39.1895 34.8068 46.2077 27.7886 54.8651 27.7886Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 2 ? "active" : "inactive",
)}
/>
<path
d="M94.0533 21.3758C100.237 21.3758 105.25 16.5907 105.25 10.6879C105.25 4.78514 100.237 0 94.0533 0C87.8695 0 82.8564 4.78514 82.8564 10.6879C82.8564 16.5907 87.8695 21.3758 94.0533 21.3758Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 3 ? "active" : "inactive",
)}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M94.0516 27.7886C102.709 27.7886 109.727 34.8068 109.727 43.4642V60.6589V84.0783C109.727 88.407 106.218 91.9161 101.889 91.9161H86.2138C81.8851 91.9161 78.376 88.407 78.376 84.0783V61.1386V43.4642C78.376 34.8068 85.3942 27.7886 94.0516 27.7886Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 3 ? "active" : "inactive",
)}
/>
</svg>
);
export type CrowdingLevel = 1 | 2 | 3;

type Props = {
crowdingLevel: CrowdingLevel;
};

const DepartureCrowding = ({ crowdingLevel }) => {
const DepartureCrowding: ComponentType<Props> = ({ crowdingLevel }) => {
return (
<div className="departure-crowding">
<CrowdingIcon crowdingLevel={crowdingLevel} />
<svg
className={classWithModifier(
"departure-crowding__icon",
`level-${crowdingLevel}`,
)}
viewBox="0 0 110 92"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.6773 21.3758C21.8612 21.3758 26.8742 16.5907 26.8742 10.6879C26.8742 4.78514 21.8612 0 15.6773 0C9.49348 0 4.48047 4.78514 4.48047 10.6879C4.48047 16.5907 9.49348 21.3758 15.6773 21.3758Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 1 ? "active" : "inactive",
)}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.6756 27.7886C24.333 27.7886 31.3512 34.8068 31.3512 43.4642V60.6589V84.0783C31.3512 88.407 27.8421 91.9161 23.5134 91.9161H7.83781C3.50911 91.9161 0 88.407 0 84.0783V61.1386V43.4642C0 34.8068 7.01821 27.7886 15.6756 27.7886Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 1 ? "active" : "inactive",
)}
/>
<path
d="M54.8668 21.3758C61.0506 21.3758 66.0637 16.5907 66.0637 10.6879C66.0637 4.78514 61.0506 0 54.8668 0C48.6829 0 43.6699 4.78514 43.6699 10.6879C43.6699 16.5907 48.6829 21.3758 54.8668 21.3758Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 2 ? "active" : "inactive",
)}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M54.8651 27.7886C63.5225 27.7886 70.5407 34.8068 70.5407 43.4642V60.6589V84.0783C70.5407 88.407 67.0316 91.9161 62.7029 91.9161H47.0273C42.6986 91.9161 39.1895 88.407 39.1895 84.0783V61.1386V43.4642C39.1895 34.8068 46.2077 27.7886 54.8651 27.7886Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 2 ? "active" : "inactive",
)}
/>
<path
d="M94.0533 21.3758C100.237 21.3758 105.25 16.5907 105.25 10.6879C105.25 4.78514 100.237 0 94.0533 0C87.8695 0 82.8564 4.78514 82.8564 10.6879C82.8564 16.5907 87.8695 21.3758 94.0533 21.3758Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 3 ? "active" : "inactive",
)}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M94.0516 27.7886C102.709 27.7886 109.727 34.8068 109.727 43.4642V60.6589V84.0783C109.727 88.407 106.218 91.9161 101.889 91.9161H86.2138C81.8851 91.9161 78.376 88.407 78.376 84.0783V61.1386V43.4642C78.376 34.8068 85.3942 27.7886 94.0516 27.7886Z"
className={classWithModifier(
"departure-crowding__icon-person",
crowdingLevel >= 3 ? "active" : "inactive",
)}
/>
</svg>
</div>
);
};
Expand Down
25 changes: 16 additions & 9 deletions assets/src/components/v2/departures/departure_row.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import React from "react";
import React, { ComponentType } from "react";

import RoutePill from "Components/v2/departures/route_pill";
import RoutePill, { Pill } from "Components/v2/departures/route_pill";
import Destination from "Components/v2/departures/destination";
import DepartureTimes from "Components/v2/departures/departure_times";
import DepartureTimes, {
TimeWithCrowding,
} from "Components/v2/departures/departure_times";
import DepartureAlerts from "Components/v2/departures/departure_alerts";

const DepartureRow = ({
type DepartureRow = {
id: string;
route: Pill;
headsign: Destination;
times_with_crowding: TimeWithCrowding[];
// currently never used, will be removed
inline_alerts: any[];
};

const DepartureRow: ComponentType<DepartureRow> = ({
headsign,
route,
times_with_crowding: timesWithCrowding,
inline_alerts: inlineAlerts,
}) => {
const routeText = Number(route.text);
return (
<div className="departure-row">
<div // Keep pill aligned to top if there is a variation for the headsign.
Expand All @@ -20,10 +30,7 @@ const DepartureRow = ({
"departure-row__route" + (headsign.variation ? "" : " center")
}
>
<RoutePill
{...route}
size={isNaN(routeText) || routeText > 200 ? "small" : "large"}
/>
<RoutePill {...route} />
</div>
<div className="departure-row__destination">
<Destination {...headsign} />
Expand Down
6 changes: 3 additions & 3 deletions assets/src/components/v2/departures/departure_time.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { ComponentType } from "react";
import { classWithModifier } from "Util/util";

const TextDepartureTime = ({ text }) => {
Expand All @@ -21,7 +21,7 @@ const TimestampDepartureTime = ({ hour, minute }) => {
return <div className="departure-time__timestamp">{timestamp}</div>;
};

type Props =
type DepartureTime =
| (TextDeparture & { type: "text" })
| (MinutesDeparture & { type: "minutes" })
| (TimestampDeparture & { type: "timestamp" });
Expand All @@ -37,7 +37,7 @@ interface TimestampDeparture {
minute: number;
}

const DepartureTime = ({ type, ...data }: Props) => {
const DepartureTime: ComponentType<DepartureTime> = ({ type, ...data }) => {
let inner;
if (type === "text") {
inner = <TextDepartureTime {...(data as TextDeparture)} />;
Expand Down
18 changes: 15 additions & 3 deletions assets/src/components/v2/departures/departure_times.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import React from "react";
import React, { ComponentType } from "react";

import DepartureTime from "Components/v2/departures/departure_time";
import DepartureCrowding from "Components/v2/departures/departure_crowding";
import DepartureCrowding, {
CrowdingLevel,
} from "Components/v2/departures/departure_crowding";

const DepartureTimes = ({ timesWithCrowding }) => {
export type TimeWithCrowding = {
id: string;
time: DepartureTime;
crowding: CrowdingLevel | null;
};

type Props = {
timesWithCrowding: TimeWithCrowding[];
};

const DepartureTimes: ComponentType<Props> = ({ timesWithCrowding }) => {
return (
<div className="departure-times-with-crowding">
{timesWithCrowding.map(({ id, time, crowding }) => (
Expand Down
9 changes: 7 additions & 2 deletions assets/src/components/v2/departures/destination.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React from "react";
import React, { ComponentType } from "react";

const Destination = ({ headsign, variation }) => {
type Destination = {
headsign: string;
variation?: string;
};

const Destination: ComponentType<Destination> = ({ headsign, variation }) => {
return (
<div className="departure-destination">
<div className="departure-destination__headsign">{headsign}</div>
Expand Down
56 changes: 31 additions & 25 deletions assets/src/components/v2/departures/normal_departures.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
import React, {
ComponentType,
useState,
forwardRef,
useLayoutEffect,
useRef,
useEffect,
useContext,
} from "react";
import weakKey from "weak-key";

import NormalSection from "Components/v2/departures/normal_section";
import NormalSection, {
Row as NormalRow,
} from "Components/v2/departures/normal_section";
import NoticeSection from "Components/v2/departures/notice_section";
import { LastFetchContext } from "../screen_container";

// TODO: fully define the type of sections and their contents, replace `any`
const NormalDeparturesRenderer = forwardRef<HTMLDivElement, any>(
type Section =
| (NormalSection & { type: "normal_section" })
| (NoticeSection & { type: "notice_section" });

type RendererProps = {
sections: Section[];
sectionSizes: number[];
};

const NormalDeparturesRenderer = forwardRef<HTMLDivElement, RendererProps>(
({ sections, sectionSizes }, ref) => {
return (
<div className="departures-container">
<div className="departures" ref={ref}>
{sections.map(({ type, ...data }, i) => {
if (type === "normal_section") {
const { rows } = data;
{sections.map((section, i) => {
const key = weakKey(section);

if (section.type === "normal_section") {
return (
<NormalSection rows={trimRows(rows, sectionSizes[i])} key={i} />
<NormalSection
rows={trimRows(section.rows, sectionSizes[i])}
key={key}
/>
);
} else if (type === "notice_section") {
return <NoticeSection {...data} key={i} />;
} else {
throw new Error(`section type not implemented: ${type}`);
return <NoticeSection {...section} key={key} />;
}
})}
</div>
Expand All @@ -37,7 +51,7 @@ const NormalDeparturesRenderer = forwardRef<HTMLDivElement, any>(

const trimRows = (rows, n) => {
const { trimmed } = rows.reduce(
({ count, trimmed }, row: Row) => {
({ count, trimmed }, row: NormalRow) => {
if (row.type == "notice_row") {
if (count < n) {
return { count: count + 1, trimmed: [...trimmed, row] };
Expand All @@ -64,20 +78,8 @@ const trimRows = (rows, n) => {
return trimmed;
};

interface DepartureRow {
type: "departure_row";
times_with_crowding: any[];
}

interface NoticeRow {
type: "notice_row";
text: object;
}

type Row = DepartureRow | NoticeRow;

const getInitialSectionSize = (data) => {
return data.rows.reduce((acc, row: Row) => {
return data.rows.reduce((acc, row: NormalRow) => {
switch (row.type) {
case "departure_row":
return acc + row.times_with_crowding.length;
Expand Down Expand Up @@ -120,7 +122,11 @@ const NormalDeparturesSizer = ({ sections, onDoneSizing }) => {
);
};

const NormalDepartures = ({ sections }) => {
type NormalDepartures = {
sections: Section[];
};

const NormalDepartures: ComponentType<NormalDepartures> = ({ sections }) => {
const [sectionSizes, setSectionSizes] = useState([]);
const lastFetch = useContext(LastFetchContext);

Expand Down
26 changes: 16 additions & 10 deletions assets/src/components/v2/departures/normal_section.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import React from "react";
import React, { ComponentType } from "react";
import weakKey from "weak-key";

import DepartureRow from "Components/v2/departures/departure_row";
import DepartureRow from "./departure_row";
import NoticeRow from "./notice_row";

const NormalSection = ({ rows }) => {
export type Row =
| (DepartureRow & { type: "departure_row" })
| (NoticeRow & { type: "notice_row" });

type NormalSection = {
rows: Row[];
};

const NormalSection: ComponentType<NormalSection> = ({ rows }) => {
return (
<div>
<div className="departures-section">
{rows.map((row, index) => {
const { id, type, ...data } = row;
if (type === "departure_row") {
return <DepartureRow {...data} key={id} />;
} else if (type === "notice_row") {
return <NoticeRow row={row} key={"notice" + index} />;
{rows.map((row) => {
if (row.type === "departure_row") {
return <DepartureRow {...row} key={row.id} />;
} else {
throw new Error(`unimplemented row type: ${type}`);
return <NoticeRow row={row} key={weakKey(row)} />;
}
})}
</div>
Expand Down
Loading

0 comments on commit 9eeafc3

Please sign in to comment.