@geops/rvf-mobility-web-component 0.1.12 → 0.1.14
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/CHANGELOG.md +47 -0
- package/index.html +1 -1
- package/index.js +340 -219
- package/input.css +2 -8
- package/package.json +18 -16
- package/scripts/build.mjs +0 -1
- package/scripts/dev.mjs +2 -1
- package/src/RouteSchedule/RouteSchedule.tsx +3 -1
- package/src/RouteStop/RouteStop.tsx +1 -1
- package/src/RvfButton/RvfButton.tsx +6 -3
- package/src/RvfCheckbox/RvfCheckbox.tsx +10 -3
- package/src/RvfExportMenu/RvfExportMenu.tsx +62 -68
- package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +3 -3
- package/src/RvfIconButton/RvfIconButton.tsx +5 -2
- package/src/RvfInputCopy/RvfInputCopy.tsx +57 -0
- package/src/RvfInputCopy/index.tsx +1 -0
- package/src/RvfLayerTree/RvfLayerTree.tsx +5 -9
- package/src/RvfLayerTree/TreeItem/TreeItem.tsx +39 -9
- package/src/RvfLayerTree/layersTreeReducer.ts +1 -0
- package/src/RvfLayerTreeButton/RvfLayerTreeButton.tsx +27 -0
- package/src/RvfLayerTreeButton/index.tsx +1 -0
- package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +9 -5
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +253 -35
- package/src/RvfOverlayHeader/RvfOverlayHeader.tsx +40 -0
- package/src/RvfOverlayHeader/index.tsx +1 -0
- package/src/RvfPermalink/RvfPermalink.tsx +18 -0
- package/src/RvfPermalink/index.tsx +1 -0
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -0
- package/src/RvfRadioButton/RvfRadioButton.tsx +1 -1
- package/src/RvfSelect/RvfSelect.tsx +2 -2
- package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +25 -6
- package/src/RvfShare/RvfPermalinkButton/RvfPermalinkButton.tsx +61 -0
- package/src/RvfShare/RvfPermalinkButton/index.tsx +1 -0
- package/src/RvfShare/RvfShare.tsx +40 -0
- package/src/RvfShare/index.tsx +1 -0
- package/src/RvfShareMenuButton/RvfShareMenuButton.tsx +27 -0
- package/src/RvfShareMenuButton/index.tsx +1 -0
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +137 -51
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +10 -2
- package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +9 -5
- package/src/RvfTopics/RvfTopics.tsx +21 -24
- package/src/icons/Automat/Automat.tsx +8 -0
- package/src/icons/Automat/index.tsx +1 -0
- package/src/icons/Automat/rvf_automat.svg +15 -0
- package/src/icons/Bike/Bike.tsx +8 -0
- package/src/icons/Bike/index.tsx +1 -0
- package/src/icons/Bike/rvf_shared_bike.svg +15 -0
- package/src/icons/Car/Car.tsx +8 -0
- package/src/icons/Car/index.tsx +1 -0
- package/src/icons/Car/rvf_shared_car.svg +16 -0
- package/src/icons/CargoBike/CargoBike.tsx +8 -0
- package/src/icons/CargoBike/index.tsx +1 -0
- package/src/icons/CargoBike/rvf_shared_cargo_bike.svg +16 -0
- package/src/icons/Copy/Copy.tsx +25 -0
- package/src/icons/Copy/index.tsx +1 -0
- package/src/icons/Doc/Doc.tsx +19 -0
- package/src/icons/Doc/doc.svg +7 -0
- package/src/icons/Doc/index.tsx +1 -0
- package/src/icons/Ebike/Ebike.tsx +8 -0
- package/src/icons/Ebike/index.tsx +1 -0
- package/src/icons/Ebike/rvf_shared_e-bike.svg +15 -0
- package/src/icons/Email/Email.tsx +19 -0
- package/src/icons/Email/email.svg +7 -0
- package/src/icons/Email/index.tsx +1 -0
- package/src/icons/FilePdf/FilePdf.tsx +19 -0
- package/src/icons/FilePdf/file-pdf.svg +7 -0
- package/src/icons/FilePdf/index.tsx +1 -0
- package/src/icons/Image/Image.tsx +24 -0
- package/src/icons/Image/index.tsx +1 -0
- package/src/icons/InPerson/InPerson.tsx +8 -0
- package/src/icons/InPerson/index.tsx +1 -0
- package/src/icons/InPerson/rvf_persoenlich.svg +17 -0
- package/src/icons/Minus/minus-grey.svg +7 -0
- package/src/icons/Ride/Ride.tsx +8 -0
- package/src/icons/Ride/index.tsx +1 -0
- package/src/icons/Ride/rvf_shared_ride.svg +15 -0
- package/src/icons/Scooter/Scooter.tsx +8 -0
- package/src/icons/Scooter/index.tsx +1 -0
- package/src/icons/Scooter/rvf_shared_scooter.svg +15 -0
- package/src/icons/Share/Share.tsx +24 -0
- package/src/icons/Share/index.tsx +1 -0
- package/src/icons/Stack/Stack.tsx +24 -0
- package/src/icons/Stack/index.tsx +1 -0
- package/src/icons/Video/Video.tsx +8 -0
- package/src/icons/Video/index.tsx +1 -0
- package/src/icons/Video/rvf_video.svg +19 -0
- package/src/icons/rvf_shared_ride_2.svg +12 -0
- package/src/utils/constants.ts +2 -0
- package/src/utils/createMobiDataBwWfsLayer.ts +28 -20
- package/src/utils/createSharedMobilityLayer.ts +44 -33
- package/src/utils/exportPdf.ts +11 -0
- package/src/utils/getAllLayers.ts +25 -0
- package/src/utils/hooks/useRvfContext.tsx +45 -0
- package/tailwind.config.mjs +32 -29
- package/src/FloatingMenu/FloatingMenu.tsx +0 -42
- package/src/FloatingMenu/index.tsx +0 -1
- package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +0 -19
- package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +0 -14
- package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +0 -27
- package/src/icons/Scooter/scooter.svg +0 -10
|
@@ -10,8 +10,15 @@ import {
|
|
|
10
10
|
RealtimeTrainId,
|
|
11
11
|
} from "mobility-toolbox-js/types";
|
|
12
12
|
import { Feature, Map as OlMap } from "ol";
|
|
13
|
+
import { Group } from "ol/layer";
|
|
13
14
|
import { memo } from "preact/compat";
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
useCallback,
|
|
17
|
+
useEffect,
|
|
18
|
+
useMemo,
|
|
19
|
+
useRef,
|
|
20
|
+
useState,
|
|
21
|
+
} from "preact/hooks";
|
|
15
22
|
|
|
16
23
|
import BaseLayer from "../BaseLayer";
|
|
17
24
|
import Copyright from "../Copyright";
|
|
@@ -26,12 +33,16 @@ import RvfExportMenu from "../RvfExportMenu";
|
|
|
26
33
|
import RvfExportMenuButton from "../RvfExportMenuButton";
|
|
27
34
|
import RvfFeatureDetails from "../RvfFeatureDetails";
|
|
28
35
|
import RvfFloatingMenu from "../RvfFloatingMenu";
|
|
36
|
+
import RvfLayerTreeButton from "../RvfLayerTreeButton";
|
|
29
37
|
// 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
|
|
30
38
|
import RvfLineNetworkPlanLayer from "../RvfLineNetworkPlanLayer";
|
|
31
39
|
import Modal from "../RvfModal";
|
|
40
|
+
import RvfOverlayHeader from "../RvfOverlayHeader";
|
|
32
41
|
import RvfPoisLayer from "../RvfPoisLayer";
|
|
33
42
|
import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
|
|
43
|
+
import RvfShare from "../RvfShare";
|
|
34
44
|
import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
|
|
45
|
+
import RvfShareMenuButton from "../RvfShareMenuButton";
|
|
35
46
|
import RvfTarifZonenLayer from "../RvfTarifZonenLayer";
|
|
36
47
|
import Topics from "../RvfTopics";
|
|
37
48
|
import RvfZoomButtons from "../RvfZoomButtons";
|
|
@@ -52,17 +63,24 @@ import MobilityEvent from "../utils/MobilityEvent";
|
|
|
52
63
|
// @ts-expect-error bad type definition
|
|
53
64
|
import style from "./index.css";
|
|
54
65
|
|
|
55
|
-
export type RvfMobilityMapProps = {} & MobilityMapProps;
|
|
66
|
+
export type RvfMobilityMapProps = { toolbar: string } & MobilityMapProps;
|
|
56
67
|
|
|
57
68
|
const bbox = RVF_EXTENT_3857.join(",");
|
|
58
69
|
|
|
59
70
|
const baseLayerProps = {
|
|
60
71
|
mapLibreOptions: {
|
|
61
|
-
maxCanvasSize: [20000, 20000], // remove 4096 limitations
|
|
72
|
+
maxCanvasSize: [20000, 20000] as [number, number], // remove 4096 limitations
|
|
62
73
|
preserveDrawingBuffer: true,
|
|
63
74
|
},
|
|
64
75
|
};
|
|
65
76
|
|
|
77
|
+
const styleProps = {
|
|
78
|
+
//fontSize: 16
|
|
79
|
+
};
|
|
80
|
+
const scrollableHandlerProps = {
|
|
81
|
+
style: { width: "calc(100% - 60px)" },
|
|
82
|
+
};
|
|
83
|
+
|
|
66
84
|
function RvfMobilityMap({
|
|
67
85
|
apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
|
|
68
86
|
baselayer = "de.rvf",
|
|
@@ -84,6 +102,7 @@ function RvfMobilityMap({
|
|
|
84
102
|
search = "false",
|
|
85
103
|
stopsurl = "https://api.geops.io/stops/v1/",
|
|
86
104
|
tenant = null,
|
|
105
|
+
toolbar = "true",
|
|
87
106
|
zoom = null,
|
|
88
107
|
}: RvfMobilityMapProps) {
|
|
89
108
|
const eventNodeRef = useRef<HTMLDivElement>();
|
|
@@ -98,9 +117,19 @@ function RvfMobilityMap({
|
|
|
98
117
|
const [stationId, setStationId] = useState<RealtimeStationId>();
|
|
99
118
|
const [trainId, setTrainId] = useState<RealtimeTrainId>();
|
|
100
119
|
const [isExportMenuOpen, setIsExportMenuOpen] = useState<boolean>(false);
|
|
120
|
+
const [isShareMenuOpen, setIsShareMenuOpen] = useState<boolean>(false);
|
|
101
121
|
const [selectedFeature, setSelectedFeature] = useState<Feature>();
|
|
102
122
|
const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
|
|
103
123
|
const [isLayerTreeOpen, setIsLayerTreeOpen] = useState<boolean>(false);
|
|
124
|
+
const [sellingPointsLayer, setSellingPointsLayer] =
|
|
125
|
+
useState<MaplibreStyleLayer>();
|
|
126
|
+
const [tarifZonenLayer, setTarifZonenLayer] = useState<MaplibreStyleLayer>();
|
|
127
|
+
const [poisLayer, setPoisLayer] = useState<MaplibreStyleLayer>();
|
|
128
|
+
const [lineNetworkPlanLayer, setLineNetworkPlanLayer] =
|
|
129
|
+
useState<MaplibreStyleLayer>();
|
|
130
|
+
const [sharedMobilityLayerGroup, setSharedMobilityLayerGroup] =
|
|
131
|
+
useState<Group>();
|
|
132
|
+
const [hasToolbar] = useState<boolean>(toolbar === "true");
|
|
104
133
|
|
|
105
134
|
// TODO: this should be removed. The parent application should be responsible to do this
|
|
106
135
|
// or we should find something that fit more usecases
|
|
@@ -201,6 +230,7 @@ function RvfMobilityMap({
|
|
|
201
230
|
realtimeurl,
|
|
202
231
|
search,
|
|
203
232
|
tenant,
|
|
233
|
+
toolbar,
|
|
204
234
|
zoom,
|
|
205
235
|
}),
|
|
206
236
|
);
|
|
@@ -208,6 +238,7 @@ function RvfMobilityMap({
|
|
|
208
238
|
baselayer,
|
|
209
239
|
center,
|
|
210
240
|
geolocation,
|
|
241
|
+
toolbar,
|
|
211
242
|
mapsurl,
|
|
212
243
|
maxzoom,
|
|
213
244
|
minzoom,
|
|
@@ -229,14 +260,129 @@ function RvfMobilityMap({
|
|
|
229
260
|
return {
|
|
230
261
|
isExportMenuOpen,
|
|
231
262
|
isLayerTreeOpen,
|
|
263
|
+
isShareMenuOpen,
|
|
264
|
+
lineNetworkPlanLayer,
|
|
265
|
+
poisLayer,
|
|
232
266
|
selectedFeature,
|
|
233
267
|
selectedFeatures,
|
|
268
|
+
sellingPointsLayer,
|
|
234
269
|
setIsExportMenuOpen,
|
|
235
270
|
setIsLayerTreeOpen,
|
|
271
|
+
setIsShareMenuOpen,
|
|
272
|
+
setLineNetworkPlanLayer,
|
|
273
|
+
setPoisLayer,
|
|
236
274
|
setSelectedFeature,
|
|
237
275
|
setSelectedFeatures,
|
|
276
|
+
setSellingPointsLayer,
|
|
277
|
+
setSharedMobilityLayerGroup,
|
|
278
|
+
setTarifZonenLayer,
|
|
279
|
+
sharedMobilityLayerGroup,
|
|
280
|
+
tarifZonenLayer,
|
|
281
|
+
};
|
|
282
|
+
}, [
|
|
283
|
+
isExportMenuOpen,
|
|
284
|
+
isLayerTreeOpen,
|
|
285
|
+
isShareMenuOpen,
|
|
286
|
+
lineNetworkPlanLayer,
|
|
287
|
+
poisLayer,
|
|
288
|
+
selectedFeature,
|
|
289
|
+
selectedFeatures,
|
|
290
|
+
sellingPointsLayer,
|
|
291
|
+
sharedMobilityLayerGroup,
|
|
292
|
+
tarifZonenLayer,
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const onLayerTreeMenuClick = useCallback(() => {
|
|
296
|
+
setIsLayerTreeOpen(!isLayerTreeOpen);
|
|
297
|
+
}, [isLayerTreeOpen]);
|
|
298
|
+
|
|
299
|
+
const onExportMenuClose = useCallback(() => {
|
|
300
|
+
setIsExportMenuOpen(false);
|
|
301
|
+
}, []);
|
|
302
|
+
|
|
303
|
+
const copyrightOptions = useMemo(() => {
|
|
304
|
+
return {
|
|
305
|
+
format: (copyrights) => {
|
|
306
|
+
const newCopyrights = [];
|
|
307
|
+
copyrights.forEach((copyright) => {
|
|
308
|
+
if (!/(sbb|rvf)/i.test(copyright)) {
|
|
309
|
+
newCopyrights.push(copyright);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
return newCopyrights.join(" | ");
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
}, []);
|
|
316
|
+
|
|
317
|
+
const stationsLayerProps = useMemo(() => {
|
|
318
|
+
return {
|
|
319
|
+
layersFilter: ({ metadata }) => {
|
|
320
|
+
return metadata?.["rvf.filter"] === "netzplan_stations";
|
|
321
|
+
},
|
|
238
322
|
};
|
|
239
|
-
}, [
|
|
323
|
+
}, []);
|
|
324
|
+
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
if (hasToolbar && isLayerTreeOpen) {
|
|
327
|
+
setIsExportMenuOpen(false);
|
|
328
|
+
setIsLayerTreeOpen(isLayerTreeOpen);
|
|
329
|
+
setSelectedFeature(null);
|
|
330
|
+
setTrainId(null);
|
|
331
|
+
setStationId(null);
|
|
332
|
+
setIsShareMenuOpen(false);
|
|
333
|
+
}
|
|
334
|
+
}, [isLayerTreeOpen, hasToolbar]);
|
|
335
|
+
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (hasToolbar && isExportMenuOpen) {
|
|
338
|
+
setIsLayerTreeOpen(false);
|
|
339
|
+
setIsExportMenuOpen(isExportMenuOpen);
|
|
340
|
+
setSelectedFeature(null);
|
|
341
|
+
setTrainId(null);
|
|
342
|
+
setIsShareMenuOpen(false);
|
|
343
|
+
setStationId(null);
|
|
344
|
+
}
|
|
345
|
+
}, [isExportMenuOpen, hasToolbar]);
|
|
346
|
+
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
if (hasToolbar && selectedFeature) {
|
|
349
|
+
setIsLayerTreeOpen(false);
|
|
350
|
+
setIsExportMenuOpen(false);
|
|
351
|
+
setTrainId(null);
|
|
352
|
+
setIsShareMenuOpen(false);
|
|
353
|
+
setStationId(null);
|
|
354
|
+
}
|
|
355
|
+
}, [selectedFeature, hasToolbar]);
|
|
356
|
+
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
if (hasToolbar && stationId) {
|
|
359
|
+
setIsLayerTreeOpen(false);
|
|
360
|
+
setIsExportMenuOpen(false);
|
|
361
|
+
setTrainId(null);
|
|
362
|
+
setIsShareMenuOpen(false);
|
|
363
|
+
setSelectedFeature(null);
|
|
364
|
+
}
|
|
365
|
+
}, [stationId, hasToolbar]);
|
|
366
|
+
|
|
367
|
+
useEffect(() => {
|
|
368
|
+
if (hasToolbar && trainId) {
|
|
369
|
+
setIsLayerTreeOpen(false);
|
|
370
|
+
setIsExportMenuOpen(false);
|
|
371
|
+
setStationId(null);
|
|
372
|
+
setIsShareMenuOpen(false);
|
|
373
|
+
setSelectedFeature(null);
|
|
374
|
+
}
|
|
375
|
+
}, [trainId, hasToolbar]);
|
|
376
|
+
|
|
377
|
+
useEffect(() => {
|
|
378
|
+
if (hasToolbar && isShareMenuOpen) {
|
|
379
|
+
setIsLayerTreeOpen(false);
|
|
380
|
+
setIsExportMenuOpen(false);
|
|
381
|
+
setStationId(null);
|
|
382
|
+
setTrainId(null);
|
|
383
|
+
setSelectedFeature(null);
|
|
384
|
+
}
|
|
385
|
+
}, [isShareMenuOpen, hasToolbar]);
|
|
240
386
|
|
|
241
387
|
return (
|
|
242
388
|
<I18nContext.Provider value={i18n}>
|
|
@@ -247,39 +393,43 @@ function RvfMobilityMap({
|
|
|
247
393
|
<div
|
|
248
394
|
className="relative size-full border font-sans @container/main"
|
|
249
395
|
ref={eventNodeRef}
|
|
250
|
-
style={
|
|
396
|
+
style={styleProps}
|
|
251
397
|
>
|
|
252
|
-
<div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
|
|
398
|
+
<div className="relative flex size-full flex-col text-base @lg/main:flex-row-reverse">
|
|
253
399
|
<Map className="relative flex-1 overflow-visible ">
|
|
254
|
-
<
|
|
255
|
-
isOpen={isLayerTreeOpen}
|
|
256
|
-
onClick={() => {
|
|
257
|
-
setIsLayerTreeOpen(!isLayerTreeOpen);
|
|
258
|
-
}}
|
|
259
|
-
title="Kartendaten"
|
|
260
|
-
>
|
|
261
|
-
<Topics className={"w-full px-2"} />
|
|
262
|
-
</RvfFloatingMenu>
|
|
263
|
-
<BaseLayer {...baseLayerProps} isNotInLayerTree />
|
|
400
|
+
<BaseLayer {...baseLayerProps} />
|
|
264
401
|
<SingleClickListener />
|
|
265
402
|
|
|
266
403
|
{realtime === "true" && <RealtimeLayer title="Echtzeit" />}
|
|
267
|
-
{
|
|
404
|
+
{
|
|
405
|
+
<StationsLayer
|
|
406
|
+
minZoom={10}
|
|
407
|
+
title="Haltestellen"
|
|
408
|
+
{...stationsLayerProps}
|
|
409
|
+
/>
|
|
410
|
+
}
|
|
268
411
|
{notification === "true" && <NotificationLayer />}
|
|
269
|
-
<RvfSellingPointsLayer title="Verkaufsstellen" />
|
|
270
|
-
<RvfLineNetworkPlanLayer
|
|
271
|
-
|
|
412
|
+
<RvfSellingPointsLayer isQueryable title="Verkaufsstellen" />
|
|
413
|
+
<RvfLineNetworkPlanLayer
|
|
414
|
+
isQueryable
|
|
415
|
+
minZoom={10}
|
|
416
|
+
title="Liniennetz"
|
|
417
|
+
/>
|
|
418
|
+
<RvfTarifZonenLayer isQueryable title="Tarifzonen" />
|
|
272
419
|
<RvfPoisLayer title="POIs" />
|
|
273
420
|
<RvfSharedMobilityLayerGroup title="Shared Mobility" />
|
|
274
421
|
|
|
275
422
|
<div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
|
|
276
423
|
<ScaleLine className="bg-slate-50/70" />
|
|
277
|
-
<Copyright
|
|
424
|
+
<Copyright
|
|
425
|
+
className="bg-slate-50/70"
|
|
426
|
+
options={copyrightOptions}
|
|
427
|
+
/>
|
|
278
428
|
</div>
|
|
279
429
|
|
|
280
430
|
<div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
|
|
281
431
|
{geolocation === "true" && <GeolocationButton />}
|
|
282
|
-
<RvfExportMenuButton />
|
|
432
|
+
{!hasToolbar && <RvfExportMenuButton />}
|
|
283
433
|
</div>
|
|
284
434
|
|
|
285
435
|
{search === "true" && (
|
|
@@ -291,31 +441,99 @@ function RvfMobilityMap({
|
|
|
291
441
|
<div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
|
|
292
442
|
<RvfZoomButtons />
|
|
293
443
|
</div>
|
|
294
|
-
</Map>
|
|
295
444
|
|
|
445
|
+
{!hasToolbar && (
|
|
446
|
+
<RvfFloatingMenu
|
|
447
|
+
isOpen={isLayerTreeOpen}
|
|
448
|
+
onClick={onLayerTreeMenuClick}
|
|
449
|
+
title="Kartendaten"
|
|
450
|
+
>
|
|
451
|
+
<Topics className={"w-full px-2"} />
|
|
452
|
+
</RvfFloatingMenu>
|
|
453
|
+
)}
|
|
454
|
+
</Map>
|
|
296
455
|
<Overlay
|
|
297
456
|
className={"z-50"}
|
|
298
|
-
ScrollableHandlerProps={
|
|
299
|
-
style: { width: "calc(100% - 60px)" },
|
|
300
|
-
}}
|
|
457
|
+
ScrollableHandlerProps={scrollableHandlerProps}
|
|
301
458
|
>
|
|
302
459
|
{realtime === "true" && trainId && (
|
|
303
|
-
|
|
460
|
+
<>
|
|
461
|
+
<RvfOverlayHeader
|
|
462
|
+
onClose={() => {
|
|
463
|
+
setTrainId(null);
|
|
464
|
+
}}
|
|
465
|
+
title="Train"
|
|
466
|
+
></RvfOverlayHeader>
|
|
467
|
+
<RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
|
|
468
|
+
</>
|
|
304
469
|
)}
|
|
305
470
|
{tenant && stationId && (
|
|
306
|
-
|
|
471
|
+
<>
|
|
472
|
+
<RvfOverlayHeader
|
|
473
|
+
onClose={() => {
|
|
474
|
+
setStationId(null);
|
|
475
|
+
}}
|
|
476
|
+
title="Station"
|
|
477
|
+
></RvfOverlayHeader>
|
|
478
|
+
<Station className="relative flex flex-col overflow-y-auto overflow-x-hidden p-2" />
|
|
479
|
+
</>
|
|
307
480
|
)}
|
|
308
481
|
{selectedFeature && (
|
|
309
|
-
|
|
482
|
+
<>
|
|
483
|
+
<RvfOverlayHeader
|
|
484
|
+
onClose={() => {
|
|
485
|
+
setSelectedFeature(null);
|
|
486
|
+
}}
|
|
487
|
+
title="Informations"
|
|
488
|
+
></RvfOverlayHeader>
|
|
489
|
+
<RvfFeatureDetails className="relative flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-2" />
|
|
490
|
+
</>
|
|
491
|
+
)}
|
|
492
|
+
{hasToolbar && isExportMenuOpen && (
|
|
493
|
+
<>
|
|
494
|
+
<RvfOverlayHeader
|
|
495
|
+
onClose={() => {
|
|
496
|
+
setIsExportMenuOpen(false);
|
|
497
|
+
}}
|
|
498
|
+
title="Export"
|
|
499
|
+
></RvfOverlayHeader>
|
|
500
|
+
<RvfExportMenu className="relative flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-2" />
|
|
501
|
+
</>
|
|
502
|
+
)}
|
|
503
|
+
{hasToolbar && isLayerTreeOpen && (
|
|
504
|
+
<>
|
|
505
|
+
<RvfOverlayHeader
|
|
506
|
+
onClose={() => {
|
|
507
|
+
setIsLayerTreeOpen(false);
|
|
508
|
+
}}
|
|
509
|
+
title="Layers"
|
|
510
|
+
></RvfOverlayHeader>
|
|
511
|
+
<Topics className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
|
|
512
|
+
</>
|
|
513
|
+
)}
|
|
514
|
+
{hasToolbar && isShareMenuOpen && (
|
|
515
|
+
<>
|
|
516
|
+
<RvfOverlayHeader
|
|
517
|
+
onClose={() => {
|
|
518
|
+
setIsLayerTreeOpen(false);
|
|
519
|
+
}}
|
|
520
|
+
title="Share"
|
|
521
|
+
></RvfOverlayHeader>
|
|
522
|
+
<RvfShare className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden p-2" />
|
|
523
|
+
</>
|
|
310
524
|
)}
|
|
311
525
|
</Overlay>
|
|
312
526
|
|
|
313
|
-
{
|
|
314
|
-
<
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
>
|
|
527
|
+
{hasToolbar && (
|
|
528
|
+
<div className={"overflow-x-hidden border-r"}>
|
|
529
|
+
<RvfLayerTreeButton className={"border-none"} />
|
|
530
|
+
<RvfExportMenuButton className={"border-none"} />
|
|
531
|
+
<RvfShareMenuButton className={"border-none"} />
|
|
532
|
+
</div>
|
|
533
|
+
)}
|
|
534
|
+
|
|
535
|
+
{!hasToolbar && isExportMenuOpen && (
|
|
536
|
+
<Modal onClose={onExportMenuClose}>
|
|
319
537
|
<RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
|
|
320
538
|
</Modal>
|
|
321
539
|
)}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import { memo } from "preact/compat";
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
5
|
+
|
|
6
|
+
import Cancel from "../icons/Cancel";
|
|
7
|
+
import RvfIconButton from "../RvfIconButton";
|
|
8
|
+
|
|
9
|
+
export type RvfOverlayHeaderProps = {
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
title?: string;
|
|
12
|
+
} & JSX.HTMLAttributes<HTMLDivElement> &
|
|
13
|
+
PreactDOMAttributes;
|
|
14
|
+
|
|
15
|
+
function RvfOverlayHeader({
|
|
16
|
+
children,
|
|
17
|
+
className,
|
|
18
|
+
onClose,
|
|
19
|
+
title,
|
|
20
|
+
...props
|
|
21
|
+
}: RvfOverlayHeaderProps) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className={twMerge(
|
|
25
|
+
"flex flex-row items-center justify-between gap-2 p-2 border-b" +
|
|
26
|
+
(className || ""),
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children || <span className={"font-bold"}>{title}</span>}
|
|
31
|
+
{onClose && (
|
|
32
|
+
<RvfIconButton className={"!size-[32px] border-none"} onClick={onClose}>
|
|
33
|
+
<Cancel />
|
|
34
|
+
</RvfIconButton>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default memo(RvfOverlayHeader);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfOverlayHeader";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import RvfInputCopy from "../RvfInputCopy";
|
|
4
|
+
|
|
5
|
+
function RvfPermalink({
|
|
6
|
+
...props
|
|
7
|
+
}: JSX.HTMLAttributes<HTMLDivElement> & PreactDOMAttributes) {
|
|
8
|
+
return (
|
|
9
|
+
<div {...props}>
|
|
10
|
+
<RvfInputCopy value={window?.location.href} />
|
|
11
|
+
<p className="py-2">
|
|
12
|
+
Sie können auch den Link aus der Adresszeile des Browsers kopieren.
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default RvfPermalink;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfPermalink";
|
|
@@ -4,9 +4,11 @@ import { memo } from "preact/compat";
|
|
|
4
4
|
import { useEffect, useMemo } from "preact/hooks";
|
|
5
5
|
|
|
6
6
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
7
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
7
8
|
|
|
8
9
|
function RvfPoisLayer(props: MaplibreStyleLayerOptions) {
|
|
9
10
|
const { baseLayer, map } = useMapContext();
|
|
11
|
+
const { setPoisLayer } = useRvfContext();
|
|
10
12
|
|
|
11
13
|
const layer = useMemo(() => {
|
|
12
14
|
if (!baseLayer) {
|
|
@@ -22,6 +24,10 @@ function RvfPoisLayer(props: MaplibreStyleLayerOptions) {
|
|
|
22
24
|
});
|
|
23
25
|
}, [baseLayer, props]);
|
|
24
26
|
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setPoisLayer(layer);
|
|
29
|
+
}, [layer, setPoisLayer]);
|
|
30
|
+
|
|
25
31
|
useEffect(() => {
|
|
26
32
|
if (!map || !layer) {
|
|
27
33
|
return;
|
|
@@ -6,7 +6,7 @@ export type RvfRadioButtonProps =
|
|
|
6
6
|
function RvfRadioButton(props: RvfRadioButtonProps) {
|
|
7
7
|
return (
|
|
8
8
|
<input
|
|
9
|
-
className="peer mr-2
|
|
9
|
+
className="peer mr-2 size-[20px] rounded-full border-2 border-grey accent-red"
|
|
10
10
|
{...props}
|
|
11
11
|
type="radio"
|
|
12
12
|
/>
|
|
@@ -9,12 +9,12 @@ function RvfSelect({ children, className, onChange }: RvfSelectProps) {
|
|
|
9
9
|
return (
|
|
10
10
|
<div className="relative flex items-center text-grey">
|
|
11
11
|
<select
|
|
12
|
-
className={`
|
|
12
|
+
className={`h-[32px] cursor-pointer appearance-none rounded-[12px] border border-current bg-white px-[13px] text-[16px] leading-[22px] disabled:cursor-default @sm/main:h-[36px] @md/main:h-[40px] ${className}`}
|
|
13
13
|
onChange={onChange}
|
|
14
14
|
>
|
|
15
15
|
{children}
|
|
16
16
|
</select>
|
|
17
|
-
<ArrowDown className="pointer-events-none absolute right-
|
|
17
|
+
<ArrowDown className="pointer-events-none absolute right-[8px]" />
|
|
18
18
|
</div>
|
|
19
19
|
);
|
|
20
20
|
}
|
|
@@ -3,26 +3,45 @@ import { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/Maplibr
|
|
|
3
3
|
import { memo } from "preact/compat";
|
|
4
4
|
import { useEffect, useMemo } from "preact/hooks";
|
|
5
5
|
|
|
6
|
+
import Automat from "../icons/Automat";
|
|
7
|
+
import InPerson from "../icons/InPerson";
|
|
8
|
+
import Video from "../icons/Video";
|
|
6
9
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
10
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
7
11
|
|
|
8
12
|
function RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
|
|
13
|
+
const { title } = props;
|
|
9
14
|
const { baseLayer, map } = useMapContext();
|
|
15
|
+
const { setSellingPointsLayer } = useRvfContext();
|
|
10
16
|
|
|
11
17
|
const layer = useMemo(() => {
|
|
12
18
|
if (!baseLayer) {
|
|
13
19
|
return null;
|
|
14
20
|
}
|
|
15
21
|
return new MaplibreStyleLayer({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
(source === "rvf" && sourceLayer === "selling_points")
|
|
20
|
-
);
|
|
22
|
+
isQueryable: true,
|
|
23
|
+
layersFilter: ({ metadata }) => {
|
|
24
|
+
return metadata?.["general.filter"] === "selling_points";
|
|
21
25
|
},
|
|
22
26
|
maplibreLayer: baseLayer,
|
|
23
27
|
...(props || {}),
|
|
28
|
+
|
|
29
|
+
title: (
|
|
30
|
+
<div className="flex items-center justify-between gap-2">
|
|
31
|
+
{title}
|
|
32
|
+
<span className="flex items-center">
|
|
33
|
+
<Automat />
|
|
34
|
+
<InPerson />
|
|
35
|
+
<Video />
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
),
|
|
24
39
|
});
|
|
25
|
-
}, [baseLayer, props]);
|
|
40
|
+
}, [baseLayer, props, title]);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setSellingPointsLayer(layer);
|
|
44
|
+
}, [layer, setSellingPointsLayer]);
|
|
26
45
|
|
|
27
46
|
useEffect(() => {
|
|
28
47
|
if (!map || !layer) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import { useState } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
import Cancel from "../../icons/Cancel";
|
|
6
|
+
import Share from "../../icons/Share";
|
|
7
|
+
import RvfIconButton from "../../RvfIconButton";
|
|
8
|
+
import RvfInputCopy from "../../RvfInputCopy";
|
|
9
|
+
|
|
10
|
+
function RvfPermalinkButton({
|
|
11
|
+
className,
|
|
12
|
+
...props
|
|
13
|
+
}: JSX.ButtonHTMLAttributes<HTMLButtonElement> & PreactDOMAttributes) {
|
|
14
|
+
const [showTooltip, setShowTooltip] = useState(null);
|
|
15
|
+
const [positionTooltip, setPositionTooltip] = useState<DOMRect>();
|
|
16
|
+
|
|
17
|
+
const handleIconClick = (event) => {
|
|
18
|
+
setPositionTooltip(event.currentTarget.getBoundingClientRect());
|
|
19
|
+
setShowTooltip(!showTooltip);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<RvfIconButton
|
|
25
|
+
className={"border-none " + className}
|
|
26
|
+
onClick={handleIconClick}
|
|
27
|
+
selected={showTooltip}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
<Share />
|
|
31
|
+
</RvfIconButton>
|
|
32
|
+
|
|
33
|
+
{showTooltip && positionTooltip && (
|
|
34
|
+
<div
|
|
35
|
+
className="fixed rounded-lg border border-grey bg-white p-2 shadow-lg"
|
|
36
|
+
style={{
|
|
37
|
+
left: positionTooltip.left + 40,
|
|
38
|
+
top: positionTooltip.top - 10,
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<div className="pr-4">
|
|
42
|
+
<RvfIconButton
|
|
43
|
+
className="absolute right-[-5px] top-[-5px] border-none bg-transparent p-0"
|
|
44
|
+
onClick={handleIconClick}
|
|
45
|
+
>
|
|
46
|
+
<Cancel height="18px" width="18px" />
|
|
47
|
+
</RvfIconButton>
|
|
48
|
+
<RvfInputCopy />
|
|
49
|
+
<p className="py-2">
|
|
50
|
+
Sie können auch den Link aus der Adresszeile des Browsers
|
|
51
|
+
kopieren.
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="absolute -left-1.5 top-4 size-2.5 rotate-45 border border-r-0 border-t-0 border-grey bg-white"></div>
|
|
55
|
+
</div>
|
|
56
|
+
)}
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default RvfPermalinkButton;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfPermalinkButton";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import CanvasSaveButton from "react-spatial/components/CanvasSaveButton";
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
5
|
+
|
|
6
|
+
import Email from "../icons/Email";
|
|
7
|
+
import Image from "../icons/Image";
|
|
8
|
+
import RvfButton from "../RvfButton";
|
|
9
|
+
import RvfPermalink from "../RvfPermalink";
|
|
10
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
11
|
+
|
|
12
|
+
function RvfShare({
|
|
13
|
+
className,
|
|
14
|
+
...props
|
|
15
|
+
}: JSX.HTMLAttributes<HTMLDivElement> & PreactDOMAttributes) {
|
|
16
|
+
const { map } = useMapContext();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className={twMerge("flex flex-col gap-2 " + className)} {...props}>
|
|
20
|
+
<a
|
|
21
|
+
className="flex items-center gap-2"
|
|
22
|
+
href={`mailto:?subject=Karte&body=${window?.location.href}`}
|
|
23
|
+
>
|
|
24
|
+
<RvfButton>
|
|
25
|
+
<Email />
|
|
26
|
+
<span>Email senden</span>
|
|
27
|
+
</RvfButton>
|
|
28
|
+
</a>
|
|
29
|
+
<CanvasSaveButton map={map}>
|
|
30
|
+
<RvfButton className={"w-fit"}>
|
|
31
|
+
<Image />
|
|
32
|
+
<span>Bild speichern</span>
|
|
33
|
+
</RvfButton>
|
|
34
|
+
</CanvasSaveButton>
|
|
35
|
+
<RvfPermalink />
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default RvfShare;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfShare";
|