@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,69 @@
1
+ import type { RealtimeStop } from "mobility-toolbox-js/types";
2
+ import type { JSX, PreactDOMAttributes } from "preact";
3
+
4
+ import { memo } from "preact/compat";
5
+ import { useEffect, useRef } from "preact/hooks";
6
+
7
+ import RouteScheduleFooter from "../RouteScheduleFooter";
8
+ import RouteScheduleHeader from "../RouteScheduleHeader";
9
+ import RouteStop from "../RouteStop";
10
+ import useMapContext from "../utils/hooks/useMapContext";
11
+
12
+ export type RouteScheduleProps = JSX.HTMLAttributes<HTMLDivElement> &
13
+ PreactDOMAttributes;
14
+
15
+ function RouteSchedule(props: RouteScheduleProps) {
16
+ const { stopSequence } = useMapContext();
17
+ const ref = useRef();
18
+
19
+ useEffect(() => {
20
+ const interval = window.setInterval(() => {
21
+ const elt = ref.current as HTMLDivElement;
22
+ if (!elt) {
23
+ return;
24
+ }
25
+ const nextStation = elt.querySelector("[data-station-passed=false]");
26
+ if (nextStation) {
27
+ nextStation.scrollIntoView({
28
+ behavior: "smooth",
29
+ });
30
+ }
31
+ clearInterval(interval);
32
+ }, 300);
33
+ return () => {
34
+ clearTimeout(interval);
35
+ };
36
+ // Scroll automatically when a new scroll infos is set.
37
+ }, [stopSequence]);
38
+
39
+ if (!stopSequence) {
40
+ return null;
41
+ }
42
+
43
+ const { className } = props;
44
+
45
+ return (
46
+ <>
47
+ <RouteScheduleHeader />
48
+ <div className={className} ref={ref}>
49
+ {stopSequence.stations.map((stop: RealtimeStop, index: number) => {
50
+ const { arrivalTime, departureTime, stationId, stationName } = stop;
51
+ return (
52
+ <RouteStop
53
+ // Train line can go in circle so begin and end have the same id,
54
+ index={index}
55
+ // using the time in the key should fix the issue.
56
+ key={
57
+ (`${stationId}` || stationName) + arrivalTime + departureTime
58
+ }
59
+ stop={stop}
60
+ />
61
+ );
62
+ })}
63
+ <RouteScheduleFooter />
64
+ </div>
65
+ </>
66
+ );
67
+ }
68
+
69
+ export default memo(RouteSchedule);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteSchedule";
Binary file
Binary file
@@ -0,0 +1,44 @@
1
+ import { memo } from "preact/compat";
2
+
3
+ import useMapContext from "../utils/hooks/useMapContext";
4
+
5
+ const defaultRenderLink = (text: string, url: string) => {
6
+ return url ? (
7
+ <a
8
+ className="whitespace-normal underline"
9
+ href={url}
10
+ rel="noreferrer"
11
+ target="_blank"
12
+ >
13
+ {text}
14
+ </a>
15
+ ) : (
16
+ text
17
+ );
18
+ };
19
+
20
+ function RouteScheduleFooter() {
21
+ const { stopSequence } = useMapContext();
22
+ if (!stopSequence.operator && !stopSequence.publisher) {
23
+ return null;
24
+ }
25
+
26
+ return (
27
+ <>
28
+ <div className="m-4 mb-0 flex flex-wrap text-sm text-gray-500 ">
29
+ {stopSequence.operator &&
30
+ defaultRenderLink(stopSequence.operator, stopSequence.operatorUrl)}
31
+ {stopSequence.operator && stopSequence.publisher && <span> - </span>}
32
+ {stopSequence.publisher &&
33
+ defaultRenderLink(stopSequence.publisher, stopSequence.publisherUrl)}
34
+ {stopSequence.license && <span>&nbsp;(</span>}
35
+ {stopSequence.license &&
36
+ defaultRenderLink(stopSequence.license, stopSequence.licenseUrl)}
37
+ {stopSequence.license && ")"}
38
+ </div>
39
+ <div className="pointer-events-none sticky bottom-0 h-8 w-full bg-gradient-to-b from-transparent to-white" />
40
+ </>
41
+ );
42
+ }
43
+
44
+ export default memo(RouteScheduleFooter);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteScheduleFooter";
@@ -0,0 +1,58 @@
1
+ import { memo } from "preact/compat";
2
+
3
+ import RouteIcon from "../RouteIcon";
4
+ import RouteInfos from "../RouteInfos";
5
+ import getBgColor from "../utils/getBgColor";
6
+ import useMapContext from "../utils/hooks/useMapContext";
7
+
8
+ function RouteScheduleHeader() {
9
+ const { isFollowing, setIsFollowing, stopSequence } = useMapContext();
10
+ const { stroke, text_color: textColor, type, vehicleType } = stopSequence;
11
+ const backgroundColor = stroke || getBgColor(type || vehicleType);
12
+ const color = textColor || "black";
13
+ return (
14
+ <div className="flex items-center gap-x-4 bg-slate-100 p-4">
15
+ <RouteIcon stopSequence={stopSequence} />
16
+ <RouteInfos className="flex grow flex-col" stopSequence={stopSequence} />
17
+ <button
18
+ className={`flex size-[38px] flex-none items-center justify-center rounded-full bg-white p-1.5 shadow-lg ${
19
+ isFollowing ? "animate-pulse" : ""
20
+ }`}
21
+ onClick={() => {
22
+ setIsFollowing(!isFollowing);
23
+ }}
24
+ style={{
25
+ /* stylelint-disable-next-line value-keyword-case */
26
+ backgroundColor: isFollowing ? backgroundColor : "white",
27
+ color: isFollowing ? color : "black",
28
+ }}
29
+ type="button"
30
+ >
31
+ <svg
32
+ fill="none"
33
+ height="24"
34
+ part="svg"
35
+ viewBox="0 0 14 14"
36
+ width="24"
37
+ xmlns="http://www.w3.org/2000/svg"
38
+ >
39
+ <path
40
+ clipRule="evenodd"
41
+ d="M7 0.333344C7.375 0.333344 7.66667 0.62501 7.66667 0.97921V2.35414C9.7292 2.66668 11.3333 4.27081 11.625 6.33334H13C13.375 6.33334 13.6667 6.62501 13.6667 7.00001C13.6667 7.37501 13.375 7.66668 13 7.66668H11.625C11.3333 9.70834 9.70833 11.3333 7.66667 11.625V13C7.66667 13.375 7.375 13.6667 7 13.6667C6.64587 13.6667 6.33333 13.375 6.33333 13V11.625C4.29167 11.3333 2.68747 9.70834 2.39587 7.66668H1C0.625 7.66668 0.333333 7.37501 0.333333 7.00001C0.333333 6.62501 0.625 6.33334 1 6.33334H2.39587C2.68747 4.27081 4.29167 2.66668 6.33333 2.35414V0.97921C6.33333 0.62501 6.64587 0.333344 7 0.333344ZM7 3.66668C5.16667 3.66668 3.66667 5.16668 3.66667 7.00001C3.66667 8.79168 5.08333 10.3125 7 10.3125C8.89587 10.3125 10.3333 8.81254 10.3333 7.00001C10.3333 5.16668 8.83333 3.66668 7 3.66668Z"
42
+ fill="currentColor"
43
+ fillRule="evenodd"
44
+ />
45
+ <path
46
+ clipRule="evenodd"
47
+ d="M5.66667 7.00001C5.66667 6.27081 6.2708 5.66668 7 5.66668C7.7292 5.66668 8.33333 6.27081 8.33333 7.00001C8.33333 7.72921 7.7292 8.33334 7 8.33334C6.2708 8.33334 5.66667 7.72921 5.66667 7.00001Z"
48
+ fill="currentColor"
49
+ fillRule="evenodd"
50
+ part="circle"
51
+ />
52
+ </svg>
53
+ </button>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export default memo(RouteScheduleHeader);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteScheduleHeader";
@@ -0,0 +1,121 @@
1
+ import type { RealtimeStation, RealtimeStop } from "mobility-toolbox-js/types";
2
+
3
+ import { JSX, PreactDOMAttributes } from "preact";
4
+ import { memo } from "preact/compat";
5
+ import { useEffect, useMemo, useState } from "preact/hooks";
6
+
7
+ import DebugStop from "../DebugStop/DebugStop";
8
+ import RouteStopDelay from "../RouteStopDelay";
9
+ import RouteStopProgress from "../RouteStopProgress";
10
+ import RouteStopStation from "../RouteStopStation";
11
+ import RouteStopTime from "../RouteStopTime";
12
+ import getStopStatus from "../utils/getStopStatus";
13
+ import useMapContext from "../utils/hooks/useMapContext";
14
+ import { RouteStopContext } from "../utils/hooks/useRouteStop";
15
+
16
+ export type RouteScheduleStopProps = {
17
+ classNameGreyOut?: string;
18
+ index?: number;
19
+ invertColor?: boolean;
20
+ stop?: {
21
+ platform?: string;
22
+ } & RealtimeStop;
23
+ } & JSX.HTMLAttributes<HTMLButtonElement> &
24
+ PreactDOMAttributes;
25
+
26
+ function RouteStop({
27
+ children,
28
+ classNameGreyOut = "text-gray-600",
29
+ index,
30
+ invertColor = false,
31
+ stop,
32
+ ...props
33
+ }: RouteScheduleStopProps) {
34
+ const { map, realtimeLayer, stopSequence } = useMapContext();
35
+ const {
36
+ // @ts-expect-error bad type definition
37
+ stopUID,
38
+ } = stop;
39
+ const [station, setStation] = useState<RealtimeStation>();
40
+ const [status, setStatus] = useState(getStopStatus(stopSequence, index));
41
+
42
+ useEffect(() => {
43
+ let interval = null;
44
+ setStatus(getStopStatus(stopSequence, index));
45
+
46
+ // To see the progress bar we have to update the status of the vehicle until we get the new stopSeqeunce.
47
+ if (status.isInBetween) {
48
+ interval = setInterval(() => {
49
+ setStatus(getStopStatus(stopSequence, index));
50
+ }, 1000);
51
+ }
52
+ return () => {
53
+ clearInterval(interval);
54
+ };
55
+ }, [index, status.isInBetween, stopSequence]);
56
+
57
+ useEffect(() => {
58
+ if (!stopUID || !realtimeLayer?.api) {
59
+ return;
60
+ }
61
+ const subscribe = async () => {
62
+ realtimeLayer?.api?.subscribe(`station ${stopUID}`, ({ content }) => {
63
+ if (content) {
64
+ setStation(content);
65
+ }
66
+ });
67
+ };
68
+ subscribe();
69
+
70
+ return () => {
71
+ setStation(null);
72
+ if (stopUID) {
73
+ realtimeLayer?.api?.unsubscribe(`station ${stopUID}`);
74
+ }
75
+ };
76
+ }, [stopUID, realtimeLayer?.api]);
77
+
78
+ const routeStopState = useMemo(() => {
79
+ return { index, invertColor, station, status, stop };
80
+ }, [stop, status, index, invertColor, station]);
81
+
82
+ let colorScheme = status.isPassed || status.isLeft ? classNameGreyOut : "";
83
+
84
+ if (invertColor) {
85
+ colorScheme =
86
+ status.isPassed || status.isLeft || status.isBoarding
87
+ ? ""
88
+ : classNameGreyOut;
89
+ }
90
+
91
+ return (
92
+ <RouteStopContext.Provider value={routeStopState}>
93
+ <button
94
+ // max-h-[58px] because the svg showing the progress is 58px height.
95
+ className={`flex max-h-[58px] w-full scroll-mt-[50px] items-stretch rounded text-left hover:bg-slate-100 ${colorScheme}`}
96
+ data-station-passed={status.isPassed} // Use for auto scroll
97
+ onClick={() => {
98
+ if (stop.coordinate) {
99
+ map.getView().animate({
100
+ center: [stop.coordinate[0], stop.coordinate[1]],
101
+ zoom: map.getView().getZoom(),
102
+ });
103
+ }
104
+ }}
105
+ type="button"
106
+ {...props}
107
+ >
108
+ {children || (
109
+ <>
110
+ <RouteStopTime className="ml-4 flex w-10 shrink-0 flex-col justify-center text-xs" />
111
+ <RouteStopDelay className="flex w-8 shrink-0 flex-col justify-center text-[0.6rem]" />
112
+ <RouteStopProgress className="relative flex w-8 shrink-0 items-center" />
113
+ <RouteStopStation className="flex grow flex-col items-start justify-center pr-2 text-sm font-medium" />
114
+ </>
115
+ )}
116
+ </button>
117
+ <DebugStop />
118
+ </RouteStopContext.Provider>
119
+ );
120
+ }
121
+ export default memo(RouteStop);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStop";
@@ -0,0 +1,36 @@
1
+ import { JSX, PreactDOMAttributes } from "preact";
2
+ import { memo } from "preact/compat";
3
+
4
+ import getDelayColor from "../utils/getDelayColor";
5
+ import getDelayString from "../utils/getDelayString";
6
+ import useRouteStop from "../utils/hooks/useRouteStop";
7
+
8
+ export type RouteStopDelayProps = JSX.HTMLAttributes<HTMLDivElement> &
9
+ PreactDOMAttributes;
10
+
11
+ function RouteStopDelay(props: RouteStopDelayProps) {
12
+ const { status, stop } = useRouteStop();
13
+ const { arrivalDelay, departureDelay } = stop;
14
+
15
+ const hideDelay =
16
+ status.isNotRealtime ||
17
+ status.isCancelled ||
18
+ status.isNotStop ||
19
+ status.isPassed;
20
+
21
+ return (
22
+ <div {...props}>
23
+ {hideDelay || arrivalDelay === null || status.isFirst ? null : (
24
+ <span style={{ color: getDelayColor(arrivalDelay) }}>
25
+ {`+${getDelayString(arrivalDelay)}`}
26
+ </span>
27
+ )}
28
+ {hideDelay || departureDelay === null || status.isLast ? null : (
29
+ <span style={{ color: getDelayColor(arrivalDelay) }}>
30
+ {`+${getDelayString(departureDelay)}`}
31
+ </span>
32
+ )}
33
+ </div>
34
+ );
35
+ }
36
+ export default memo(RouteStopDelay);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopDelay";
@@ -0,0 +1,24 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+
5
+ import RouteStopPlatform from "../RouteStopPlatform";
6
+ import useRouteStop from "../utils/hooks/useRouteStop";
7
+
8
+ export type RouteStopNameProps = JSX.HTMLAttributes<HTMLDivElement> &
9
+ PreactDOMAttributes;
10
+
11
+ function RouteStopName({ children, ...props }: RouteStopNameProps) {
12
+ const { stop } = useRouteStop();
13
+ const { stationName } = stop || {};
14
+ return (
15
+ <div {...props}>
16
+ {stationName}
17
+ <br />
18
+ <RouteStopPlatform className="rounded-sm bg-slate-100 px-0.5 py-px text-xs group-hover:bg-slate-50" />
19
+ {children}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ export default memo(RouteStopName);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopName";
@@ -0,0 +1,29 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+
5
+ import useI18n from "../utils/hooks/useI18n";
6
+ import useMapContext from "../utils/hooks/useMapContext";
7
+ import useRouteStop from "../utils/hooks/useRouteStop";
8
+
9
+ export type RouteStopPlatformProps = JSX.HTMLAttributes<HTMLSpanElement> &
10
+ PreactDOMAttributes;
11
+
12
+ function RouteStopPlatform({ ...props }: RouteStopPlatformProps) {
13
+ const { stop } = useRouteStop();
14
+ const { stopSequence } = useMapContext();
15
+ const { type } = stopSequence;
16
+ const { t } = useI18n();
17
+ const { platform } = stop || {};
18
+ if (!platform) {
19
+ return null;
20
+ }
21
+ const translated = t(`platform_${type || "rail"}`);
22
+ return (
23
+ <span {...props}>
24
+ {translated || t(`platform_rail`)} {platform}
25
+ </span>
26
+ );
27
+ }
28
+
29
+ export default memo(RouteStopPlatform);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopPlatform";
@@ -0,0 +1,101 @@
1
+ import { JSX, PreactDOMAttributes } from "preact";
2
+ import { memo } from "preact/compat";
3
+
4
+ import getMainColorForVehicle from "../utils/getMainColorForVehicle";
5
+ import useMapContext from "../utils/hooks/useMapContext";
6
+ import useRouteStop from "../utils/hooks/useRouteStop";
7
+
8
+ export type RouteStopProgressProps = {
9
+ svgProps?: JSX.HTMLAttributes<SVGElement> & PreactDOMAttributes;
10
+ } & JSX.HTMLAttributes<HTMLDivElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function RouteStopProgress({ svgProps, ...props }: RouteStopProgressProps) {
14
+ const { stopSequence } = useMapContext();
15
+ const { invertColor, status } = useRouteStop();
16
+ const { isBoarding, isFirst, isLast, isLeft, isPassed, progress } = status;
17
+ const y1 = isFirst ? "50%" : "-100%";
18
+ const y2 = isLast ? "50%" : "100%";
19
+ const yDone = `${progress}%`;
20
+
21
+ const greyColor = "rgb(156, 163, 175)";
22
+ const lineColor = getMainColorForVehicle(stopSequence);
23
+
24
+ let colorScheme = isPassed ? greyColor : lineColor;
25
+ let invertColorScheme = isPassed ? lineColor : greyColor;
26
+ let progressDoneColor = greyColor;
27
+
28
+ if (invertColor) {
29
+ colorScheme = isPassed ? lineColor : greyColor;
30
+ invertColorScheme = isPassed ? greyColor : lineColor;
31
+ progressDoneColor = lineColor;
32
+ }
33
+
34
+ const circleColor =
35
+ !isPassed && (isLeft || isBoarding) ? invertColorScheme : colorScheme;
36
+
37
+ return (
38
+ <div {...props}>
39
+ <svg
40
+ fill="none"
41
+ height="100%"
42
+ stroke={colorScheme}
43
+ width="16"
44
+ xmlns="http://www.w3.org/2000/svg"
45
+ {...svgProps}
46
+ >
47
+ {/* Circle used to display a black border */}
48
+ <circle cx="50%" cy="50%" r="5" stroke="black" strokeWidth="6" />
49
+
50
+ {/* Black line used to display a black border */}
51
+ <line
52
+ stroke="black"
53
+ strokeWidth="6"
54
+ x1="50%"
55
+ x2="50%"
56
+ y1={y1}
57
+ y2={y2}
58
+ />
59
+
60
+ {/* Colored line (grey or color) */}
61
+ <line
62
+ stroke={progressDoneColor}
63
+ strokeWidth="4"
64
+ x1="50%"
65
+ x2="50%"
66
+ y1={y1}
67
+ y2={yDone}
68
+ />
69
+ <line
70
+ strokeWidth="4"
71
+ x1="50%"
72
+ x2="50%"
73
+ y1={`${progress - 2}%`} // we use -2 because sometimes it could be a small gap between the previousline and this one
74
+ y2={y2}
75
+ />
76
+
77
+ {/* Colored circle (grey or color) */}
78
+ <circle cx="50%" cy="50%" r="5" strokeWidth="4" />
79
+ <circle
80
+ className={isBoarding ? "animate-pulse" : ""}
81
+ cx="50%"
82
+ cy="50%"
83
+ r="5"
84
+ stroke={circleColor}
85
+ strokeWidth="4"
86
+ />
87
+
88
+ {/* white circle with black border */}
89
+ <circle
90
+ cx="50%"
91
+ cy="50%"
92
+ fill="white"
93
+ r="3"
94
+ stroke="black"
95
+ strokeWidth="1"
96
+ />
97
+ </svg>
98
+ </div>
99
+ );
100
+ }
101
+ export default memo(RouteStopProgress);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopProgress";
@@ -0,0 +1,26 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+
5
+ import StationServices from "../StationServices";
6
+ import useRouteStop from "../utils/hooks/useRouteStop";
7
+
8
+ export type RouteStopNameProps = JSX.HTMLAttributes<HTMLDivElement> &
9
+ PreactDOMAttributes;
10
+
11
+ function RouteStopServices(props: RouteStopNameProps) {
12
+ const { station } = useRouteStop();
13
+ if (!station) {
14
+ return null;
15
+ }
16
+ return (
17
+ <StationServices
18
+ accessibility
19
+ station={station}
20
+ {...props}
21
+ iconProps={{ width: 20 }}
22
+ />
23
+ );
24
+ }
25
+
26
+ export default memo(RouteStopServices);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopServices";
@@ -0,0 +1,32 @@
1
+ import { JSX, PreactDOMAttributes } from "preact";
2
+ import { memo } from "preact/compat";
3
+
4
+ import RouteStopName from "../RouteStopName";
5
+ import RouteStopServices from "../RouteStopServices";
6
+ import useRouteStop from "../utils/hooks/useRouteStop";
7
+
8
+ export type RouteStopStationProps = {
9
+ classNameCancelled?: string;
10
+ } & JSX.HTMLAttributes<HTMLDivElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function RouteStopStation({
14
+ children,
15
+ className = "flex items-center gap-2",
16
+ classNameCancelled = "text-red-600 line-through",
17
+ ...props
18
+ }: RouteStopStationProps) {
19
+ const { status } = useRouteStop();
20
+
21
+ return (
22
+ <div
23
+ {...props}
24
+ className={`${className} ${status.isCancelled ? classNameCancelled : ""}`}
25
+ >
26
+ <RouteStopName />
27
+ <RouteStopServices className="flex flex-wrap gap-1" />
28
+ {children}
29
+ </div>
30
+ );
31
+ }
32
+ export default memo(RouteStopStation);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopStation";
@@ -0,0 +1,34 @@
1
+ import { JSX, PreactDOMAttributes } from "preact";
2
+ import { memo } from "preact/compat";
3
+
4
+ import getHoursAndMinutes from "../utils/getHoursAndMinutes";
5
+ import useRouteStop from "../utils/hooks/useRouteStop";
6
+
7
+ export type RouteStopTimeProps = JSX.HTMLAttributes<HTMLDivElement> &
8
+ PreactDOMAttributes;
9
+
10
+ function RouteStopTime(props: RouteStopTimeProps) {
11
+ const { status, stop } = useRouteStop();
12
+ const { isCancelled, isFirst, isLast } = status;
13
+ const { aimedArrivalTime, aimedDepartureTime } = stop;
14
+
15
+ return (
16
+ <div {...props}>
17
+ <span
18
+ className={`${isCancelled ? "text-red-600 line-through" : ""} ${
19
+ isFirst ? "hidden" : ""
20
+ }`}
21
+ >
22
+ {getHoursAndMinutes(aimedArrivalTime)}
23
+ </span>
24
+ <span
25
+ className={`${status.isCancelled ? "text-red-600 line-through" : ""} ${
26
+ isLast ? "hidden" : ""
27
+ }`}
28
+ >
29
+ {getHoursAndMinutes(aimedDepartureTime)}
30
+ </span>
31
+ </div>
32
+ );
33
+ }
34
+ export default memo(RouteStopTime);
@@ -0,0 +1 @@
1
+ export { default } from "./RouteStopTime";