@geops/rvf-mobility-web-component 0.1.81 → 0.1.84

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/index.js +206 -187
  3. package/package.json +2 -2
  4. package/src/Departure/Departure.tsx +6 -3
  5. package/src/FeatureDetails/FeatureDetails.tsx +4 -4
  6. package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +22 -0
  7. package/src/LayerTreeMenu/LayerTreeMenu.tsx +12 -4
  8. package/src/LayoutState/LayoutState.tsx +144 -63
  9. package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +2 -2
  10. package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +2 -40
  11. package/src/MapLayout/MapLayout.tsx +2 -7
  12. package/src/MobilityMap/MobilityMap.tsx +1 -6
  13. package/src/MobilityMap/MobilityMapAttributes.ts +28 -7
  14. package/src/MobilityNotifications/MobilityNotifications.tsx +1 -0
  15. package/src/NotificationDetails/NotificationDetails.tsx +21 -22
  16. package/src/NotificationsLayer/NotificationsLayer.tsx +49 -3
  17. package/src/OverlayDetails/OverlayDetails.tsx +5 -0
  18. package/src/RealtimeLayer/RealtimeLayer.tsx +44 -50
  19. package/src/RouteIcon/RouteIcon.tsx +25 -14
  20. package/src/RouteSchedule/RouteSchedule.tsx +14 -11
  21. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +6 -3
  22. package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +8 -2
  23. package/src/RouteStop/RouteStop.tsx +14 -29
  24. package/src/RouteStopPlatform/RouteStopPlatform.tsx +1 -3
  25. package/src/RouteStopProgress/RouteStopProgress.tsx +11 -18
  26. package/src/RouteStopStation/RouteStopStation.tsx +3 -3
  27. package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +16 -3
  28. package/src/RvfSearch/RvfSearch.tsx +5 -4
  29. package/src/Search/Search.tsx +41 -20
  30. package/src/Search/SearchBase.tsx +169 -0
  31. package/src/Search/SearchHeadless.tsx +79 -0
  32. package/src/Search/index.tsx +2 -0
  33. package/src/SearchLinesResult/SearchLinesResult.tsx +43 -0
  34. package/src/SearchLinesResult/index.tsx +1 -0
  35. package/src/SearchLinesResults/SearchLinesResults.tsx +106 -0
  36. package/src/SearchLinesResults/index.tsx +1 -0
  37. package/src/SearchLnpStopsResult/SearchLnpStopsResult.tsx +42 -0
  38. package/src/SearchLnpStopsResult/index.tsx +1 -0
  39. package/src/SearchLnpStopsResults/SearchLnpStopsResults.tsx +106 -0
  40. package/src/SearchLnpStopsResults/index.tsx +1 -0
  41. package/src/SearchResult/SearchResult.tsx +25 -0
  42. package/src/SearchResult/index.tsx +1 -0
  43. package/src/SearchResults/SearchResults.tsx +81 -0
  44. package/src/SearchResults/index.tsx +1 -0
  45. package/src/SearchResultsHeader/SearchResultsHeader.tsx +36 -0
  46. package/src/SearchResultsHeader/index.tsx +1 -0
  47. package/src/SearchStopsResult/SearchStopsResult.tsx +43 -0
  48. package/src/SearchStopsResult/index.tsx +1 -0
  49. package/src/SearchStopsResults/SearchStopsResults.tsx +101 -0
  50. package/src/SearchStopsResults/index.tsx +1 -0
  51. package/src/SearchTrainsResult/SearchTrainsResult.tsx +42 -0
  52. package/src/SearchTrainsResult/index.tsx +1 -0
  53. package/src/SearchTrainsResults/SearchTrainsResults.tsx +109 -0
  54. package/src/SearchTrainsResults/index.tsx +1 -0
  55. package/src/SingleClickListener/SingleClickListener.tsx +38 -2
  56. package/src/SituationDetails/SituationDetails.tsx +7 -6
  57. package/src/Station/Station.tsx +23 -48
  58. package/src/StationHeader/StationHeader.tsx +3 -3
  59. package/src/StationsLayer/StationsLayer.tsx +9 -1
  60. package/src/StopsSearch/StopsSearch.tsx +2 -2
  61. package/src/ui/InputSearch/InputSearch.tsx +105 -0
  62. package/src/ui/InputSearch/index.tsx +1 -0
  63. package/src/utils/centerOnVehicle.ts +9 -2
  64. package/src/utils/constants.ts +8 -1
  65. package/src/utils/fullTrajectoryStyle.ts +4 -7
  66. package/src/utils/getBgColor.ts +4 -2
  67. package/src/utils/getDelayColorForVehicle.test.ts +21 -11
  68. package/src/utils/getDelayColorForVehicle.ts +7 -5
  69. package/src/utils/getDelayTextForVehicle.test.ts +12 -12
  70. package/src/utils/getDelayTextForVehicle.ts +4 -0
  71. package/src/utils/getMainColorForVehicle.ts +11 -4
  72. package/src/utils/getRadius.ts +9 -3
  73. package/src/utils/getTextColor.ts +1 -1
  74. package/src/utils/getTextColorForVehicle.ts +29 -0
  75. package/src/utils/getTextFontForVehicle.test.ts +1 -1
  76. package/src/utils/getTextFontForVehicle.tsx +11 -3
  77. package/src/utils/getTextForVehicle.ts +7 -1
  78. package/src/utils/hooks/useFit.tsx +69 -0
  79. package/src/utils/hooks/useFitOnFeatures.tsx +77 -0
  80. package/src/utils/hooks/useLayersConfig.tsx +3 -0
  81. package/src/utils/hooks/useLnp.tsx +39 -5
  82. package/src/utils/hooks/useMapContext.tsx +2 -5
  83. package/src/utils/hooks/useRealtimeDepartures.tsx +45 -0
  84. package/src/utils/hooks/useRealtimeRenderedTrajectory.tsx +42 -0
  85. package/src/utils/hooks/useRealtimeStation.tsx +39 -0
  86. package/src/utils/hooks/useRealtimeStopSequences.tsx +43 -0
  87. package/src/utils/hooks/useRealtimeTrainsByRouteIdentifier.tsx +71 -0
  88. package/src/utils/hooks/useRouteStop.tsx +7 -1
  89. package/src/utils/hooks/useSearchLines.tsx +34 -0
  90. package/src/utils/hooks/useSearchLnpStops.tsx +38 -0
  91. package/src/utils/hooks/useSearchStops.tsx +85 -0
  92. package/src/utils/hooks/useSearchTrains.tsx +83 -0
  93. package/src/utils/realtimeRVFStyle.ts +38 -30
  94. package/src/utils/translations.ts +17 -0
  95. package/tash +58 -0
  96. package/src/utils/centerOnStation.ts +0 -18
  97. package/src/utils/getDelayFontForVehicle.test.ts +0 -7
  98. package/src/utils/getDelayFontForVehicle.tsx +0 -8
  99. package/src/utils/hooks/useStation.tsx +0 -22
@@ -2,31 +2,31 @@ import getDelayTextForVehicle from "./getDelayTextForVehicle";
2
2
 
3
3
  describe("getDelayTextForVehicle", () => {
4
4
  it("returns cancelled character", () => {
5
- expect(getDelayTextForVehicle(7200000, true)).toBe(
5
+ expect(getDelayTextForVehicle(null, null, 7200000, true)).toBe(
6
6
  String.fromCodePoint(0x00d7),
7
7
  );
8
8
  });
9
9
 
10
10
  it("returns hours (floor)", () => {
11
- expect(getDelayTextForVehicle(7200000)).toBe("+2h");
12
- expect(getDelayTextForVehicle(7255555)).toBe("+2h1m");
11
+ expect(getDelayTextForVehicle(null, null, 7200000)).toBe("+2h");
12
+ expect(getDelayTextForVehicle(null, null, 7255555)).toBe("+2h1m");
13
13
  });
14
14
 
15
15
  it("returns minutes (round)", () => {
16
- expect(getDelayTextForVehicle(120000)).toBe("+2m");
17
- expect(getDelayTextForVehicle(151000)).toBe("+3m");
16
+ expect(getDelayTextForVehicle(null, null, 120000)).toBe("+2m");
17
+ expect(getDelayTextForVehicle(null, null, 151000)).toBe("+3m");
18
18
  });
19
19
 
20
20
  it("doesn't display seconds", () => {
21
- expect(getDelayTextForVehicle(1000)).toBe("");
22
- expect(getDelayTextForVehicle(30000)).toBe("+1m");
23
- expect(getDelayTextForVehicle(7255555)).toBe("+2h1m");
21
+ expect(getDelayTextForVehicle(null, null, 1000)).toBe("");
22
+ expect(getDelayTextForVehicle(null, null, 30000)).toBe("+1m");
23
+ expect(getDelayTextForVehicle(null, null, 7255555)).toBe("+2h1m");
24
24
  });
25
25
 
26
26
  it("returns empty value", () => {
27
- expect(getDelayTextForVehicle(1000)).toBe("");
28
- expect(getDelayTextForVehicle(null)).toBe("");
29
- expect(getDelayTextForVehicle(undefined)).toBe("");
30
- expect(getDelayTextForVehicle(0)).toBe("");
27
+ expect(getDelayTextForVehicle(null, null, 1000)).toBe("");
28
+ expect(getDelayTextForVehicle(null, null, null)).toBe("");
29
+ expect(getDelayTextForVehicle(null, null, undefined)).toBe("");
30
+ expect(getDelayTextForVehicle(null, null, 0)).toBe("");
31
31
  });
32
32
  });
@@ -1,11 +1,15 @@
1
1
  import getDelayString from "./getDelayString";
2
2
 
3
+ import type { RealtimeTrajectory, ViewState } from "mobility-toolbox-js/types";
4
+
3
5
  /**
4
6
  * This function returns the text displays near the vehicle.
5
7
  * We use getDelayString inside it to make sure that RouteSchedule and
6
8
  * the map have the same values.
7
9
  */
8
10
  const getDelayTextForVehicle = (
11
+ trajectory: RealtimeTrajectory,
12
+ viewState: ViewState,
9
13
  delayInMs: number,
10
14
  cancelled = false,
11
15
  ): string => {
@@ -1,3 +1,5 @@
1
+ import { realtimeStyleUtils } from "mobility-toolbox-js/ol";
2
+
1
3
  import getBgColor from "./getBgColor";
2
4
 
3
5
  import type {
@@ -8,11 +10,13 @@ import type {
8
10
  RealtimeTrajectory,
9
11
  } from "mobility-toolbox-js/types";
10
12
 
11
- // This function returns the main color of a line using a line, trajectory, stopsequence or departure object.
13
+ /**
14
+ * Return the color depending on an object representing a vehicle or a line.
15
+ * This function is used to have the same color on the map and on other components.
16
+ */
12
17
  const getMainColorForVehicle = (object: unknown = null): string => {
13
18
  const line =
14
19
  (object as RealtimeTrajectory)?.properties?.line ||
15
- // @ts-expect-error bad type definition
16
20
  (object as RealtimeStopSequence)?.line ||
17
21
  (object as RealtimeLine);
18
22
 
@@ -35,10 +39,13 @@ const getMainColorForVehicle = (object: unknown = null): string => {
35
39
  type = "rail";
36
40
  }
37
41
  }
38
- color = getBgColor(type, line);
42
+ color =
43
+ getBgColor(type, line) ||
44
+ realtimeStyleUtils.getColorForType(type) ||
45
+ realtimeStyleUtils.getColorForType("rail");
39
46
  }
40
47
 
41
- if (color && color[0] !== "#") {
48
+ if (color && !color.startsWith("#")) {
42
49
  color = `#${color}`;
43
50
  }
44
51
 
@@ -1,5 +1,7 @@
1
1
  import { realtimeConfig } from "mobility-toolbox-js/ol";
2
2
 
3
+ import type { RealtimeMot } from "mobility-toolbox-js/types";
4
+
3
5
  // mobility-portal config
4
6
  const trackerRadiusMapping = {
5
7
  0: [0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7], // Tram
@@ -28,11 +30,15 @@ const radiusMapping: Record<number, number[]> = trackerRadiusMapping;
28
30
  // [0, 0, 0, 0, 0, 2, 2, 3, 7, 7, 7, 7, 7, 7, 12, 12, 15],
29
31
  // ];
30
32
 
31
- export const getRadius = (type = 0, zoom = 0, cancelled = false) => {
33
+ export const getRadius = (
34
+ type: number | RealtimeMot = 0,
35
+ zoom = 0,
36
+ cancelled = false,
37
+ ) => {
38
+ const z = Math.min(Math.floor(zoom || 1), 16);
32
39
  try {
33
- // @ts-expect-error - bad type definition
34
40
  const typeIdx = realtimeConfig.getTypeIndex(type);
35
- return radiusMapping[typeIdx][zoom] * (cancelled ? 2 : 1);
41
+ return radiusMapping[typeIdx][z] * (cancelled ? 2 : 1);
36
42
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
43
  } catch (e) {
38
44
  return 1;
@@ -4,7 +4,7 @@ const getTextColor = (type) => {
4
4
  if (type === "bus" || type === "tram") {
5
5
  return "#fff";
6
6
  }
7
- return realtimeConfig.getTextColor(type);
7
+ return realtimeConfig.getTextColorForType(type);
8
8
  };
9
9
 
10
10
  export default getTextColor;
@@ -0,0 +1,29 @@
1
+ import { realtimeConfig } from "mobility-toolbox-js/ol";
2
+
3
+ import type {
4
+ RealtimeDeparture,
5
+ RealtimeLine,
6
+ RealtimeStopSequence,
7
+ RealtimeTrajectory,
8
+ } from "mobility-toolbox-js/types";
9
+
10
+ import type { LnpLineInfo } from "./hooks/useLnp";
11
+
12
+ const getTextColorForVehicle = (object: unknown) => {
13
+ const textColor =
14
+ (object as LnpLineInfo | RealtimeLine).text_color ||
15
+ (object as RealtimeDeparture | RealtimeStopSequence).line?.text_color ||
16
+ (object as RealtimeTrajectory).properties?.line?.text_color;
17
+
18
+ if (textColor) {
19
+ return textColor;
20
+ }
21
+
22
+ const type =
23
+ (object as RealtimeStopSequence).type ||
24
+ (object as RealtimeTrajectory).properties?.type;
25
+
26
+ return realtimeConfig.getTextColorForType(type);
27
+ };
28
+
29
+ export default getTextColorForVehicle;
@@ -2,6 +2,6 @@ import getTextFontForVehicle from "./getTextFontForVehicle";
2
2
 
3
3
  describe("getTextFontForVehicle", () => {
4
4
  it("returns font that inherit", () => {
5
- expect(getTextFontForVehicle(12)).toBe("bold 12px arial");
5
+ expect(getTextFontForVehicle(null, null, 12)).toBe("bold 12px arial");
6
6
  });
7
7
  });
@@ -1,8 +1,16 @@
1
+ import type { ViewState } from "mobility-toolbox-js/types";
2
+
1
3
  /**
2
- * Return the font for the delay text in the map.
4
+ * Return the font depending on an object representing a vehicle or a line.
5
+ * This function is used to have the same font on the map and on other components.
3
6
  */
4
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
5
- const getTextFontForVehicle = (fontSize: number, text?: string) => {
7
+ const getTextFontForVehicle = (
8
+ object?: unknown,
9
+ viewState?: ViewState,
10
+ fontSize?: number,
11
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
+ text?: string,
13
+ ) => {
6
14
  return `bold ${fontSize}px arial`;
7
15
  };
8
16
 
@@ -4,12 +4,18 @@ import type {
4
4
  RealtimeTrajectory,
5
5
  } from "mobility-toolbox-js/types";
6
6
 
7
+ import type { LnpLineInfo } from "./hooks/useLnp";
8
+
9
+ /**
10
+ * Return the text depending on an object representing a vehicle or a line.
11
+ * This function is used to have the same text on the map and on other components.
12
+ */
7
13
  const getTextForVehicle = (object: unknown = ""): string => {
8
14
  const name =
9
15
  (object as RealtimeTrajectory)?.properties?.line?.name ||
10
- // @ts-expect-error bad type definition
11
16
  (object as RealtimeStopSequence)?.line?.name ||
12
17
  (object as RealtimeLine)?.name ||
18
+ (object as LnpLineInfo)?.short_name ||
13
19
  (object as string) ||
14
20
  "";
15
21
 
@@ -0,0 +1,69 @@
1
+ import { Feature } from "ol";
2
+ import { Point } from "ol/geom";
3
+ import { fromExtent } from "ol/geom/Polygon";
4
+ import { useEffect, useRef } from "preact/hooks";
5
+
6
+ import useFitOnFeatures from "./useFitOnFeatures";
7
+
8
+ import type {
9
+ RealtimeRouteIdentifierMatch,
10
+ RealtimeTrainDetail,
11
+ } from "mobility-toolbox-js/types";
12
+ import type { GeoJSONFeature } from "ol/format/GeoJSON";
13
+
14
+ import type { LnpLineInfo, LnpStopInfo } from "./useLnp";
15
+ import type { StopsFeature } from "./useSearchStops";
16
+
17
+ export type FitOnObject = (obj: FitObject, willOverlayOpen?: boolean) => void;
18
+
19
+ export type FitObject =
20
+ | LnpLineInfo
21
+ | LnpStopInfo
22
+ | RealtimeRouteIdentifierMatch
23
+ | RealtimeTrainDetail
24
+ | StopsFeature;
25
+
26
+ function useFit() {
27
+ const fitOnFeatures = useFitOnFeatures();
28
+ const fit = useRef<FitOnObject>();
29
+
30
+ useEffect(() => {
31
+ fit.current = (obj: FitObject, willOverlayOpen = false) => {
32
+ if (
33
+ (obj as StopsFeature)?.type === "Feature" &&
34
+ (obj as StopsFeature).geometry
35
+ ) {
36
+ fitOnFeatures.current([obj as GeoJSONFeature], willOverlayOpen);
37
+ return;
38
+ }
39
+ let extent =
40
+ (obj as LnpLineInfo)?.extent || (obj as RealtimeTrainDetail)?.bounds;
41
+
42
+ if ((obj as RealtimeRouteIdentifierMatch)?.trains?.length) {
43
+ extent = (obj as RealtimeRouteIdentifierMatch).trains[0].bounds as [
44
+ number,
45
+ number,
46
+ number,
47
+ number,
48
+ ];
49
+ }
50
+
51
+ if (extent) {
52
+ const feature = new Feature(fromExtent(extent));
53
+ fitOnFeatures.current([feature], willOverlayOpen);
54
+ return;
55
+ }
56
+
57
+ const coordinate = (obj as LnpStopInfo)?.coordinates;
58
+ if (coordinate) {
59
+ const feature = new Feature(new Point(coordinate));
60
+ fitOnFeatures.current([feature], willOverlayOpen);
61
+ return;
62
+ }
63
+ };
64
+ }, [fitOnFeatures]);
65
+
66
+ return fit;
67
+ }
68
+
69
+ export default useFit;
@@ -0,0 +1,77 @@
1
+ import { GeoJSON } from "ol/format";
2
+ import { Vector } from "ol/source";
3
+ import { useEffect, useRef } from "preact/hooks";
4
+
5
+ import { FIT_ON_FEATURES_MAX_ZOOM_POINT } from "../constants";
6
+
7
+ import useMapContext from "./useMapContext";
8
+
9
+ import type { Feature, Map } from "ol";
10
+ import type { GeoJSONFeature } from "ol/format/GeoJSON";
11
+ const geojson = new GeoJSON();
12
+
13
+ export type FitOnFeatures = (
14
+ features: (Feature | GeoJSONFeature)[],
15
+ willOverlayOpen?: boolean,
16
+ map?: Map,
17
+ ) => void;
18
+
19
+ const useFitOnFeatures = () => {
20
+ const { isOverlayOpen, map: contextMap } = useMapContext();
21
+
22
+ const isOverlayOpenRef = useRef(isOverlayOpen);
23
+ const fitOnFeatures = useRef<FitOnFeatures>();
24
+
25
+ useEffect(() => {
26
+ isOverlayOpenRef.current = isOverlayOpen;
27
+ }, [isOverlayOpen]);
28
+
29
+ useEffect(() => {
30
+ fitOnFeatures.current = (
31
+ features: (Feature | GeoJSONFeature)[],
32
+ willOverlayOpen = false,
33
+ map?: Map,
34
+ ) => {
35
+ if ((!map && !contextMap) || !features?.length) {
36
+ return;
37
+ }
38
+ let feats = features as Feature[];
39
+
40
+ // Convert to ol features if GeoJSON
41
+ const geoJSONFeature = features?.[0] as GeoJSONFeature;
42
+ if (geoJSONFeature?.geometry && geoJSONFeature?.type === "Feature") {
43
+ // Single feature case
44
+ feats = geojson.readFeatures(
45
+ {
46
+ features,
47
+ type: "FeatureCollection",
48
+ },
49
+ { featureProjection: "EPSG:3857" },
50
+ );
51
+ }
52
+
53
+ const mapToUse = map || contextMap;
54
+ const extent = new Vector({ features: feats }).getExtent();
55
+ mapToUse.getView().fit(extent, {
56
+ duration: 500,
57
+ maxZoom:
58
+ extent[0] === extent[2] || extent[1] === extent[3]
59
+ ? FIT_ON_FEATURES_MAX_ZOOM_POINT
60
+ : undefined,
61
+ padding: [
62
+ 50,
63
+ 50,
64
+ 50,
65
+ willOverlayOpen || isOverlayOpenRef.current ? 350 : 50,
66
+ ],
67
+ });
68
+ return () => {
69
+ mapToUse?.getView().cancelAnimations();
70
+ };
71
+ };
72
+ }, [contextMap]);
73
+
74
+ return fitOnFeatures;
75
+ };
76
+
77
+ export default useFitOnFeatures;
@@ -4,10 +4,13 @@ import { LAYERS_NAMES } from "../constants";
4
4
 
5
5
  import useMapContext from "./useMapContext";
6
6
 
7
+ import type { LAYER_TREE_HIDE_PROP } from "../constants";
8
+
7
9
  export interface LayerConfig {
8
10
  featurelink?: {
9
11
  href?: string;
10
12
  };
13
+ [LAYER_TREE_HIDE_PROP]?: boolean;
11
14
  link?: {
12
15
  href?: string;
13
16
  show?: boolean;
@@ -6,8 +6,9 @@ import useMapContext from "./useMapContext";
6
6
 
7
7
  import type { VectorTileSource } from "maplibre-gl";
8
8
 
9
- export interface LineInfo {
9
+ export interface LnpLineInfo {
10
10
  color: string;
11
+ extent: [number, number, number, number];
11
12
  external_id: string;
12
13
  id: string;
13
14
  long_name: string;
@@ -18,16 +19,19 @@ export interface LineInfo {
18
19
  text_color: string;
19
20
  }
20
21
 
21
- export interface StopInfo {
22
+ export interface LnpStopInfo {
23
+ codes: string[];
24
+ coordinates: [number, number];
22
25
  external_id: string;
23
26
  importance: number;
24
27
  long_name: string;
25
28
  short_name: string;
29
+ tralis_network: string;
26
30
  visibility_level: number;
27
31
  }
28
32
 
29
- export type LinesInfos = Record<string, LineInfo>;
30
- export type StopsInfos = Record<string, StopInfo>;
33
+ export type LinesInfos = Record<string, LnpLineInfo>;
34
+ export type StopsInfos = Record<string, LnpStopInfo>;
31
35
 
32
36
  let cacheLnpSourceInfo: {
33
37
  [LNP_MD_LINES]: LinesInfos;
@@ -91,7 +95,7 @@ export function useLnpStopsInfos(): StopsInfos {
91
95
  * This hook search line informations from lnp data. It takes a string in
92
96
  * parameter then it will search if there is a property that exactly match this value.
93
97
  */
94
- function useLnpLineInfo(text: string): LineInfo {
98
+ function useLnpLineInfo(text: string): LnpLineInfo {
95
99
  const linesInfos = useLnpLinesInfos();
96
100
 
97
101
  if (!linesInfos || !text) {
@@ -109,4 +113,34 @@ function useLnpLineInfo(text: string): LineInfo {
109
113
  });
110
114
  }
111
115
 
116
+ /**
117
+ * This hook search line informations from lnp data. It takes a string in
118
+ * parameter then it will search if there is a property that exactly match this value.
119
+ */
120
+ export function useLnpStopInfo(text: string): LnpStopInfo {
121
+ const stationsInfos = useLnpStopsInfos();
122
+
123
+ if (!stationsInfos || !text) {
124
+ return null;
125
+ }
126
+
127
+ if (stationsInfos[text]) {
128
+ return stationsInfos[text];
129
+ }
130
+
131
+ return Object.values(stationsInfos).find((info) => {
132
+ return (
133
+ ["id", "external_id", "short_name", "long_name"].find((key) => {
134
+ return !!info[key] && info[key].toLowerCase() === text.toLowerCase();
135
+ }) ||
136
+ info?.codes?.find((code) => {
137
+ return (
138
+ code.toLowerCase() === text.toLowerCase() ||
139
+ code.split(":")[1]?.toLowerCase() === text.toLowerCase()
140
+ );
141
+ })
142
+ );
143
+ });
144
+ }
145
+
112
146
  export default useLnpLineInfo;
@@ -97,14 +97,12 @@ export type MapContextType = {
97
97
  setSelectedFeature: (feature: Feature) => void;
98
98
  setSelectedFeatures: (features: Feature[]) => void;
99
99
  setStation: (station?: RealtimeStation) => void;
100
- setStationId: (stationId?: RealtimeStationId) => void;
100
+ setStationId: (stationId?: RealtimeStationId | string) => void;
101
101
  setStationsLayer: (stationsLayer?: MaplibreStyleLayer) => void;
102
- setStopSequence: (stopSequence?: RealtimeStopSequence) => void;
103
102
  setTrainId: (trainId?: RealtimeTrainId) => void;
104
103
  station: RealtimeStation;
105
- stationId: RealtimeStationId;
104
+ stationId: RealtimeStationId | string;
106
105
  stationsLayer: MaplibreStyleLayer;
107
- stopSequence: RealtimeStopSequence;
108
106
  trainId: RealtimeTrainId;
109
107
  } & MobilityMapProps;
110
108
 
@@ -150,7 +148,6 @@ export const MapContext = createContext<MapContextType>({
150
148
  setStation: (station?: RealtimeStation) => {},
151
149
  setStationId: (stationId?: RealtimeStationId) => {},
152
150
  setStationsLayer: (stationsLayer?: MaplibreStyleLayer) => {},
153
- setStopSequence: (stopSequence?: RealtimeStopSequence) => {},
154
151
  setTrainId: (trainId?: RealtimeTrainId) => {},
155
152
  } as MapContextType);
156
153
 
@@ -0,0 +1,45 @@
1
+ import { debounceDeparturesMessages } from "mobility-toolbox-js/ol";
2
+ import { useEffect, useMemo, useState } from "preact/hooks";
3
+
4
+ import useMapContext from "./useMapContext";
5
+
6
+ import type { RealtimeDeparture } from "mobility-toolbox-js/types";
7
+
8
+ function useRealtimeDepartures(stationId: number | string) {
9
+ const { realtimeLayer } = useMapContext();
10
+ const [departures, setDepartures] = useState<RealtimeDeparture[]>();
11
+
12
+ const api = useMemo(() => {
13
+ return realtimeLayer?.api;
14
+ }, [realtimeLayer?.api]);
15
+
16
+ useEffect(() => {
17
+ if (!stationId || !api) {
18
+ return;
19
+ }
20
+
21
+ if (!api.wsApi.open) {
22
+ api.open();
23
+ }
24
+
25
+ const onMessage = debounceDeparturesMessages(
26
+ (newDepartures: RealtimeDeparture[]) => {
27
+ setDepartures(newDepartures);
28
+ return null;
29
+ },
30
+ false,
31
+ 180,
32
+ );
33
+ // @ts-expect-error bad type definition
34
+ api.subscribeTimetable(stationId, onMessage);
35
+
36
+ return () => {
37
+ setDepartures(null);
38
+ api.unsubscribeTimetable(stationId as number);
39
+ };
40
+ }, [stationId, api]);
41
+
42
+ return departures;
43
+ }
44
+
45
+ export default useRealtimeDepartures;
@@ -0,0 +1,42 @@
1
+ import { useEffect, useState } from "preact/hooks";
2
+
3
+ import useMapContext from "./useMapContext";
4
+
5
+ import type { RealtimeTrajectory } from "mobility-toolbox-js/types";
6
+
7
+ function useRealtimeRenderedTrajectories(trainId: number | string) {
8
+ const { realtimeLayer } = useMapContext();
9
+ const [trajectory, setTrajectory] = useState<RealtimeTrajectory>();
10
+ const [trainIdFound, setTrainIdFound] = useState<string>();
11
+
12
+ // We try to find the trainId every second until we have it
13
+ // TODO: find a efficient way to find the trajectory without polling
14
+ useEffect(() => {
15
+ if (trainIdFound || !trainId || !realtimeLayer) {
16
+ return;
17
+ }
18
+ const timeout = setInterval(() => {
19
+ let traj = realtimeLayer?.trajectories?.[trainId];
20
+
21
+ if (!traj) {
22
+ traj = Object.values(realtimeLayer?.trajectories)?.find((item) => {
23
+ return item.properties.route_identifier === trainId;
24
+ });
25
+ }
26
+
27
+ if (traj) {
28
+ setTrajectory(traj);
29
+ setTrainIdFound(traj.properties.train_id);
30
+ }
31
+ }, 1000);
32
+
33
+ return () => {
34
+ clearInterval(timeout);
35
+ setTrainIdFound(undefined);
36
+ };
37
+ }, [trainId, realtimeLayer, trainIdFound]);
38
+
39
+ return trajectory;
40
+ }
41
+
42
+ export default useRealtimeRenderedTrajectories;
@@ -0,0 +1,39 @@
1
+ import { useEffect, useMemo, useState } from "preact/hooks";
2
+
3
+ import useMapContext from "./useMapContext";
4
+
5
+ import type { RealtimeStation } from "mobility-toolbox-js/types";
6
+
7
+ function useRealtimeStation(stationId: number | string) {
8
+ const { realtimeLayer } = useMapContext();
9
+ const [station, setStation] = useState<RealtimeStation>();
10
+ const api = useMemo(() => {
11
+ return realtimeLayer?.api;
12
+ }, [realtimeLayer?.api]);
13
+
14
+ useEffect(() => {
15
+ if (!stationId || !api) {
16
+ return;
17
+ }
18
+
19
+ if (!api.wsApi.open) {
20
+ api.wsApi.connect(api.url);
21
+ }
22
+
23
+ api.subscribe(`station ${stationId}`, ({ content }) => {
24
+ if (content) {
25
+ setStation(content as RealtimeStation);
26
+ }
27
+ });
28
+
29
+ return () => {
30
+ setStation(undefined);
31
+ if (stationId) {
32
+ api?.unsubscribe(`station ${stationId}`);
33
+ }
34
+ };
35
+ }, [stationId, api]);
36
+ return station;
37
+ }
38
+
39
+ export default useRealtimeStation;
@@ -0,0 +1,43 @@
1
+ import { useEffect, useMemo, useState } from "preact/hooks";
2
+
3
+ import useMapContext from "./useMapContext";
4
+
5
+ import type { RealtimeStopSequence } from "mobility-toolbox-js/types";
6
+
7
+ function useRealtimeStopSequences(trainId: string) {
8
+ const { realtimeLayer } = useMapContext();
9
+ const [stopSequences, setStopSequences] = useState<RealtimeStopSequence[]>();
10
+
11
+ const api = useMemo(() => {
12
+ return realtimeLayer?.api;
13
+ }, [realtimeLayer?.api]);
14
+
15
+ useEffect(() => {
16
+ let trainIdSubscribed = null;
17
+ if (!trainId || !api) {
18
+ return;
19
+ }
20
+
21
+ if (!api.wsApi.open) {
22
+ api.open();
23
+ }
24
+
25
+ trainIdSubscribed = trainId;
26
+
27
+ const onMessage = ({ content }) => {
28
+ if (content) {
29
+ setStopSequences([...content]);
30
+ }
31
+ };
32
+ api.subscribeStopSequence(trainId, onMessage);
33
+
34
+ return () => {
35
+ setStopSequences([]);
36
+ api.unsubscribeStopSequence(trainIdSubscribed);
37
+ };
38
+ }, [trainId, api]);
39
+
40
+ return stopSequences;
41
+ }
42
+
43
+ export default useRealtimeStopSequences;