@opentripplanner/map-popup 6.1.0-alpha.3 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentripplanner/map-popup",
3
- "version": "6.1.0-alpha.3",
3
+ "version": "7.0.0",
4
4
  "description": "A component for displaying map popup contents",
5
5
  "main": "lib/index.js",
6
6
  "module": "esm/index.js",
@@ -12,13 +12,13 @@
12
12
  "private": false,
13
13
  "dependencies": {
14
14
  "flat": "^5.0.2",
15
- "@opentripplanner/base-map": "5.1.1-alpha.1",
16
- "@opentripplanner/building-blocks": "3.0.1-alpha.1",
17
- "@opentripplanner/core-utils": "14.0.0-alpha.2",
18
- "@opentripplanner/from-to-location-picker": "4.0.1-alpha.1"
15
+ "@opentripplanner/base-map": "6.0.0",
16
+ "@opentripplanner/building-blocks": "3.0.1",
17
+ "@opentripplanner/core-utils": "13.0.1",
18
+ "@opentripplanner/from-to-location-picker": "4.0.1"
19
19
  },
20
20
  "devDependencies": {
21
- "@opentripplanner/types": "7.0.0-alpha.19"
21
+ "@opentripplanner/types": "7.0.0"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^18.2.0",
@@ -4,7 +4,7 @@ import styled from "styled-components";
4
4
  import { Station, Stop } from "@opentripplanner/types";
5
5
  import { IntlProvider } from "react-intl";
6
6
  import { Meta } from "@storybook/react";
7
- import MapPopupContents from "./index";
7
+ import MapPopupContents, { Feed } from "./index";
8
8
 
9
9
  // HOC to wrap components with IntlProvider
10
10
  const withIntl = (Story: () => JSX.Element) => (
@@ -37,38 +37,69 @@ const STOP_WITH_CODE = {
37
37
  name: "W Burnside & SW 2nd"
38
38
  };
39
39
 
40
+ const STOP_WITH_FEED_ID = {
41
+ flex: false,
42
+ code: "9526",
43
+ gtfsId: "trimet:9526",
44
+ id: "trimet:9526",
45
+ lat: 45.523009,
46
+ lon: -122.672529,
47
+ name: "W Burnside & SW 2nd"
48
+ };
49
+
50
+ const SAMPLE_FEEDS: Feed[] = [
51
+ {
52
+ feedId: "trimet",
53
+ publisher: {
54
+ name: "TriMet"
55
+ }
56
+ },
57
+ {
58
+ feedId: "c-tran",
59
+ publisher: {
60
+ name: "C-TRAN"
61
+ }
62
+ },
63
+ {
64
+ feedId: "portland-streetcar",
65
+ publisher: {
66
+ name: "Portland Streetcar"
67
+ }
68
+ }
69
+ ];
70
+
40
71
  const STATION = {
41
72
  "stroke-width": 2,
42
73
  allowDropoff: true,
43
74
  allowPickup: true,
44
- bikesAvailable: 6,
45
75
  color: "#f00",
46
76
  id: '"hub_1580"',
47
77
  isCarStation: false,
48
78
  isFloatingBike: false,
79
+ lat: 45.5219604810172,
80
+ lon: -122.6896771788597,
49
81
  name: "SW Morrison at 18th",
50
- networks: ["BIKETOWN"],
82
+ network: "BIKETOWN",
51
83
  realTimeData: true,
52
84
  spacesAvailable: 11,
53
- x: -122.6896771788597,
54
- y: 45.5219604810172
85
+ vehiclesAvailable: 6
55
86
  };
56
87
 
57
88
  const FLOATING_VEHICLE = {
58
89
  "stroke-width": 1,
59
90
  allowDropoff: false,
60
91
  allowPickup: true,
61
- bikesAvailable: 1,
62
92
  color: "#f00",
63
93
  id: '"bike_6861"',
64
94
  isCarStation: false,
65
95
  isFloatingBike: true,
96
+ lat: 45.525486666666666,
97
+ lon: -122.70486,
66
98
  name: "0541",
67
- networks: ["BIKETOWN"],
99
+ network: "BIKETOWN",
68
100
  realTimeData: true,
69
101
  spacesAvailable: 0,
70
- x: -122.70486,
71
- y: 45.525486666666666
102
+ vehiclesAvailable: 1
72
103
  };
73
104
 
74
105
  const FLOATING_CAR = {
@@ -79,12 +110,12 @@ const FLOATING_CAR = {
79
110
  id: "car_6861",
80
111
  isCarStation: false,
81
112
  isFloatingCar: true,
113
+ lat: 52.52,
114
+ lon: 13.405,
82
115
  name: "0541",
83
- networks: ["MILES"], // https://miles-mobility.com
116
+ network: "MILES", // https://miles-mobility.com
84
117
  realTimeData: true,
85
- spacesAvailable: 0,
86
- x: 13.405,
87
- y: 52.52
118
+ spacesAvailable: 0
88
119
  };
89
120
 
90
121
  const getEntityPrefixExample = (entity: Stop | Station) => {
@@ -102,6 +133,16 @@ const getEntityPrefixExample = (entity: Stop | Station) => {
102
133
  export const StopEntity = (): JSX.Element => (
103
134
  <MapPopupContents
104
135
  entity={STOP_WITH_CODE}
136
+ feeds={SAMPLE_FEEDS}
137
+ setLocation={action("setLocation")}
138
+ setViewedStop={action("setViewedStop")}
139
+ />
140
+ );
141
+
142
+ export const StopEntityWithFeedName = (): JSX.Element => (
143
+ <MapPopupContents
144
+ entity={STOP_WITH_FEED_ID}
145
+ feeds={SAMPLE_FEEDS}
105
146
  setLocation={action("setLocation")}
106
147
  setViewedStop={action("setViewedStop")}
107
148
  />
@@ -265,7 +265,7 @@ exports[`Map Popup StopEntityNoStopCode smoke-test 1`] = `
265
265
  W Burnside &amp; SW 2nd
266
266
  </header>
267
267
  <p class="styled__PopupRow-sc-12kjso7-2 ckOOWr">
268
- <button class="styled__ViewStopButton-sc-12v7ov3-0 bbtcwi">
268
+ <button class="styled__ViewStopButton-sc-12v7ov3-0 hXaHvR">
269
269
  Stop Viewer
270
270
  </button>
271
271
  </p>
@@ -385,3 +385,68 @@ exports[`Map Popup StopEntityWithEntityPrefix smoke-test 1`] = `
385
385
  </div>
386
386
  </div>
387
387
  `;
388
+
389
+ exports[`Map Popup StopEntityWithFeedName smoke-test 1`] = `
390
+ <div class="styled__MapOverlayPopup-sc-12kjso7-1 cPqJxe">
391
+ <div id="focus-trimet3A9526-popup-focus-trap"
392
+ role="presentation"
393
+ >
394
+ <header class="styled__PopupTitle-sc-12kjso7-3 jRNaQh">
395
+ W Burnside &amp; SW 2nd (TriMet 9526)
396
+ </header>
397
+ <p class="styled__PopupRow-sc-12kjso7-2 ckOOWr">
398
+ <strong>
399
+ Stop ID: 9526
400
+ </strong>
401
+ <button class="styled__ViewStopButton-sc-12v7ov3-0 hXaHvR">
402
+ Stop Viewer
403
+ </button>
404
+ </p>
405
+ <p class="styled__PopupRow-sc-12kjso7-2 ckOOWr">
406
+ <strong>
407
+ Plan a trip:
408
+ </strong>
409
+ <span class="styled__FromToPickerSpan-sc-vb4790-1 giBPod">
410
+ <span class="styled__LocationPickerSpan-sc-vb4790-0 gsVfXo">
411
+ <svg viewbox="0 0 512 512"
412
+ height="0.9em"
413
+ width="0.9em"
414
+ aria-hidden="true"
415
+ focusable="false"
416
+ fill="currentColor"
417
+ xmlns="http://www.w3.org/2000/svg"
418
+ class="StyledIconBase-sc-ea9ulj-0 ebjPRL styled__FromIcon-sc-n5xcvc-0 dDqEuj"
419
+ >
420
+ <path fill="currentColor"
421
+ d="M160 256c0-53.9 42.1-96 96-96 53 0 96 42.1 96 96 0 53-43 96-96 96-53.9 0-96-43-96-96zm352 0c0 141.4-114.6 256-256 256S0 397.4 0 256 114.6 0 256 0s256 114.6 256 256zM256 48C141.1 48 48 141.1 48 256s93.1 208 208 208 208-93.1 208-208S370.9 48 256 48z"
422
+ >
423
+ </path>
424
+ </svg>
425
+ <button class="styled__Button-sc-vb4790-2 ekklXB">
426
+ From here
427
+ </button>
428
+ </span>
429
+ <span class="styled__LocationPickerSpan-sc-vb4790-0 gsVfXo">
430
+ <svg viewbox="0 0 384 512"
431
+ height="0.9em"
432
+ width="0.9em"
433
+ aria-hidden="true"
434
+ focusable="false"
435
+ fill="currentColor"
436
+ xmlns="http://www.w3.org/2000/svg"
437
+ class="StyledIconBase-sc-ea9ulj-0 ebjPRL styled__ToIcon-sc-n5xcvc-2 gZxRwk"
438
+ >
439
+ <path fill="currentColor"
440
+ d="M215.7 499.2C267 435 384 279.4 384 192 384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2 12.3 15.3 35.1 15.3 47.4 0zM192 256c-35.3 0-64-28.7-64-64s28.7-64 64-64 64 28.7 64 64-28.7 64-64 64z"
441
+ >
442
+ </path>
443
+ </svg>
444
+ <button class="styled__Button-sc-vb4790-2 ekklXB">
445
+ To here
446
+ </button>
447
+ </span>
448
+ </span>
449
+ </p>
450
+ </div>
451
+ </div>
452
+ `;
package/src/index.tsx CHANGED
@@ -1,20 +1,26 @@
1
1
  import React, { useCallback } from "react";
2
2
  import FromToLocationPicker from "@opentripplanner/from-to-location-picker";
3
- import coreUtils from "@opentripplanner/core-utils";
4
3
 
5
4
  // eslint-disable-next-line prettier/prettier
6
- import type { Company, ConfiguredCompany, Location, Station, Stop, StopEventHandler } from "@opentripplanner/types";
5
+ import type {
6
+ Company,
7
+ ConfiguredCompany,
8
+ Location,
9
+ Stop,
10
+ StopEventHandler,
11
+ TileLayerStation
12
+ } from "@opentripplanner/types";
7
13
 
8
14
  import { FocusTrapWrapper } from "@opentripplanner/building-blocks";
9
15
  import { flatten } from "flat";
10
16
  import { FormattedMessage, useIntl } from "react-intl";
11
17
  import { Styled } from "@opentripplanner/base-map";
12
18
 
19
+ import { Entity, getNetwork, makeDefaultGetEntityName, type StopIdAgencyMap } from "./util";
13
20
  import { ViewStopButton } from "./styled";
14
21
 
15
22
  // Load the default messages.
16
23
  import defaultEnglishMessages from "../i18n/en-US.yml";
17
- import { makeDefaultGetEntityName } from "./util";
18
24
 
19
25
  // HACK: We should flatten the messages loaded above because
20
26
  // the YAML loaders behave differently between webpack and our version of jest:
@@ -22,6 +28,14 @@ import { makeDefaultGetEntityName } from "./util";
22
28
  // - the yaml loader for jest returns messages with flattened ids.
23
29
  export const defaultMessages: { [key: string]: string } = flatten(defaultEnglishMessages);
24
30
 
31
+ export type Feed = {
32
+ feedId: string;
33
+ publisher: {
34
+ name: string;
35
+ };
36
+ };
37
+
38
+
25
39
  const generateLocation = (entity: Entity, name: string) => {
26
40
  // @ts-expect-error some of these values may be null, but that's ok
27
41
  const { lon: entityLon, lat: entityLat, x, y } = entity
@@ -33,7 +47,7 @@ const generateLocation = (entity: Entity, name: string) => {
33
47
  return { lat, lon, name };
34
48
  }
35
49
 
36
- const StationHubDetails = ({ station }: { station: Station }) => {
50
+ const StationHubDetails = ({ station }: { station: TileLayerStation }) => {
37
51
  return (
38
52
  <Styled.PopupRow>
39
53
  <div>
@@ -43,7 +57,7 @@ const StationHubDetails = ({ station }: { station: Station }) => {
43
57
  }
44
58
  description="Label text for the number of bikes available"
45
59
  id="otpUi.MapPopup.availableBikes"
46
- values={{ value: station.bikesAvailable }}
60
+ values={{ value: station.vehiclesAvailable }}
47
61
  />
48
62
  </div>
49
63
  <div>
@@ -85,37 +99,55 @@ const StopDetails = ({ id, setViewedStop }: { id: string, setViewedStop: () => v
85
99
  )
86
100
  }
87
101
 
88
- type Entity = Stop | Station
89
102
  type Props = {
90
- closePopup?: (arg?: any) => void
103
+ closePopup?: (arg?: boolean) => void
91
104
  configCompanies?: ConfiguredCompany[];
92
105
  entity: Entity
93
- getEntityName?: (entity: Entity, configCompanies: Company[],) => string;
106
+ getEntityName?: (entity: Entity, configCompanies: Company[], feedName?: string) => string;
94
107
  getEntityPrefix?: (entity: Entity) => JSX.Element
108
+ feeds?: Feed[]
95
109
  setLocation?: ({ location, locationType }: { location: Location, locationType: string }) => void;
96
110
  setViewedStop?: StopEventHandler;
97
111
  };
98
112
 
99
- function entityIsStation(entity: Entity): entity is Station {
100
- return "bikesAvailable" in entity
113
+ function entityIsStation(entity: Entity): entity is TileLayerStation {
114
+ return "vehiclesAvailable" in entity
101
115
  }
102
116
 
103
117
  /**
104
118
  * Renders a map popup for a stop, scooter, or shared bike
105
119
  */
106
- export function MapPopup({ closePopup = () => {}, configCompanies, entity, getEntityName, getEntityPrefix, setLocation, setViewedStop }: Props): JSX.Element {
120
+ export function MapPopup({
121
+ closePopup = () => {},
122
+ configCompanies,
123
+ entity,
124
+ getEntityName,
125
+ getEntityPrefix,
126
+ setLocation,
127
+ setViewedStop,
128
+ feeds,
129
+ }: Props): JSX.Element {
107
130
 
108
131
  const intl = useIntl()
109
132
  if (!entity) return <></>
110
133
 
111
134
  const getNameFunc = getEntityName || makeDefaultGetEntityName(intl, defaultMessages);
112
- const name = getNameFunc(entity, configCompanies);
113
-
114
- const stationNetwork = "networks" in entity && (coreUtils.itinerary.getCompaniesLabelFromNetworks(entity?.networks || [], configCompanies) || entity?.networks?.[0]);
135
+
136
+ // Find the feed name using the logic from generateLabel in otp.ts
137
+ let feedName: string | undefined;
138
+ if (feeds && entity.id) {
139
+ const feedId = entity.id.split(":")[0];
140
+ const feed = feeds.find(f => f.feedId === feedId);
141
+ feedName = feed?.publisher?.name;
142
+ }
143
+
144
+ const name = getNameFunc(entity, configCompanies, feedName);
145
+
146
+ const stationNetwork = getNetwork(entity, configCompanies);
115
147
 
116
148
  const bikesAvailablePresent = entityIsStation(entity)
117
- const entityIsStationHub = bikesAvailablePresent && entity?.bikesAvailable !== undefined && !entity?.isFloatingBike;
118
- const stopId = !bikesAvailablePresent && entity?.code;
149
+ const entityIsStationHub = bikesAvailablePresent && entity.vehiclesAvailable !== undefined && !entity.isFloatingBike;
150
+ const stopId = !bikesAvailablePresent && "code" in entity && entity.code;
119
151
  const id = `focus-${encodeURIComponent(entity.id).replace(/%/g, "")}-popup`
120
152
 
121
153
  return (
@@ -137,7 +169,7 @@ export function MapPopup({ closePopup = () => {}, configCompanies, entity, getEn
137
169
  {setViewedStop && !bikesAvailablePresent && (
138
170
  <StopDetails
139
171
  id={stopId}
140
- setViewedStop={useCallback(() => setViewedStop(entity), [entity])}
172
+ setViewedStop={useCallback(() => setViewedStop(entity as Stop), [entity])}
141
173
  />
142
174
  )}
143
175
 
@@ -157,4 +189,5 @@ export function MapPopup({ closePopup = () => {}, configCompanies, entity, getEn
157
189
  );
158
190
  }
159
191
 
160
- export default MapPopup;
192
+ export default MapPopup;
193
+ export { type StopIdAgencyMap };
package/src/util.ts CHANGED
@@ -1,25 +1,37 @@
1
- import { Company, Station, Stop } from "@opentripplanner/types";
1
+ import {
2
+ Agency,
3
+ Company,
4
+ Station,
5
+ Stop,
6
+ TileLayerStation
7
+ } from "@opentripplanner/types";
2
8
  import { IntlShape } from "react-intl";
3
9
  import coreUtils from "@opentripplanner/core-utils";
4
10
 
11
+ export type StopIdAgencyMap = Record<string, Agency>;
12
+ export type Entity = Station | Stop | TileLayerStation;
13
+
14
+ export function getNetwork(entity: Entity, configCompanies: Company[]): string {
15
+ return (
16
+ "network" in entity &&
17
+ (coreUtils.itinerary.getCompaniesLabelFromNetworks(
18
+ [entity.network] || [],
19
+ configCompanies
20
+ ) ||
21
+ entity.network)
22
+ );
23
+ }
24
+
5
25
  // eslint-disable-next-line import/prefer-default-export
6
26
  export function makeDefaultGetEntityName(
7
27
  intl: IntlShape,
8
28
  defaultEnglishMessages: { [key: string]: string }
9
29
  ) {
10
30
  return function defaultGetEntityName(
11
- entity: Station | Stop,
12
- configCompanies: Company[]
31
+ entity: Entity,
32
+ configCompanies: Company[],
33
+ feedName?: string
13
34
  ): string | null {
14
- // TODO: Stop generating this / passing it to the car string? Is it needed?
15
- // In English we say "Car: " instead
16
- const stationNetworks =
17
- "networks" in entity &&
18
- (coreUtils.itinerary.getCompaniesLabelFromNetworks(
19
- entity?.networks || [],
20
- configCompanies
21
- ) ||
22
- entity?.networks?.[0]);
23
35
  let stationName: string | null = entity.name || entity.id;
24
36
  // If the station name or id is a giant UUID (with more than 3 "-" characters)
25
37
  // best not to show that at all. The company name will still be shown.
@@ -41,6 +53,9 @@ export function makeDefaultGetEntityName(
41
53
  { name: stationName }
42
54
  );
43
55
  } else if ("isFloatingCar" in entity && entity.isFloatingCar) {
56
+ // TODO: Stop generating this / passing it to the car string? Is it needed?
57
+ // In English we say "Car: " instead
58
+ const stationNetwork = getNetwork(entity, configCompanies);
44
59
  stationName = intl.formatMessage(
45
60
  {
46
61
  defaultMessage: defaultEnglishMessages["otpUi.MapPopup.floatingCar"],
@@ -48,7 +63,7 @@ export function makeDefaultGetEntityName(
48
63
  id: "otpUi.MapPopup.floatingCar"
49
64
  },
50
65
  {
51
- company: stationNetworks,
66
+ company: stationNetwork,
52
67
  name: stationName
53
68
  }
54
69
  );
@@ -63,6 +78,8 @@ export function makeDefaultGetEntityName(
63
78
  },
64
79
  { name: stationName }
65
80
  );
81
+ } else if (feedName && "code" in entity) {
82
+ stationName = `${stationName} (${feedName} ${entity.code})`;
66
83
  }
67
84
  return stationName;
68
85
  };
package/tsconfig.json CHANGED
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "extends": "../../tsconfig.json",
3
-
4
3
  "compilerOptions": {
5
4
  "composite": true,
6
5
  "outDir": "./lib",