Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
38 changes: 17 additions & 21 deletions packages/base-map/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import maplibregl, {
MapLayerMouseEvent,
MapLayerTouchEvent
} from "maplibre-gl";
import React, { useCallback, useState } from "react";
import { Map, MapProps } from "react-map-gl/maplibre";
import maplibregl, { MapLayerMouseEvent } from "maplibre-gl";
import { Map, MapProps, ViewStateChangeEvent } from "react-map-gl/maplibre";

import { useIntl } from "react-intl";

import * as Styled from "./styled";
import * as util from "./util";
import MarkerWithPopup from "./MarkerWithPopup";
import generateMapControlTranslations from "./mapControlLocale";
import * as Styled from "./styled";
import * as util from "./util";

/**
* The BaseMap component renders a MapLibre map
Expand Down Expand Up @@ -42,7 +45,7 @@ type Props = React.ComponentPropsWithoutRef<React.ElementType> & {
/** A callback method which is fired when the layer selector component is clicked with the left mouse button/tapped */
onClickLayerSelector?: () => void;
/** A callback method which is fired when the map is clicked with the right mouse button/long tapped */
onContextMenu?: (e: MapLayerMouseEvent) => void;
onContextMenu?: (e: MapLayerMouseEvent | MapLayerTouchEvent) => void;
/** A callback method which is fired when the map zoom or map bounds change */
onViewportChanged?: (e: State) => void;
/** When set to true, all hidden layers will be removed. No layers will be uncheckable until
Expand Down Expand Up @@ -80,7 +83,7 @@ const BaseMap = ({
// Firefox and Safari on iOS: hover is not triggered when the user touches the layer selector
// (unlike Firefox or Chromium on Android), so we have to detect touch and trigger hover ourselves.
const [fakeMobileHover, setFakeHover] = useState(false);
const [longPressTimer, setLongPressTimer] = useState(null);
const [longPressTimer, setLongPressTimer] = useState<number | undefined>();

const toggleableLayers = Array.isArray(children)
? children
Expand Down Expand Up @@ -108,12 +111,14 @@ const BaseMap = ({
typeof baseLayer === "object" ? baseLayer?.[0] : baseLayer
);

const clearLongPressTimer = useCallback(() => clearTimeout(longPressTimer), [
longPressTimer
]);
const clearLongPressTimer = useCallback(() => {
if (longPressTimer) {
clearTimeout(longPressTimer);
}
}, [longPressTimer]);

const handleViewportChange = useCallback(
evt => {
(evt: ViewStateChangeEvent) => {
onViewportChanged && onViewportChanged(evt.viewState);
clearLongPressTimer();
},
Expand Down Expand Up @@ -153,17 +158,8 @@ const BaseMap = ({
// Start detecting long presses on screens when there is only one touch point.
// If the user is pinching the map or does other multi-touch actions, cancel long-press detection.
const touchPointCount = e.points.length;
if (touchPointCount === 1) {
setLongPressTimer(
setTimeout(
// In practice, MapLayerTouchEvent and MapTouchEvent behave as if they were
// subclasses of MapLayerMouseEvent and MapMouseEvent, respectively,
// so the conversion from touch to mouse event works, with the caveat that
// the `type` prop takes different string values between mouse and touch events.
() => onContextMenu((e as unknown) as MapLayerMouseEvent),
600
)
);
if (touchPointCount === 1 && onContextMenu) {
setLongPressTimer(window.setTimeout(() => onContextMenu(e), 600));
} else {
clearLongPressTimer();
}
Expand Down
2 changes: 1 addition & 1 deletion packages/base-map/src/mapControlLocale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const MAP_CONTROL_TRANSLATION_KEYS = [
const generateMapControlTranslations = (
intl: IntlShape
): Record<string, string> => {
const languageObject = {};
const languageObject: Record<string, string> = {};

MAP_CONTROL_TRANSLATION_KEYS.forEach(x => {
languageObject[x] = intl.formatMessage({
Expand Down
3 changes: 2 additions & 1 deletion packages/base-map/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"lib": ["es2019", "dom"],
"outDir": "./lib",
"rootDir": "./src",
"skipLibCheck": true
"skipLibCheck": true,
"strict": true
},
"include": ["src/**/*"],
"references": [
Expand Down
4 changes: 2 additions & 2 deletions packages/building-blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"module": "esm/index.js",
"private": false,
"devDependencies": {
"@opentripplanner/types": "workspace:^",
"@opentripplanner/core-utils": "workspace:^"
"@opentripplanner/core-utils": "workspace:^",
"@opentripplanner/types": "workspace:^"
},
"peerDependencies": {
"react": "catalog:",
Expand Down
2 changes: 1 addition & 1 deletion packages/core-utils/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentripplanner/core-utils",
"version": "16.0.4",
"version": "16.0.5",
"description": "Core functionality that is shared among numerous UI components",
"engines": {
"node": ">=13"
Expand Down
4 changes: 4 additions & 0 deletions packages/core-utils/src/__tests__/__snapshots__/query.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ exports[`query parseLocationString should return location with coordinates as na

exports[`query parseLocationString should return null for null input 1`] = `null`;

exports[`query parseLocationString should return null with name as name with no coords (but with commas) for input 1`] = `null`;

exports[`query parseLocationString should return null with name as name with no coords for input 1`] = `null`;

exports[`query planParamsToQuery should parse a depart at query 1`] = `
{
"bannedRoutes": "897ABC",
Expand Down
7 changes: 7 additions & 0 deletions packages/core-utils/src/__tests__/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ describe("query", () => {
parseLocationString("33.983929829,-87.3892387982")
).toMatchSnapshot();
});

it("should return null with name as name with no coords for input", () => {
expect(parseLocationString("1 cool street")).toMatchSnapshot();
});
it("should return null with name as name with no coords (but with commas) for input", () => {
expect(parseLocationString("148, se street")).toMatchSnapshot();
});
});

describe("planParamsToQuery", () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/core-utils/src/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ async function getFirstGeocodeResult(
if (firstResult) {
return geocoder.getLocationFromGeocodedFeature(firstResult);
}
return null;
return { name: `(Geocoder failure) ${text}` };
});
}

Expand All @@ -265,7 +265,7 @@ export function parseLocationString(value) {
? stringToCoords(parts[1])
: stringToCoords(parts[0]);
const name = parts[1] ? parts[0] : coordsToString(coordinates);
return coordinates.length === 2
return coordinates.length === 2 && coordinates.every(c => !!c)
? {
name: name || null,
lat: coordinates[0] || null,
Expand Down
4 changes: 2 additions & 2 deletions packages/timetable/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opentripplanner/timetable",
"version": "1.0.0",
"version": "1.1.0",
"description": "A component to display a timetable for a given route pattern",
"main": "lib/index.js",
"module": "esm/index.js",
Expand All @@ -11,13 +11,13 @@
"private": false,
"peerDependencies": {
"react": "catalog:",
"react-intl": "catalog:",
"styled-components": "catalog:"
},
"scripts": {
"tsc": "tsc"
},
"dependencies": {
"react-intl": "catalog:",
"toposort": "^2.0.2"
},
"devDependencies": {
Expand Down
39 changes: 27 additions & 12 deletions packages/timetable/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ const TBody = styled.tbody`
white-space: nowrap;
`;

const TH = styled.th`
const TH = styled.th<{ closed?: boolean }>`
min-width: ${COLUMN_WIDTH};
padding: 1rem;
text-decoration: ${props => (props.closed ? "line-through" : "")};
`;

const TR = styled.tr`
text-align: center;
vertical-align: middle;
`;

const TD = styled.td`
const TD = styled.td<{ closed?: boolean }>`
padding: 1rem;
text-decoration: ${props => (props.closed ? "line-through" : "")};
`;

interface PatternStop {
Expand Down Expand Up @@ -176,6 +178,8 @@ interface TimeTableProps {
route: Route;
/** Whether to show only timepoint stops in the timetable */
timepointsOnly: boolean;
/** A set of gtfsId values for stops that are closed and should be shown with strikethrough */
closedStops?: Set<string>;
/** If the topological sort of the stop IDs fails for any reason, a `false` value here
* will cause the timetable to use a fallback "naive" stop sorting
*/
Expand All @@ -194,6 +198,7 @@ interface TimeTableProps {

const TimeTable = (props: TimeTableProps): JSX.Element => {
const {
closedStops,
directionId,
errorOnStopSorting,
includeDwellStops,
Expand Down Expand Up @@ -314,27 +319,35 @@ const TimeTable = (props: TimeTableProps): JSX.Element => {
}, [allTrips, comparator]);

return (
<Table tabIndex={0}>
<thead>
<Table className="timetable-table" tabIndex={0}>
<thead className="timetable-thead">
<tr>
{(showBlockId ? [{ id: "blockIdHeader", name: "Block ID" }] : [])
.concat(filteredPatternStops)
.map((s, index) => {
return (
<TH key={index} scope="col">
<TH
className="timetable-th"
key={index}
scope="col"
closed={closedStops && closedStops.has(s.id)}
>
{s.name}
</TH>
);
})}
</tr>
</thead>
<TBody>
<TBody className="timetable-tbody">
{timetableTrips.map((t, index) => {
const rowValues: string[] = showBlockId ? [t.blockId] : [];
const rowValues: { closed: boolean; value: string }[] = showBlockId
? [{ closed: false, value: t.blockId }]
: [];
filteredPatternStops.forEach(patternStop => {
const stopDetail = t.stops.get(patternStop.id);
rowValues.push(
stopDetail
rowValues.push({
closed: closedStops?.has(patternStop.id) || false,
value: stopDetail
? intl
? intl.formatTime(stopDetail.time)
: stopDetail.time.toLocaleTimeString("en-us", {
Expand All @@ -344,13 +357,15 @@ const TimeTable = (props: TimeTableProps): JSX.Element => {
minute: "2-digit"
})
: "-"
);
});
});

return (
<TR key={index}>
<TR className="timetable-tr" key={index}>
{rowValues.map((r, rowIndex) => (
<TD key={rowIndex}>{r}</TD>
<TD className="timetable-td" key={rowIndex} closed={r.closed}>
{r.value}
</TD>
))}
</TR>
);
Expand Down
23 changes: 22 additions & 1 deletion packages/timetable/src/stories/Timetable.story.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
// eslint-disable-next-line prettier/prettier
import type { Meta, StoryObj } from "@storybook/react-vite"
import React from "react";

import TimeTable from ".."

import twinCitiesRouteMock from "../../__mocks__/route-mock.json"

const meta = {
component: TimeTable,
title: "Timetable"
title: "Timetable",
argTypes: {
closedStops: {
options: ["Grand St NE & 29th Ave NE (Direction: 1)", "46th St & I-35W (Direction: 0)"],
control: { type: "check" },
mapping: {
"Grand St NE & 29th Ave NE (Direction: 1)": "2:14634",
"46th St & I-35W (Direction: 0)": "2:53545"
}
},
directionId: {
control: { type: "radio" },
options: [0, 1]
}
}
} satisfies Meta<typeof TimeTable>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
// eslint-disable-next-line react/display-name
render: (args) => {
const { closedStops } = args;
// eslint-disable-next-line react/jsx-props-no-spreading
return <TimeTable {...args} closedStops={new Set(closedStops)} />
},
args: {
directionId: 0,
route: twinCitiesRouteMock.data.route,
Expand Down
Loading
Loading