@geops/rvf-mobility-web-component 0.1.8

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 (212) hide show
  1. package/.fixpackrc +21 -0
  2. package/.husky/commit-msg +1 -0
  3. package/.husky/post-checkout +1 -0
  4. package/.husky/post-merge +1 -0
  5. package/.husky/post-rebase +1 -0
  6. package/.husky/pre-commit +1 -0
  7. package/.lintstagedrc.js +12 -0
  8. package/.nvmrc +1 -0
  9. package/.prettierrc.js +1 -0
  10. package/CHANGELOG.md +183 -0
  11. package/Logo.svg +10 -0
  12. package/README.md +179 -0
  13. package/__mocks__/dataurl.js +11 -0
  14. package/__mocks__/mapbox-gl.js +19 -0
  15. package/commitlint.config.cjs +1 -0
  16. package/doc/.eslintrc.json +3 -0
  17. package/doc/.fixpackrc +21 -0
  18. package/doc/.prettierrc +1 -0
  19. package/doc/README.md +14 -0
  20. package/doc/declarations.d.ts +6 -0
  21. package/doc/next.config.mjs +4 -0
  22. package/doc/package.json +43 -0
  23. package/doc/postcss.config.mjs +8 -0
  24. package/doc/public/README.md +1 -0
  25. package/doc/src/app/components/GeopsAPIKeyLink.tsx +6 -0
  26. package/doc/src/app/components/GeopsAPIsLink.tsx +6 -0
  27. package/doc/src/app/components/GeopsMapsAPILink.tsx +8 -0
  28. package/doc/src/app/components/GeopsMobility.tsx +9 -0
  29. package/doc/src/app/components/GeopsMobilityDoc.tsx +231 -0
  30. package/doc/src/app/components/GeopsMobilitySearch.tsx +10 -0
  31. package/doc/src/app/components/GeopsMobilitySearchDoc.tsx +129 -0
  32. package/doc/src/app/components/GeopsRealtimeAPILink.tsx +10 -0
  33. package/doc/src/app/components/GeopsStopsAPILink.tsx +8 -0
  34. package/doc/src/app/components/Link.tsx +9 -0
  35. package/doc/src/app/components/WebComponentDoc.tsx +296 -0
  36. package/doc/src/app/favicon.ico +0 -0
  37. package/doc/src/app/geops-mobility/page.tsx +6 -0
  38. package/doc/src/app/geops-mobility-search/page.tsx +7 -0
  39. package/doc/src/app/globals.css +38 -0
  40. package/doc/src/app/hooks/useAttrFromUrlParams.ts +21 -0
  41. package/doc/src/app/hooks/useIsFullScreen.ts +14 -0
  42. package/doc/src/app/hooks/usePublicKey.ts +21 -0
  43. package/doc/src/app/layout.tsx +51 -0
  44. package/doc/src/app/page.tsx +86 -0
  45. package/doc/src/geops-ui.ts +3 -0
  46. package/doc/tailwind.config.ts +20 -0
  47. package/doc/tsconfig.json +40 -0
  48. package/eslint.config.mjs +40 -0
  49. package/favicon.ico +0 -0
  50. package/global.d.ts +4 -0
  51. package/iframe.html +34 -0
  52. package/index.html +276 -0
  53. package/index.js +2162 -0
  54. package/input.css +34 -0
  55. package/jest-setup.js +4 -0
  56. package/jest.config.js +17 -0
  57. package/package.json +80 -0
  58. package/scripts/build.mjs +16 -0
  59. package/scripts/dev.mjs +26 -0
  60. package/search.html +144 -0
  61. package/src/BaseLayer/BaseLayer.tsx +36 -0
  62. package/src/BaseLayer/index.tsx +1 -0
  63. package/src/Copyright/Copyright.tsx +54 -0
  64. package/src/Copyright/index.css +3 -0
  65. package/src/Copyright/index.tsx +1 -0
  66. package/src/DebugDeparture/DebugDeparture.tsx +116 -0
  67. package/src/DebugDeparture/index.tsx +1 -0
  68. package/src/DebugStop/DebugStop.tsx +47 -0
  69. package/src/DebugStop/index.tsx +1 -0
  70. package/src/Departure/Departure.tsx +55 -0
  71. package/src/Departure/index.tsx +1 -0
  72. package/src/GeolocationButton/GeolocationButton.tsx +81 -0
  73. package/src/GeolocationButton/index.tsx +1 -0
  74. package/src/Map/Map.tsx +89 -0
  75. package/src/Map/index.tsx +1 -0
  76. package/src/MobilityMap/MobilityMap.tsx +259 -0
  77. package/src/MobilityMap/index.css +13 -0
  78. package/src/MobilityMap/index.tsx +1 -0
  79. package/src/NotificationLayer/NotificationLayer.tsx +156 -0
  80. package/src/NotificationLayer/index.tsx +1 -0
  81. package/src/NotificationLayer/notificationUtils.ts +191 -0
  82. package/src/Overlay/Overlay.tsx +57 -0
  83. package/src/Overlay/index.tsx +1 -0
  84. package/src/RealtimeLayer/RealtimeLayer.tsx +230 -0
  85. package/src/RealtimeLayer/index.tsx +1 -0
  86. package/src/RouteDestination/RouteDestination.test.tsx +13 -0
  87. package/src/RouteDestination/RouteDestination.tsx +15 -0
  88. package/src/RouteDestination/index.tsx +1 -0
  89. package/src/RouteIcon/RouteIcon.tsx +66 -0
  90. package/src/RouteIcon/index.tsx +1 -0
  91. package/src/RouteIdentifier/RouteIdentifer.tsx +35 -0
  92. package/src/RouteIdentifier/index.tsx +1 -0
  93. package/src/RouteInfos/RouteInfos.tsx +22 -0
  94. package/src/RouteInfos/index.tsx +1 -0
  95. package/src/RouteSchedule/RouteSchedule.tsx +69 -0
  96. package/src/RouteSchedule/firstStation.png +0 -0
  97. package/src/RouteSchedule/index.tsx +1 -0
  98. package/src/RouteSchedule/lastStation.png +0 -0
  99. package/src/RouteSchedule/line.png +0 -0
  100. package/src/RouteSchedule/station.png +0 -0
  101. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +44 -0
  102. package/src/RouteScheduleFooter/index.tsx +1 -0
  103. package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +58 -0
  104. package/src/RouteScheduleHeader/index.tsx +1 -0
  105. package/src/RouteStop/RouteStop.tsx +121 -0
  106. package/src/RouteStop/index.tsx +1 -0
  107. package/src/RouteStopDelay/RouteStopDelay.tsx +36 -0
  108. package/src/RouteStopDelay/index.tsx +1 -0
  109. package/src/RouteStopName/RouteStopName.tsx +24 -0
  110. package/src/RouteStopName/index.tsx +1 -0
  111. package/src/RouteStopPlatform/RouteStopPlatform.tsx +29 -0
  112. package/src/RouteStopPlatform/index.tsx +1 -0
  113. package/src/RouteStopProgress/RouteStopProgress.tsx +101 -0
  114. package/src/RouteStopProgress/index.tsx +1 -0
  115. package/src/RouteStopServices/RouteStopServices.tsx +26 -0
  116. package/src/RouteStopServices/index.tsx +1 -0
  117. package/src/RouteStopStation/RouteStopStation.tsx +32 -0
  118. package/src/RouteStopStation/index.tsx +1 -0
  119. package/src/RouteStopTime/RouteStopTime.tsx +34 -0
  120. package/src/RouteStopTime/index.tsx +1 -0
  121. package/src/RvfMobilityMap/RvfMobilityMap.tsx +245 -0
  122. package/src/RvfMobilityMap/index.css +13 -0
  123. package/src/RvfMobilityMap/index.tsx +1 -0
  124. package/src/ScaleLine/ScaleLine.tsx +51 -0
  125. package/src/ScaleLine/index.css +6 -0
  126. package/src/ScaleLine/index.tsx +1 -0
  127. package/src/ScrollableHandler/ScrollableHandler.tsx +65 -0
  128. package/src/ScrollableHandler/index.tsx +1 -0
  129. package/src/Search/Search.tsx +18 -0
  130. package/src/Search/index.tsx +1 -0
  131. package/src/SingleClickListener/SingleClickListener.tsx +103 -0
  132. package/src/SingleClickListener/index.tsx +1 -0
  133. package/src/Station/Station.tsx +68 -0
  134. package/src/Station/index.tsx +1 -0
  135. package/src/StationHeader/StationHeader.tsx +32 -0
  136. package/src/StationHeader/index.tsx +1 -0
  137. package/src/StationName/StationName.tsx +21 -0
  138. package/src/StationName/index.tsx +1 -0
  139. package/src/StationServices/StationServices.tsx +80 -0
  140. package/src/StationServices/index.tsx +1 -0
  141. package/src/StationsLayer/StationsLayer.tsx +41 -0
  142. package/src/StationsLayer/index.tsx +1 -0
  143. package/src/StopsSearch/StopsSearch.tsx +254 -0
  144. package/src/StopsSearch/index.tsx +1 -0
  145. package/src/icons/Airport/Airport.tsx +17 -0
  146. package/src/icons/Airport/airport-14-svgrepo-com.svg +41 -0
  147. package/src/icons/Airport/index.tsx +1 -0
  148. package/src/icons/BarAndRestaurants/BarAndRestaurants.tsx +17 -0
  149. package/src/icons/BarAndRestaurants/food-restaurant-svgrepo-com.svg +12 -0
  150. package/src/icons/BarAndRestaurants/index.tsx +1 -0
  151. package/src/icons/Bathroom/Bathroom.tsx +59 -0
  152. package/src/icons/Bathroom/bathroom-restroom-svgrepo-com.svg +38 -0
  153. package/src/icons/Bathroom/index.tsx +1 -0
  154. package/src/icons/BikeStorage/BikeStorage.tsx +17 -0
  155. package/src/icons/BikeStorage/index.tsx +1 -0
  156. package/src/icons/BikeStorage/parking-bicycle-14-svgrepo-com.svg +41 -0
  157. package/src/icons/Elevator/Elevator.tsx +16 -0
  158. package/src/icons/Elevator/elevator-svgrepo-com.svg +2 -0
  159. package/src/icons/Elevator/index.tsx +1 -0
  160. package/src/icons/Police/Police.tsx +20 -0
  161. package/src/icons/Police/index.tsx +1 -0
  162. package/src/icons/Police/polizia.png +0 -0
  163. package/src/icons/README.md +52 -0
  164. package/src/icons/WaitingAreas/WaitingAreas.tsx +16 -0
  165. package/src/icons/WaitingAreas/highway-rest-area-svgrepo-com.svg +5 -0
  166. package/src/icons/WaitingAreas/index.tsx +1 -0
  167. package/src/icons/WaitingAreas/wheelchair-svgrepo-com.svg +2 -0
  168. package/src/icons/WheelChair/WheelChair.tsx +16 -0
  169. package/src/icons/WheelChair/disabili.png +0 -0
  170. package/src/icons/WheelChair/index.tsx +1 -0
  171. package/src/icons/WheelChair/wheelchair-svgrepo-com.svg +2 -0
  172. package/src/index.tsx +50 -0
  173. package/src/utils/MobilityEvent.ts +21 -0
  174. package/src/utils/addSourceAndLayers.ts +62 -0
  175. package/src/utils/centerOnStation.ts +17 -0
  176. package/src/utils/centerOnVehicle.ts +50 -0
  177. package/src/utils/getBgColor.ts +3 -0
  178. package/src/utils/getDelayColor.test.ts +20 -0
  179. package/src/utils/getDelayColor.ts +23 -0
  180. package/src/utils/getDelayColorForVehicle.test.ts +28 -0
  181. package/src/utils/getDelayColorForVehicle.ts +25 -0
  182. package/src/utils/getDelayFontForVehicle.test.ts +7 -0
  183. package/src/utils/getDelayFontForVehicle.tsx +8 -0
  184. package/src/utils/getDelayString.test.ts +22 -0
  185. package/src/utils/getDelayString.ts +28 -0
  186. package/src/utils/getDelayTextForVehicle.test.ts +28 -0
  187. package/src/utils/getDelayTextForVehicle.ts +21 -0
  188. package/src/utils/getFullTrajectoryAndFit.ts +40 -0
  189. package/src/utils/getHoursAndMinutes.test.ts +14 -0
  190. package/src/utils/getHoursAndMinutes.ts +22 -0
  191. package/src/utils/getMainColorForVehicle.test.ts +27 -0
  192. package/src/utils/getMainColorForVehicle.ts +46 -0
  193. package/src/utils/getStopStatus.test.ts +104 -0
  194. package/src/utils/getStopStatus.ts +171 -0
  195. package/src/utils/getTextFontForVehicle.test.ts +7 -0
  196. package/src/utils/getTextFontForVehicle.tsx +9 -0
  197. package/src/utils/getTextForVehicle.test.ts +17 -0
  198. package/src/utils/getTextForVehicle.ts +19 -0
  199. package/src/utils/hooks/useDebug.tsx +11 -0
  200. package/src/utils/hooks/useDeparture.tsx +23 -0
  201. package/src/utils/hooks/useI18n.tsx +20 -0
  202. package/src/utils/hooks/useMapContext.tsx +74 -0
  203. package/src/utils/hooks/useParams.ts +5 -0
  204. package/src/utils/hooks/useRouteStop.tsx +33 -0
  205. package/src/utils/hooks/useStation.tsx +21 -0
  206. package/src/utils/hooks/useUpdatePermalink.tsx +33 -0
  207. package/src/utils/hooks/useZoom.tsx +32 -0
  208. package/src/utils/i18n.ts +16 -0
  209. package/src/utils/translations.ts +31 -0
  210. package/tailwind.config.mjs +56 -0
  211. package/testNotification.json +50653 -0
  212. package/tsconfig.json +12 -0
@@ -0,0 +1,81 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { Geolocation } from "ol";
4
+ import { unByKey } from "ol/Observable";
5
+ import { fromLonLat } from "ol/proj";
6
+ import { useEffect, useMemo } from "preact/hooks";
7
+
8
+ import useMapContext from "../utils/hooks/useMapContext";
9
+
10
+ export type GeolocationButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
11
+ PreactDOMAttributes;
12
+
13
+ const TRACKING_ZOOM = 16;
14
+
15
+ function GeolocationButton({ ...props }: GeolocationButtonProps) {
16
+ const mapContext = useMapContext();
17
+ const { isTracking, map, setIsTracking } = mapContext;
18
+
19
+ const geolocation = useMemo(() => {
20
+ return new Geolocation();
21
+ }, []);
22
+
23
+ useEffect(() => {
24
+ let keys = [];
25
+ if (!map || !geolocation) {
26
+ return;
27
+ }
28
+ keys = [
29
+ // First time we zoom and center on the position
30
+ geolocation.once("change:position", (evt) => {
31
+ const position = evt.target.getPosition();
32
+ if (evt.target.getPosition()) {
33
+ map.getView().setZoom(TRACKING_ZOOM);
34
+ map.getView().setCenter(fromLonLat(position, "EPSG:3857"));
35
+ }
36
+ }),
37
+ // then we only center the map.
38
+ geolocation.on("change:position", (evt) => {
39
+ const position = evt.target.getPosition();
40
+ if (evt.target.getPosition()) {
41
+ map.getView().setCenter(fromLonLat(position, "EPSG:3857"));
42
+ }
43
+ }),
44
+ ];
45
+
46
+ return () => {
47
+ unByKey(keys);
48
+ };
49
+ }, [map, geolocation]);
50
+
51
+ useEffect(() => {
52
+ geolocation.setTracking(isTracking);
53
+ }, [geolocation, isTracking]);
54
+
55
+ return (
56
+ <button
57
+ className="rounded-full bg-white p-1 shadow-lg"
58
+ onClick={() => {
59
+ setIsTracking(!isTracking);
60
+ }}
61
+ type="button"
62
+ {...props}
63
+ >
64
+ <svg
65
+ className={isTracking ? "animate-pulse" : ""}
66
+ fill="currentColor"
67
+ focusable="false"
68
+ height="1.5em"
69
+ stroke="currentColor"
70
+ strokeWidth="0"
71
+ viewBox="0 0 512 512"
72
+ width="1.5em"
73
+ xmlns="http://www.w3.org/2000/svg"
74
+ >
75
+ <path d="M256 56c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m0-48C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 168c-44.183 0-80 35.817-80 80s35.817 80 80 80 80-35.817 80-80-35.817-80-80-80z" />
76
+ </svg>
77
+ </button>
78
+ );
79
+ }
80
+
81
+ export default GeolocationButton;
@@ -0,0 +1 @@
1
+ export { default } from "./GeolocationButton";
@@ -0,0 +1,89 @@
1
+ import { Map as OlMap } from "ol";
2
+ // @ts-expect-error bad type definition
3
+ import olStyle from "ol/ol.css";
4
+ import { JSX, PreactDOMAttributes } from "preact";
5
+ import { memo } from "preact/compat";
6
+ import { useEffect, useRef } from "preact/hooks";
7
+
8
+ import useMapContext from "../utils/hooks/useMapContext";
9
+
10
+ export type RealtimeMapProps = JSX.HTMLAttributes<HTMLDivElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function Map({ children, ...props }: RealtimeMapProps) {
14
+ const mapRef = useRef();
15
+ const {
16
+ center = "831634,5933959",
17
+ map,
18
+ maxzoom,
19
+ minzoom,
20
+ setMap,
21
+ zoom = "13",
22
+ } = useMapContext();
23
+
24
+ useEffect(() => {
25
+ let newMap: OlMap;
26
+ if (mapRef.current) {
27
+ newMap = new OlMap({ controls: [], target: mapRef.current });
28
+ setMap(newMap);
29
+ }
30
+
31
+ return () => {
32
+ newMap?.setTarget();
33
+ setMap();
34
+ };
35
+ }, [setMap]);
36
+
37
+ useEffect(() => {
38
+ if (!map) {
39
+ return;
40
+ }
41
+ const [x, y] = center.split(",").map((c) => {
42
+ return parseFloat(c);
43
+ });
44
+ if (x && y) {
45
+ map.getView().setCenter([x, y]);
46
+ }
47
+ }, [map, center]);
48
+
49
+ useEffect(() => {
50
+ if (!map) {
51
+ return;
52
+ }
53
+ const number = parseFloat(zoom);
54
+ if (number) {
55
+ map.getView().setZoom(number);
56
+ }
57
+ }, [map, zoom]);
58
+
59
+ useEffect(() => {
60
+ if (!map) {
61
+ return;
62
+ }
63
+ const number = parseFloat(maxzoom);
64
+ if (number) {
65
+ map.getView().setMaxZoom(number);
66
+ }
67
+ }, [map, maxzoom]);
68
+
69
+ useEffect(() => {
70
+ if (!map) {
71
+ return;
72
+ }
73
+ const number = parseFloat(minzoom);
74
+ if (number) {
75
+ map.getView().setMinZoom(number);
76
+ }
77
+ }, [map, minzoom]);
78
+
79
+ return (
80
+ <>
81
+ <style>{olStyle}</style>
82
+ <div ref={mapRef} {...props}>
83
+ {children}
84
+ </div>
85
+ </>
86
+ );
87
+ }
88
+
89
+ export default memo(Map);
@@ -0,0 +1 @@
1
+ export { default } from "./Map";
@@ -0,0 +1,259 @@
1
+ import {
2
+ MaplibreLayer,
3
+ MaplibreStyleLayer,
4
+ RealtimeLayer as MbtRealtimeLayer,
5
+ } from "mobility-toolbox-js/ol";
6
+ import {
7
+ RealtimeStation,
8
+ RealtimeStationId,
9
+ RealtimeStopSequence,
10
+ RealtimeTrainId,
11
+ } from "mobility-toolbox-js/types";
12
+ import { Map as OlMap } from "ol";
13
+ import { memo } from "preact/compat";
14
+ import { useEffect, useMemo, useState } from "preact/hooks";
15
+
16
+ import BaseLayer from "../BaseLayer";
17
+ import Copyright from "../Copyright";
18
+ import GeolocationButton from "../GeolocationButton";
19
+ import Map from "../Map";
20
+ import NotificationLayer from "../NotificationLayer";
21
+ import Overlay from "../Overlay";
22
+ import RealtimeLayer from "../RealtimeLayer";
23
+ import RouteSchedule from "../RouteSchedule";
24
+ import ScaleLine from "../ScaleLine";
25
+ import Search from "../Search";
26
+ import SingleClickListener from "../SingleClickListener/SingleClickListener";
27
+ import Station from "../Station";
28
+ import StationsLayer from "../StationsLayer";
29
+ // @ts-expect-error bad type definition
30
+ import tailwind from "../style.css";
31
+ import { I18nContext } from "../utils/hooks/useI18n";
32
+ import { MapContext } from "../utils/hooks/useMapContext";
33
+ import useUpdatePermalink from "../utils/hooks/useUpdatePermalink";
34
+ import i18n from "../utils/i18n";
35
+ import MobilityEvent from "../utils/MobilityEvent";
36
+ // @ts-expect-error bad type definition
37
+ import style from "./index.css";
38
+ // Notificationurl example: https://mobility-web-component-tmp.vercel.app/geops-mobility?notificationurl=https%3A%2F%2Fmoco.geops.io%2Fapi%2Fv1%2Fexport%2Fnotification%2F%3Fsso_config%3Dsob&geolocation=false&realtime=false&search=false&notificationat=2024-01-25T22%3A59%3A00Z
39
+
40
+ export interface MobilityMapProps {
41
+ apikey?: string;
42
+ baselayer?: string;
43
+ center?: string;
44
+ geolocation?: string;
45
+ mapsurl?: string;
46
+ maxzoom?: string;
47
+ minzoom?: string;
48
+ mots?: string;
49
+ notification?: string;
50
+ notificationat?: string; // 2024-01-25T22:59:00Z
51
+ notificationbeforelayerid?: string;
52
+ notificationurl?: string; // https://moco.geops.io/api/v1/export/notification/?sso_config=sob
53
+ permalink?: string;
54
+ realtime?: string;
55
+ realtimeurl?: string;
56
+ search?: string;
57
+ stopsurl?: string;
58
+ tenant?: string;
59
+ zoom?: string;
60
+ }
61
+
62
+ function MobilityMap({
63
+ apikey = null,
64
+ baselayer = "travic_v2",
65
+ center = "831634,5933959",
66
+ geolocation = "true",
67
+ mapsurl = "https://maps.geops.io",
68
+ maxzoom = null,
69
+ minzoom = null,
70
+ mots = null,
71
+ notification = "true",
72
+ notificationat = null,
73
+ notificationbeforelayerid = null,
74
+ notificationurl = null,
75
+ permalink = "false",
76
+ realtime = "true",
77
+ realtimeurl = "wss://api.geops.io/tracker-ws/v1/ws",
78
+ search = "true",
79
+ stopsurl = "https://api.geops.io/stops/v1/",
80
+ tenant = null,
81
+ zoom = "13",
82
+ }: MobilityMapProps) {
83
+ const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
84
+ const [isFollowing, setIsFollowing] = useState(false);
85
+ const [isTracking, setIsTracking] = useState(false);
86
+ const [stopSequence, setStopSequence] = useState<RealtimeStopSequence>();
87
+ const [stationsLayer, setStationsLayer] = useState<MaplibreStyleLayer>();
88
+ const [station, setStation] = useState<RealtimeStation>();
89
+ const [realtimeLayer, setRealtimeLayer] = useState<MbtRealtimeLayer>();
90
+ const [map, setMap] = useState<OlMap>();
91
+ const [stationId, setStationId] = useState<RealtimeStationId>();
92
+ const [trainId, setTrainId] = useState<RealtimeTrainId>();
93
+
94
+ // TODO: this should be removed. The parent application should be responsible to do this
95
+ // or we should find something that fit more usecases
96
+ const { x, y, z } = useUpdatePermalink(map, permalink === "true");
97
+
98
+ const mapContextValue = useMemo(() => {
99
+ return {
100
+ // MobilityMapProps && MapContextProps
101
+ apikey,
102
+ baselayer,
103
+ baseLayer,
104
+ center,
105
+ geolocation,
106
+ isFollowing,
107
+ isTracking,
108
+ map,
109
+ mapsurl,
110
+ maxzoom,
111
+ minzoom,
112
+ mots,
113
+ notification,
114
+ notificationat,
115
+ notificationbeforelayerid,
116
+ notificationurl,
117
+ permalink,
118
+ realtimeLayer,
119
+ realtimeurl,
120
+ setBaseLayer,
121
+ setIsFollowing,
122
+ setIsTracking,
123
+ setMap,
124
+ setRealtimeLayer,
125
+ setStation,
126
+ setStationId,
127
+ setStationsLayer,
128
+ setStopSequence,
129
+ setTrainId,
130
+ station,
131
+ stationId,
132
+ stationsLayer,
133
+ stopSequence,
134
+ stopsurl,
135
+ tenant,
136
+ trainId,
137
+ zoom,
138
+ };
139
+ }, [
140
+ apikey,
141
+ baselayer,
142
+ baseLayer,
143
+ center,
144
+ geolocation,
145
+ isFollowing,
146
+ isTracking,
147
+ map,
148
+ mapsurl,
149
+ maxzoom,
150
+ minzoom,
151
+ mots,
152
+ notification,
153
+ notificationat,
154
+ notificationbeforelayerid,
155
+ notificationurl,
156
+ permalink,
157
+ realtimeLayer,
158
+ realtimeurl,
159
+ station,
160
+ stationId,
161
+ stationsLayer,
162
+ stopSequence,
163
+ stopsurl,
164
+ tenant,
165
+ trainId,
166
+ zoom,
167
+ ]);
168
+
169
+ useEffect(() => {
170
+ dispatchEvent(
171
+ new MobilityEvent<MobilityMapProps>("mwc:attribute", {
172
+ baselayer,
173
+ center: x && y ? `${x},${y}` : center,
174
+ geolocation,
175
+ mapsurl,
176
+ maxzoom,
177
+ minzoom,
178
+ mots,
179
+ notification,
180
+ notificationat,
181
+ notificationbeforelayerid,
182
+ notificationurl,
183
+ realtime,
184
+ realtimeurl,
185
+ search,
186
+ tenant,
187
+ zoom: z || zoom,
188
+ }),
189
+ );
190
+ }, [
191
+ baselayer,
192
+ center,
193
+ geolocation,
194
+ mapsurl,
195
+ maxzoom,
196
+ minzoom,
197
+ mots,
198
+ notification,
199
+ notificationat,
200
+ notificationurl,
201
+ notificationbeforelayerid,
202
+ realtime,
203
+ realtimeurl,
204
+ search,
205
+ tenant,
206
+ zoom,
207
+ x,
208
+ y,
209
+ z,
210
+ ]);
211
+
212
+ return (
213
+ <I18nContext.Provider value={i18n}>
214
+ <style>{tailwind}</style>
215
+ <style>{style}</style>
216
+ <MapContext.Provider value={mapContextValue}>
217
+ <div className="relative size-full border font-sans @container/main">
218
+ <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
219
+ <Map className="relative flex-1 overflow-visible ">
220
+ <BaseLayer />
221
+ <SingleClickListener />
222
+ {realtime === "true" && <RealtimeLayer />}
223
+ {tenant && <StationsLayer />}
224
+ {notification === "true" && <NotificationLayer />}
225
+ <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
226
+ <ScaleLine className="bg-slate-50/70" />
227
+ <Copyright className="bg-slate-50/70" />
228
+ </div>
229
+ <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
230
+ {geolocation === "true" && <GeolocationButton />}
231
+ </div>
232
+ {search === "true" && (
233
+ <div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
234
+ <Search />
235
+ </div>
236
+ )}
237
+ </Map>
238
+
239
+ <Overlay
240
+ className={"z-50"}
241
+ ScrollableHandlerProps={{
242
+ style: { width: "calc(100% - 60px)" },
243
+ }}
244
+ >
245
+ {realtime === "true" && trainId && (
246
+ <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
247
+ )}
248
+ {tenant && stationId && (
249
+ <Station className="relative overflow-y-auto overflow-x-hidden" />
250
+ )}
251
+ </Overlay>
252
+ </div>
253
+ </div>
254
+ </MapContext.Provider>
255
+ </I18nContext.Provider>
256
+ );
257
+ }
258
+
259
+ export default memo(MobilityMap);
@@ -0,0 +1,13 @@
1
+ ::-webkit-scrollbar {
2
+ width: 3px;
3
+ height: 3px;
4
+ }
5
+
6
+ ::-webkit-scrollbar-thumb {
7
+ background: lightgray;
8
+ z-index: 5;
9
+ }
10
+
11
+ ::-webkit-scrollbar-track {
12
+ background: transparent;
13
+ }
@@ -0,0 +1 @@
1
+ export { default } from "../RvfMobilityMap";
@@ -0,0 +1,156 @@
1
+ import { useEffect, useMemo, useState } from "preact/hooks";
2
+
3
+ import useMapContext from "../utils/hooks/useMapContext";
4
+ import useZoom from "../utils/hooks/useZoom";
5
+ import {
6
+ addNotificationsLayers,
7
+ getNotificationsWithStatus,
8
+ parsePreviewNotification,
9
+ } from "./notificationUtils";
10
+
11
+ type Graphs = Record<string, string>;
12
+
13
+ interface Metadata {
14
+ graphs?: Graphs;
15
+ }
16
+
17
+ const useNotifications = () => {
18
+ const {
19
+ baselayer,
20
+ baseLayer,
21
+ notificationat,
22
+ notificationbeforelayerid,
23
+ notificationurl,
24
+ } = useMapContext();
25
+ const zoom = useZoom();
26
+ const [notifications, setNotifications] = useState([]);
27
+ const [previewNotification, setPreviewNotification] = useState(null);
28
+ const [shouldAddPreviewNotifications, setShouldAddPreviewNotifications] =
29
+ useState<boolean>(true);
30
+
31
+ const [style, setStyle] = useState<string>();
32
+ const [styleMetadata, setStyleMetadata] = useState<Metadata>();
33
+
34
+ useEffect(() => {
35
+ if (!baseLayer) {
36
+ return;
37
+ }
38
+ setStyle(baselayer);
39
+ if (!baseLayer.loaded) {
40
+ // @ts-expect-error bad type definition
41
+ baseLayer.once("load", () => {
42
+ return setStyleMetadata(baseLayer.mbMap?.getStyle()?.metadata);
43
+ });
44
+ } else {
45
+ setStyleMetadata(baseLayer.mbMap?.getStyle()?.metadata);
46
+ }
47
+ }, [baseLayer, baselayer]);
48
+
49
+ const now = useMemo(() => {
50
+ return notificationat ? new Date(notificationat) : new Date();
51
+ }, [notificationat]);
52
+
53
+ const graphMapping = useMemo(() => {
54
+ return styleMetadata?.graphs || { 1: "osm" };
55
+ }, [styleMetadata]);
56
+
57
+ const graphsString = useMemo(() => {
58
+ return [
59
+ ...new Set(
60
+ Object.keys(graphMapping || []).map((key) => {
61
+ return graphMapping[key];
62
+ }),
63
+ ),
64
+ ].join(",");
65
+ }, [graphMapping]);
66
+
67
+ useEffect(() => {
68
+ // Listen for incoming messages through the MOCO iframe
69
+ window.addEventListener("message", (event) => {
70
+ if (event.data.notification) {
71
+ setPreviewNotification(event.data.notification);
72
+ setShouldAddPreviewNotifications(true);
73
+ }
74
+ });
75
+ }, []);
76
+
77
+ useEffect(() => {
78
+ let abortCtrl: AbortController;
79
+
80
+ // Fetch the main MOCO notifications
81
+ const fetchNotifications = async () => {
82
+ const suffix = /\?/.test(notificationurl) ? "&" : "?";
83
+ const url = `${notificationurl}${suffix}graph=${graphsString}`;
84
+
85
+ abortCtrl?.abort();
86
+ abortCtrl = new AbortController();
87
+ const response = await fetch(url, { signal: abortCtrl.signal });
88
+ const data = await response.json();
89
+ setNotifications(getNotificationsWithStatus(data, now));
90
+ setShouldAddPreviewNotifications(true);
91
+ };
92
+
93
+ if (notificationurl && graphsString) {
94
+ fetchNotifications();
95
+ }
96
+
97
+ return () => {
98
+ abortCtrl?.abort();
99
+ };
100
+ }, [notificationurl, graphsString, now]);
101
+
102
+ useEffect(() => {
103
+ // Merge notifications with the previewNotification
104
+ const newNotifications = [...notifications];
105
+ if (shouldAddPreviewNotifications && previewNotification?.[style]) {
106
+ const parsedPreviewNotification = parsePreviewNotification(
107
+ previewNotification?.[style],
108
+ );
109
+ const index = newNotifications.findIndex((n) => {
110
+ return n.properties.id === previewNotification[style].id;
111
+ });
112
+
113
+ if (index > -1) {
114
+ newNotifications[index] = parsedPreviewNotification;
115
+ } else {
116
+ newNotifications.push(parsedPreviewNotification);
117
+ }
118
+
119
+ setNotifications(getNotificationsWithStatus(newNotifications, now));
120
+ setShouldAddPreviewNotifications(false);
121
+ }
122
+ }, [
123
+ previewNotification,
124
+ notifications,
125
+ shouldAddPreviewNotifications,
126
+ style,
127
+ now,
128
+ ]);
129
+
130
+ useEffect(() => {
131
+ // Add the notifications to the map
132
+ if (styleMetadata && notifications?.length) {
133
+ addNotificationsLayers(
134
+ baseLayer,
135
+ notifications,
136
+ notificationbeforelayerid,
137
+ zoom,
138
+ graphMapping,
139
+ );
140
+ }
141
+ }, [
142
+ notifications,
143
+ notificationbeforelayerid,
144
+ styleMetadata,
145
+ zoom,
146
+ graphMapping,
147
+ baseLayer,
148
+ ]);
149
+
150
+ return notifications;
151
+ };
152
+
153
+ export default function NotificationLayer() {
154
+ useNotifications();
155
+ return null;
156
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./NotificationLayer";