@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,245 @@
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 { getCenter } from "ol/extent";
14
+ import { fromLonLat } from "ol/proj";
15
+ import { memo } from "preact/compat";
16
+ import { useEffect, useMemo, useState } from "preact/hooks";
17
+
18
+ import BaseLayer from "../BaseLayer";
19
+ import Copyright from "../Copyright";
20
+ import GeolocationButton from "../GeolocationButton";
21
+ import Map from "../Map";
22
+ import { MobilityMapProps } from "../MobilityMap/MobilityMap";
23
+ import NotificationLayer from "../NotificationLayer";
24
+ import Overlay from "../Overlay";
25
+ import RealtimeLayer from "../RealtimeLayer";
26
+ import RouteSchedule from "../RouteSchedule";
27
+ import ScaleLine from "../ScaleLine";
28
+ import Search from "../Search";
29
+ import SingleClickListener from "../SingleClickListener/SingleClickListener";
30
+ import Station from "../Station";
31
+ import StationsLayer from "../StationsLayer";
32
+ // @ts-expect-error bad type definition
33
+ import tailwind from "../style.css";
34
+ import { I18nContext } from "../utils/hooks/useI18n";
35
+ import { MapContext } from "../utils/hooks/useMapContext";
36
+ import useUpdatePermalink from "../utils/hooks/useUpdatePermalink";
37
+ import i18n from "../utils/i18n";
38
+ import MobilityEvent from "../utils/MobilityEvent";
39
+ // @ts-expect-error bad type definition
40
+ import style from "./index.css";
41
+ // 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
42
+
43
+ export type RvfMobilityMapProps = {} & MobilityMapProps;
44
+
45
+ const extent = [7.5, 47.7, 8.45, 48.4];
46
+ const centerRvf = fromLonLat(getCenter(extent)).join(",");
47
+
48
+ function RvfMobilityMap({
49
+ apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
50
+ baselayer = "de.rvf",
51
+ center = centerRvf,
52
+ geolocation = "true",
53
+ mapsurl = "https://maps.geops.io",
54
+ maxzoom = null,
55
+ minzoom = null,
56
+ mots = null,
57
+ notification = "true",
58
+ notificationat = null,
59
+ notificationbeforelayerid = null,
60
+ notificationurl = null,
61
+ permalink = "false",
62
+ realtime = "true",
63
+ realtimeurl = "wss://api.geops.io/tracker-ws/v1/ws",
64
+ search = "false",
65
+ stopsurl = "https://api.geops.io/stops/v1/",
66
+ tenant = null,
67
+ zoom = "9.8",
68
+ }: RvfMobilityMapProps) {
69
+ const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
70
+ const [isFollowing, setIsFollowing] = useState(false);
71
+ const [isTracking, setIsTracking] = useState(false);
72
+ const [stopSequence, setStopSequence] = useState<RealtimeStopSequence>();
73
+ const [stationsLayer, setStationsLayer] = useState<MaplibreStyleLayer>();
74
+ const [station, setStation] = useState<RealtimeStation>();
75
+ const [realtimeLayer, setRealtimeLayer] = useState<MbtRealtimeLayer>();
76
+ const [map, setMap] = useState<OlMap>();
77
+ const [stationId, setStationId] = useState<RealtimeStationId>();
78
+ const [trainId, setTrainId] = useState<RealtimeTrainId>();
79
+
80
+ // TODO: this should be removed. The parent application should be responsible to do this
81
+ // or we should find something that fit more usecases
82
+ const { x, y, z } = useUpdatePermalink(map, permalink === "true");
83
+
84
+ const mapContextValue = useMemo(() => {
85
+ return {
86
+ // MobilityMapProps && MapContextProps
87
+ apikey,
88
+ baselayer,
89
+ baseLayer,
90
+ center,
91
+ geolocation,
92
+ isFollowing,
93
+ isTracking,
94
+ map,
95
+ mapsurl,
96
+ maxzoom,
97
+ minzoom,
98
+ mots,
99
+ notification,
100
+ notificationat,
101
+ notificationbeforelayerid,
102
+ notificationurl,
103
+ permalink,
104
+ realtimeLayer,
105
+ realtimeurl,
106
+ setBaseLayer,
107
+ setIsFollowing,
108
+ setIsTracking,
109
+ setMap,
110
+ setRealtimeLayer,
111
+ setStation,
112
+ setStationId,
113
+ setStationsLayer,
114
+ setStopSequence,
115
+ setTrainId,
116
+ station,
117
+ stationId,
118
+ stationsLayer,
119
+ stopSequence,
120
+ stopsurl,
121
+ tenant,
122
+ trainId,
123
+ zoom,
124
+ };
125
+ }, [
126
+ apikey,
127
+ baselayer,
128
+ baseLayer,
129
+ center,
130
+ geolocation,
131
+ isFollowing,
132
+ isTracking,
133
+ map,
134
+ mapsurl,
135
+ maxzoom,
136
+ minzoom,
137
+ mots,
138
+ notification,
139
+ notificationat,
140
+ notificationbeforelayerid,
141
+ notificationurl,
142
+ permalink,
143
+ realtimeLayer,
144
+ realtimeurl,
145
+ station,
146
+ stationId,
147
+ stationsLayer,
148
+ stopSequence,
149
+ stopsurl,
150
+ tenant,
151
+ trainId,
152
+ zoom,
153
+ ]);
154
+
155
+ useEffect(() => {
156
+ dispatchEvent(
157
+ new MobilityEvent<RvfMobilityMapProps>("mwc:attribute", {
158
+ baselayer,
159
+ center: x && y ? `${x},${y}` : center,
160
+ geolocation,
161
+ mapsurl,
162
+ maxzoom,
163
+ minzoom,
164
+ mots,
165
+ notification,
166
+ notificationat,
167
+ notificationbeforelayerid,
168
+ notificationurl,
169
+ realtime,
170
+ realtimeurl,
171
+ search,
172
+ tenant,
173
+ zoom: z || zoom,
174
+ }),
175
+ );
176
+ }, [
177
+ baselayer,
178
+ center,
179
+ geolocation,
180
+ mapsurl,
181
+ maxzoom,
182
+ minzoom,
183
+ mots,
184
+ notification,
185
+ notificationat,
186
+ notificationurl,
187
+ notificationbeforelayerid,
188
+ realtime,
189
+ realtimeurl,
190
+ search,
191
+ tenant,
192
+ zoom,
193
+ x,
194
+ y,
195
+ z,
196
+ ]);
197
+
198
+ return (
199
+ <I18nContext.Provider value={i18n}>
200
+ <style>{tailwind}</style>
201
+ <style>{style}</style>
202
+ <MapContext.Provider value={mapContextValue}>
203
+ <div className="relative size-full border font-sans @container/main">
204
+ <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
205
+ <Map className="relative flex-1 overflow-visible ">
206
+ <BaseLayer />
207
+ <SingleClickListener />
208
+ {realtime === "true" && <RealtimeLayer />}
209
+ {tenant && <StationsLayer />}
210
+ {notification === "true" && <NotificationLayer />}
211
+ <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
212
+ <ScaleLine className="bg-slate-50/70" />
213
+ <Copyright className="bg-slate-50/70" />
214
+ </div>
215
+ <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
216
+ {geolocation === "true" && <GeolocationButton />}
217
+ </div>
218
+ {search === "true" && (
219
+ <div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
220
+ <Search />
221
+ </div>
222
+ )}
223
+ </Map>
224
+
225
+ <Overlay
226
+ className={"z-50"}
227
+ ScrollableHandlerProps={{
228
+ style: { width: "calc(100% - 60px)" },
229
+ }}
230
+ >
231
+ {realtime === "true" && trainId && (
232
+ <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
233
+ )}
234
+ {tenant && stationId && (
235
+ <Station className="relative overflow-y-auto overflow-x-hidden" />
236
+ )}
237
+ </Overlay>
238
+ </div>
239
+ </div>
240
+ </MapContext.Provider>
241
+ </I18nContext.Provider>
242
+ );
243
+ }
244
+
245
+ export default memo(RvfMobilityMap);
@@ -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,51 @@
1
+ import ScaleLineControl, {
2
+ Options as ScaleLineOptions,
3
+ } from "ol/control/ScaleLine";
4
+ import { JSX, PreactDOMAttributes } from "preact";
5
+ import { useEffect, useMemo, useState } from "preact/hooks";
6
+
7
+ import useMapContext from "../utils/hooks/useMapContext";
8
+ // @ts-expect-error bad type definition
9
+ import style from "./index.css";
10
+
11
+ type ScaleLineProps = {
12
+ options?: ScaleLineOptions;
13
+ } & JSX.HTMLAttributes<HTMLDivElement> &
14
+ PreactDOMAttributes;
15
+
16
+ function ScaleLine({ options, ...props }: ScaleLineProps) {
17
+ const { map } = useMapContext();
18
+ const [target, setTarget] = useState<HTMLElement>();
19
+ const control = useMemo(() => {
20
+ if (!target) {
21
+ return null;
22
+ }
23
+ return new ScaleLineControl({ target, ...options });
24
+ }, [options, target]);
25
+
26
+ useEffect(() => {
27
+ if (!map || !control) {
28
+ return;
29
+ }
30
+ map.addControl(control);
31
+
32
+ return () => {
33
+ if (map && control) {
34
+ map.removeControl(control);
35
+ }
36
+ };
37
+ }, [map, control]);
38
+
39
+ return (
40
+ <div
41
+ ref={(node) => {
42
+ setTarget(node);
43
+ }}
44
+ {...props}
45
+ >
46
+ <style>{style}</style>
47
+ </div>
48
+ );
49
+ }
50
+
51
+ export default ScaleLine;
@@ -0,0 +1,6 @@
1
+ .ol-scale-line {
2
+ position:relative;
3
+ left: unset;
4
+ bottom: unset;
5
+ background:unset;
6
+ }
@@ -0,0 +1 @@
1
+ export { default } from "./ScaleLine";
@@ -0,0 +1,65 @@
1
+ import { JSX, PreactDOMAttributes } from "preact";
2
+ import { useEffect, useState } from "preact/hooks";
3
+
4
+ export type ScrollableHandlerProps = JSX.HTMLAttributes<HTMLDivElement> &
5
+ PreactDOMAttributes;
6
+
7
+ function ScrollableHandler(props: ScrollableHandlerProps) {
8
+ const [elt, setElt] = useState<HTMLElement>();
9
+ const [overlayElt, setOverlayElt] = useState<HTMLElement>();
10
+ const { children } = props;
11
+
12
+ useEffect(() => {
13
+ // Clean css added by the scroller
14
+ return () => {
15
+ if (overlayElt) {
16
+ overlayElt.style.height = "";
17
+ overlayElt.style.maxHeight = "";
18
+ }
19
+ };
20
+ }, [overlayElt]);
21
+
22
+ return (
23
+ <div
24
+ ref={(node) => {
25
+ if (node) {
26
+ setElt(node);
27
+ setOverlayElt(node.parentElement);
28
+ }
29
+ }}
30
+ {...props}
31
+ onPointerDown={(evt) => {
32
+ elt.setPointerCapture(evt.pointerId);
33
+
34
+ const mapRect = overlayElt.parentElement.getBoundingClientRect();
35
+ const eltRect = elt.getBoundingClientRect();
36
+ const deltaToTop = mapRect.top + (evt.clientY - eltRect.top);
37
+
38
+ function onDragg(event: PointerEvent) {
39
+ overlayElt.style.height = `calc(100% - ${
40
+ event.clientY - deltaToTop
41
+ }px)`;
42
+ overlayElt.style.maxHeight = `100%`;
43
+ }
44
+
45
+ function onDragStop(event: PointerEvent) {
46
+ (event.target as HTMLElement).releasePointerCapture(evt.pointerId);
47
+ document.removeEventListener("pointermove", onDragg);
48
+ document.removeEventListener("pointerup", onDragStop);
49
+ }
50
+ document.addEventListener("pointerup", onDragStop);
51
+ document.addEventListener("pointermove", onDragg);
52
+ document.addEventListener("pointercancel", (event: PointerEvent) => {
53
+ document.removeEventListener("pointermove", onDragg);
54
+ document.removeEventListener("pointerup", onDragStop);
55
+ event.stopPropagation();
56
+ event.preventDefault();
57
+ });
58
+ }}
59
+ >
60
+ {children}
61
+ </div>
62
+ );
63
+ }
64
+
65
+ export default ScrollableHandler;
@@ -0,0 +1 @@
1
+ export { default } from "./ScrollableHandler";
@@ -0,0 +1,18 @@
1
+ import StopsSearch from "../StopsSearch";
2
+ import centerOnStation from "../utils/centerOnStation";
3
+ import useMapContext from "../utils/hooks/useMapContext";
4
+
5
+ function Search() {
6
+ const { apikey, map, stopsurl } = useMapContext();
7
+
8
+ return (
9
+ <StopsSearch
10
+ apikey={apikey}
11
+ onselect={(selected) => {
12
+ return centerOnStation(selected, map);
13
+ }}
14
+ url={stopsurl}
15
+ />
16
+ );
17
+ }
18
+ export default Search;
@@ -0,0 +1 @@
1
+ export { default } from "./Search";
@@ -0,0 +1,103 @@
1
+ import { Feature, MapBrowserEvent } from "ol";
2
+ import { unByKey } from "ol/Observable";
3
+ import { useCallback, useEffect } from "preact/hooks";
4
+
5
+ import useMapContext from "../utils/hooks/useMapContext";
6
+
7
+ function SingleClickListener() {
8
+ const {
9
+ map,
10
+ realtimeLayer,
11
+ setStationId,
12
+ setTrainId,
13
+ stationId,
14
+ stationsLayer,
15
+ tenant,
16
+ trainId,
17
+ } = useMapContext();
18
+
19
+ const onPointerMove = useCallback(
20
+ async (evt: MapBrowserEvent<PointerEvent>) => {
21
+ const [realtimeFeature] = evt.map.getFeaturesAtPixel(evt.pixel, {
22
+ layerFilter: (l) => {
23
+ return l === realtimeLayer;
24
+ },
25
+ });
26
+ realtimeLayer?.highlight(realtimeFeature as Feature);
27
+
28
+ const stationsFeatures = evt.map.getFeaturesAtPixel(evt.pixel, {
29
+ layerFilter: (l) => {
30
+ return l === stationsLayer;
31
+ },
32
+ });
33
+
34
+ const [stationFeature] = stationsFeatures.filter((feat) => {
35
+ return feat.get("tralis_network")?.includes(tenant);
36
+ });
37
+
38
+ evt.map.getTargetElement().style.cursor =
39
+ realtimeFeature || stationFeature ? "pointer" : "default";
40
+ },
41
+ [realtimeLayer, stationsLayer, tenant],
42
+ );
43
+
44
+ const onSingleClick = useCallback(
45
+ async (evt: MapBrowserEvent<PointerEvent>) => {
46
+ const [realtimeFeature] = evt.map.getFeaturesAtPixel(evt.pixel, {
47
+ layerFilter: (l) => {
48
+ return l === realtimeLayer;
49
+ },
50
+ });
51
+
52
+ const stationsFeatures = evt.map.getFeaturesAtPixel(evt.pixel, {
53
+ layerFilter: (l) => {
54
+ return l === stationsLayer;
55
+ },
56
+ });
57
+ const [stationFeature] = stationsFeatures.filter((feat) => {
58
+ return feat.get("tralis_network")?.includes(tenant);
59
+ });
60
+
61
+ const newStationId = stationFeature?.get("uid");
62
+
63
+ const newTrainId = realtimeFeature?.get("train_id");
64
+
65
+ if (newStationId && stationId !== newStationId) {
66
+ setStationId(newStationId);
67
+ setTrainId(null);
68
+ } else if (newTrainId && newTrainId !== trainId) {
69
+ setTrainId(realtimeFeature.get("train_id"));
70
+ setStationId(null);
71
+ } else {
72
+ setTrainId(null);
73
+ setStationId(null);
74
+ }
75
+ },
76
+ [
77
+ stationId,
78
+ trainId,
79
+ realtimeLayer,
80
+ stationsLayer,
81
+ tenant,
82
+ setStationId,
83
+ setTrainId,
84
+ ],
85
+ );
86
+
87
+ useEffect(() => {
88
+ const key = map?.on("singleclick", onSingleClick);
89
+ return () => {
90
+ unByKey(key);
91
+ };
92
+ }, [map, onSingleClick]);
93
+
94
+ useEffect(() => {
95
+ const key = map?.on("pointermove", onPointerMove);
96
+ return () => {
97
+ unByKey(key);
98
+ };
99
+ }, [map, onPointerMove]);
100
+
101
+ return null;
102
+ }
103
+ export default SingleClickListener;
@@ -0,0 +1 @@
1
+ export { default } from "./SingleClickListener";
@@ -0,0 +1,68 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { debounceDeparturesMessages } from "mobility-toolbox-js/ol";
4
+ import { RealtimeDeparture } from "mobility-toolbox-js/types";
5
+ import { memo } from "preact/compat";
6
+ import { useEffect, useRef, useState } from "preact/hooks";
7
+
8
+ import Departure from "../Departure";
9
+ import StationHeader from "../StationHeader";
10
+ import useMapContext from "../utils/hooks/useMapContext";
11
+
12
+ export type StationProps = JSX.HTMLAttributes<HTMLDivElement> &
13
+ PreactDOMAttributes;
14
+
15
+ function Station(props: StationProps) {
16
+ const { realtimeLayer, station } = useMapContext();
17
+ const [departures, setDepartures] = useState<RealtimeDeparture[]>();
18
+ const ref = useRef();
19
+ const { className } = props;
20
+
21
+ useEffect(() => {
22
+ if (!station || !realtimeLayer?.api) {
23
+ return;
24
+ }
25
+
26
+ const onMessage = debounceDeparturesMessages(
27
+ (departures: RealtimeDeparture[]) => {
28
+ setDepartures(departures);
29
+ return null;
30
+ },
31
+ false,
32
+ 180,
33
+ );
34
+ // @ts-expect-error bad type definition
35
+ realtimeLayer.api.subscribeDepartures(station?.properties?.uid, onMessage);
36
+
37
+ return () => {
38
+ setDepartures(null);
39
+ // @ts-expect-error bad type definition
40
+ realtimeLayer?.api?.unsubscribeDepartures(station?.properties.uid);
41
+ };
42
+ }, [station, realtimeLayer?.api]);
43
+
44
+ if (!station) {
45
+ return null;
46
+ }
47
+
48
+ return (
49
+ <>
50
+ <StationHeader />
51
+ <div className={className} ref={ref}>
52
+ {(departures || [])
53
+ // .filter(hideDepartures)
54
+ .map((departure: RealtimeDeparture, index: number) => {
55
+ return (
56
+ <Departure
57
+ departure={departure}
58
+ index={index}
59
+ key={departure.call_id}
60
+ />
61
+ );
62
+ })}
63
+ </div>
64
+ </>
65
+ );
66
+ }
67
+
68
+ export default memo(Station);
@@ -0,0 +1 @@
1
+ export { default } from "./Station";
@@ -0,0 +1,32 @@
1
+ import { memo } from "preact/compat";
2
+
3
+ import StationName from "../StationName";
4
+ import StationServices from "../StationServices";
5
+ import useMapContext from "../utils/hooks/useMapContext";
6
+
7
+ function StationHeader() {
8
+ const { station } = useMapContext();
9
+ return (
10
+ <div className="flex items-center gap-x-4 bg-slate-100 p-4">
11
+ <div className="flex grow flex-col">
12
+ <span className="font-bold">
13
+ <StationName station={station} />
14
+ </span>
15
+ <StationServices
16
+ accessibility
17
+ airport
18
+ barAndRestaurants
19
+ bathroom
20
+ bikeStorage
21
+ className="flex gap-2"
22
+ elevator
23
+ police
24
+ station={station}
25
+ waitingAreas
26
+ />
27
+ </div>
28
+ </div>
29
+ );
30
+ }
31
+
32
+ export default memo(StationHeader);
@@ -0,0 +1 @@
1
+ export { default } from "./StationHeader";
@@ -0,0 +1,21 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { RealtimeStation } from "mobility-toolbox-js/types";
4
+ import { memo } from "preact/compat";
5
+
6
+ export type StationNameProps = {
7
+ station: RealtimeStation;
8
+ } & JSX.HTMLAttributes<HTMLDivElement> &
9
+ PreactDOMAttributes;
10
+
11
+ function StationName({ children, station, ...props }: StationNameProps) {
12
+ const { name } = station?.properties || {};
13
+ return (
14
+ <div {...props}>
15
+ {name}
16
+ {children}
17
+ </div>
18
+ );
19
+ }
20
+
21
+ export default memo(StationName);
@@ -0,0 +1 @@
1
+ export { default } from "./StationName";