@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.
- package/.fixpackrc +21 -0
- package/.husky/commit-msg +1 -0
- package/.husky/post-checkout +1 -0
- package/.husky/post-merge +1 -0
- package/.husky/post-rebase +1 -0
- package/.husky/pre-commit +1 -0
- package/.lintstagedrc.js +12 -0
- package/.nvmrc +1 -0
- package/.prettierrc.js +1 -0
- package/CHANGELOG.md +183 -0
- package/Logo.svg +10 -0
- package/README.md +179 -0
- package/__mocks__/dataurl.js +11 -0
- package/__mocks__/mapbox-gl.js +19 -0
- package/commitlint.config.cjs +1 -0
- package/doc/.eslintrc.json +3 -0
- package/doc/.fixpackrc +21 -0
- package/doc/.prettierrc +1 -0
- package/doc/README.md +14 -0
- package/doc/declarations.d.ts +6 -0
- package/doc/next.config.mjs +4 -0
- package/doc/package.json +43 -0
- package/doc/postcss.config.mjs +8 -0
- package/doc/public/README.md +1 -0
- package/doc/src/app/components/GeopsAPIKeyLink.tsx +6 -0
- package/doc/src/app/components/GeopsAPIsLink.tsx +6 -0
- package/doc/src/app/components/GeopsMapsAPILink.tsx +8 -0
- package/doc/src/app/components/GeopsMobility.tsx +9 -0
- package/doc/src/app/components/GeopsMobilityDoc.tsx +231 -0
- package/doc/src/app/components/GeopsMobilitySearch.tsx +10 -0
- package/doc/src/app/components/GeopsMobilitySearchDoc.tsx +129 -0
- package/doc/src/app/components/GeopsRealtimeAPILink.tsx +10 -0
- package/doc/src/app/components/GeopsStopsAPILink.tsx +8 -0
- package/doc/src/app/components/Link.tsx +9 -0
- package/doc/src/app/components/WebComponentDoc.tsx +296 -0
- package/doc/src/app/favicon.ico +0 -0
- package/doc/src/app/geops-mobility/page.tsx +6 -0
- package/doc/src/app/geops-mobility-search/page.tsx +7 -0
- package/doc/src/app/globals.css +38 -0
- package/doc/src/app/hooks/useAttrFromUrlParams.ts +21 -0
- package/doc/src/app/hooks/useIsFullScreen.ts +14 -0
- package/doc/src/app/hooks/usePublicKey.ts +21 -0
- package/doc/src/app/layout.tsx +51 -0
- package/doc/src/app/page.tsx +86 -0
- package/doc/src/geops-ui.ts +3 -0
- package/doc/tailwind.config.ts +20 -0
- package/doc/tsconfig.json +40 -0
- package/eslint.config.mjs +40 -0
- package/favicon.ico +0 -0
- package/global.d.ts +4 -0
- package/iframe.html +34 -0
- package/index.html +276 -0
- package/index.js +2162 -0
- package/input.css +34 -0
- package/jest-setup.js +4 -0
- package/jest.config.js +17 -0
- package/package.json +80 -0
- package/scripts/build.mjs +16 -0
- package/scripts/dev.mjs +26 -0
- package/search.html +144 -0
- package/src/BaseLayer/BaseLayer.tsx +36 -0
- package/src/BaseLayer/index.tsx +1 -0
- package/src/Copyright/Copyright.tsx +54 -0
- package/src/Copyright/index.css +3 -0
- package/src/Copyright/index.tsx +1 -0
- package/src/DebugDeparture/DebugDeparture.tsx +116 -0
- package/src/DebugDeparture/index.tsx +1 -0
- package/src/DebugStop/DebugStop.tsx +47 -0
- package/src/DebugStop/index.tsx +1 -0
- package/src/Departure/Departure.tsx +55 -0
- package/src/Departure/index.tsx +1 -0
- package/src/GeolocationButton/GeolocationButton.tsx +81 -0
- package/src/GeolocationButton/index.tsx +1 -0
- package/src/Map/Map.tsx +89 -0
- package/src/Map/index.tsx +1 -0
- package/src/MobilityMap/MobilityMap.tsx +259 -0
- package/src/MobilityMap/index.css +13 -0
- package/src/MobilityMap/index.tsx +1 -0
- package/src/NotificationLayer/NotificationLayer.tsx +156 -0
- package/src/NotificationLayer/index.tsx +1 -0
- package/src/NotificationLayer/notificationUtils.ts +191 -0
- package/src/Overlay/Overlay.tsx +57 -0
- package/src/Overlay/index.tsx +1 -0
- package/src/RealtimeLayer/RealtimeLayer.tsx +230 -0
- package/src/RealtimeLayer/index.tsx +1 -0
- package/src/RouteDestination/RouteDestination.test.tsx +13 -0
- package/src/RouteDestination/RouteDestination.tsx +15 -0
- package/src/RouteDestination/index.tsx +1 -0
- package/src/RouteIcon/RouteIcon.tsx +66 -0
- package/src/RouteIcon/index.tsx +1 -0
- package/src/RouteIdentifier/RouteIdentifer.tsx +35 -0
- package/src/RouteIdentifier/index.tsx +1 -0
- package/src/RouteInfos/RouteInfos.tsx +22 -0
- package/src/RouteInfos/index.tsx +1 -0
- package/src/RouteSchedule/RouteSchedule.tsx +69 -0
- package/src/RouteSchedule/firstStation.png +0 -0
- package/src/RouteSchedule/index.tsx +1 -0
- package/src/RouteSchedule/lastStation.png +0 -0
- package/src/RouteSchedule/line.png +0 -0
- package/src/RouteSchedule/station.png +0 -0
- package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +44 -0
- package/src/RouteScheduleFooter/index.tsx +1 -0
- package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +58 -0
- package/src/RouteScheduleHeader/index.tsx +1 -0
- package/src/RouteStop/RouteStop.tsx +121 -0
- package/src/RouteStop/index.tsx +1 -0
- package/src/RouteStopDelay/RouteStopDelay.tsx +36 -0
- package/src/RouteStopDelay/index.tsx +1 -0
- package/src/RouteStopName/RouteStopName.tsx +24 -0
- package/src/RouteStopName/index.tsx +1 -0
- package/src/RouteStopPlatform/RouteStopPlatform.tsx +29 -0
- package/src/RouteStopPlatform/index.tsx +1 -0
- package/src/RouteStopProgress/RouteStopProgress.tsx +101 -0
- package/src/RouteStopProgress/index.tsx +1 -0
- package/src/RouteStopServices/RouteStopServices.tsx +26 -0
- package/src/RouteStopServices/index.tsx +1 -0
- package/src/RouteStopStation/RouteStopStation.tsx +32 -0
- package/src/RouteStopStation/index.tsx +1 -0
- package/src/RouteStopTime/RouteStopTime.tsx +34 -0
- package/src/RouteStopTime/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +245 -0
- package/src/RvfMobilityMap/index.css +13 -0
- package/src/RvfMobilityMap/index.tsx +1 -0
- package/src/ScaleLine/ScaleLine.tsx +51 -0
- package/src/ScaleLine/index.css +6 -0
- package/src/ScaleLine/index.tsx +1 -0
- package/src/ScrollableHandler/ScrollableHandler.tsx +65 -0
- package/src/ScrollableHandler/index.tsx +1 -0
- package/src/Search/Search.tsx +18 -0
- package/src/Search/index.tsx +1 -0
- package/src/SingleClickListener/SingleClickListener.tsx +103 -0
- package/src/SingleClickListener/index.tsx +1 -0
- package/src/Station/Station.tsx +68 -0
- package/src/Station/index.tsx +1 -0
- package/src/StationHeader/StationHeader.tsx +32 -0
- package/src/StationHeader/index.tsx +1 -0
- package/src/StationName/StationName.tsx +21 -0
- package/src/StationName/index.tsx +1 -0
- package/src/StationServices/StationServices.tsx +80 -0
- package/src/StationServices/index.tsx +1 -0
- package/src/StationsLayer/StationsLayer.tsx +41 -0
- package/src/StationsLayer/index.tsx +1 -0
- package/src/StopsSearch/StopsSearch.tsx +254 -0
- package/src/StopsSearch/index.tsx +1 -0
- package/src/icons/Airport/Airport.tsx +17 -0
- package/src/icons/Airport/airport-14-svgrepo-com.svg +41 -0
- package/src/icons/Airport/index.tsx +1 -0
- package/src/icons/BarAndRestaurants/BarAndRestaurants.tsx +17 -0
- package/src/icons/BarAndRestaurants/food-restaurant-svgrepo-com.svg +12 -0
- package/src/icons/BarAndRestaurants/index.tsx +1 -0
- package/src/icons/Bathroom/Bathroom.tsx +59 -0
- package/src/icons/Bathroom/bathroom-restroom-svgrepo-com.svg +38 -0
- package/src/icons/Bathroom/index.tsx +1 -0
- package/src/icons/BikeStorage/BikeStorage.tsx +17 -0
- package/src/icons/BikeStorage/index.tsx +1 -0
- package/src/icons/BikeStorage/parking-bicycle-14-svgrepo-com.svg +41 -0
- package/src/icons/Elevator/Elevator.tsx +16 -0
- package/src/icons/Elevator/elevator-svgrepo-com.svg +2 -0
- package/src/icons/Elevator/index.tsx +1 -0
- package/src/icons/Police/Police.tsx +20 -0
- package/src/icons/Police/index.tsx +1 -0
- package/src/icons/Police/polizia.png +0 -0
- package/src/icons/README.md +52 -0
- package/src/icons/WaitingAreas/WaitingAreas.tsx +16 -0
- package/src/icons/WaitingAreas/highway-rest-area-svgrepo-com.svg +5 -0
- package/src/icons/WaitingAreas/index.tsx +1 -0
- package/src/icons/WaitingAreas/wheelchair-svgrepo-com.svg +2 -0
- package/src/icons/WheelChair/WheelChair.tsx +16 -0
- package/src/icons/WheelChair/disabili.png +0 -0
- package/src/icons/WheelChair/index.tsx +1 -0
- package/src/icons/WheelChair/wheelchair-svgrepo-com.svg +2 -0
- package/src/index.tsx +50 -0
- package/src/utils/MobilityEvent.ts +21 -0
- package/src/utils/addSourceAndLayers.ts +62 -0
- package/src/utils/centerOnStation.ts +17 -0
- package/src/utils/centerOnVehicle.ts +50 -0
- package/src/utils/getBgColor.ts +3 -0
- package/src/utils/getDelayColor.test.ts +20 -0
- package/src/utils/getDelayColor.ts +23 -0
- package/src/utils/getDelayColorForVehicle.test.ts +28 -0
- package/src/utils/getDelayColorForVehicle.ts +25 -0
- package/src/utils/getDelayFontForVehicle.test.ts +7 -0
- package/src/utils/getDelayFontForVehicle.tsx +8 -0
- package/src/utils/getDelayString.test.ts +22 -0
- package/src/utils/getDelayString.ts +28 -0
- package/src/utils/getDelayTextForVehicle.test.ts +28 -0
- package/src/utils/getDelayTextForVehicle.ts +21 -0
- package/src/utils/getFullTrajectoryAndFit.ts +40 -0
- package/src/utils/getHoursAndMinutes.test.ts +14 -0
- package/src/utils/getHoursAndMinutes.ts +22 -0
- package/src/utils/getMainColorForVehicle.test.ts +27 -0
- package/src/utils/getMainColorForVehicle.ts +46 -0
- package/src/utils/getStopStatus.test.ts +104 -0
- package/src/utils/getStopStatus.ts +171 -0
- package/src/utils/getTextFontForVehicle.test.ts +7 -0
- package/src/utils/getTextFontForVehicle.tsx +9 -0
- package/src/utils/getTextForVehicle.test.ts +17 -0
- package/src/utils/getTextForVehicle.ts +19 -0
- package/src/utils/hooks/useDebug.tsx +11 -0
- package/src/utils/hooks/useDeparture.tsx +23 -0
- package/src/utils/hooks/useI18n.tsx +20 -0
- package/src/utils/hooks/useMapContext.tsx +74 -0
- package/src/utils/hooks/useParams.ts +5 -0
- package/src/utils/hooks/useRouteStop.tsx +33 -0
- package/src/utils/hooks/useStation.tsx +21 -0
- package/src/utils/hooks/useUpdatePermalink.tsx +33 -0
- package/src/utils/hooks/useZoom.tsx +32 -0
- package/src/utils/i18n.ts +16 -0
- package/src/utils/translations.ts +31 -0
- package/tailwind.config.mjs +56 -0
- package/testNotification.json +50653 -0
- 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¬ificationat=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 @@
|
|
|
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 @@
|
|
|
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";
|