@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.
Files changed (100) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/index.html +1 -1
  3. package/index.js +340 -219
  4. package/input.css +2 -8
  5. package/package.json +18 -16
  6. package/scripts/build.mjs +0 -1
  7. package/scripts/dev.mjs +2 -1
  8. package/src/RouteSchedule/RouteSchedule.tsx +3 -1
  9. package/src/RouteStop/RouteStop.tsx +1 -1
  10. package/src/RvfButton/RvfButton.tsx +6 -3
  11. package/src/RvfCheckbox/RvfCheckbox.tsx +10 -3
  12. package/src/RvfExportMenu/RvfExportMenu.tsx +62 -68
  13. package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +3 -3
  14. package/src/RvfIconButton/RvfIconButton.tsx +5 -2
  15. package/src/RvfInputCopy/RvfInputCopy.tsx +57 -0
  16. package/src/RvfInputCopy/index.tsx +1 -0
  17. package/src/RvfLayerTree/RvfLayerTree.tsx +5 -9
  18. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +39 -9
  19. package/src/RvfLayerTree/layersTreeReducer.ts +1 -0
  20. package/src/RvfLayerTreeButton/RvfLayerTreeButton.tsx +27 -0
  21. package/src/RvfLayerTreeButton/index.tsx +1 -0
  22. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +9 -5
  23. package/src/RvfMobilityMap/RvfMobilityMap.tsx +253 -35
  24. package/src/RvfOverlayHeader/RvfOverlayHeader.tsx +40 -0
  25. package/src/RvfOverlayHeader/index.tsx +1 -0
  26. package/src/RvfPermalink/RvfPermalink.tsx +18 -0
  27. package/src/RvfPermalink/index.tsx +1 -0
  28. package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -0
  29. package/src/RvfRadioButton/RvfRadioButton.tsx +1 -1
  30. package/src/RvfSelect/RvfSelect.tsx +2 -2
  31. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +25 -6
  32. package/src/RvfShare/RvfPermalinkButton/RvfPermalinkButton.tsx +61 -0
  33. package/src/RvfShare/RvfPermalinkButton/index.tsx +1 -0
  34. package/src/RvfShare/RvfShare.tsx +40 -0
  35. package/src/RvfShare/index.tsx +1 -0
  36. package/src/RvfShareMenuButton/RvfShareMenuButton.tsx +27 -0
  37. package/src/RvfShareMenuButton/index.tsx +1 -0
  38. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +137 -51
  39. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +10 -2
  40. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +9 -5
  41. package/src/RvfTopics/RvfTopics.tsx +21 -24
  42. package/src/icons/Automat/Automat.tsx +8 -0
  43. package/src/icons/Automat/index.tsx +1 -0
  44. package/src/icons/Automat/rvf_automat.svg +15 -0
  45. package/src/icons/Bike/Bike.tsx +8 -0
  46. package/src/icons/Bike/index.tsx +1 -0
  47. package/src/icons/Bike/rvf_shared_bike.svg +15 -0
  48. package/src/icons/Car/Car.tsx +8 -0
  49. package/src/icons/Car/index.tsx +1 -0
  50. package/src/icons/Car/rvf_shared_car.svg +16 -0
  51. package/src/icons/CargoBike/CargoBike.tsx +8 -0
  52. package/src/icons/CargoBike/index.tsx +1 -0
  53. package/src/icons/CargoBike/rvf_shared_cargo_bike.svg +16 -0
  54. package/src/icons/Copy/Copy.tsx +25 -0
  55. package/src/icons/Copy/index.tsx +1 -0
  56. package/src/icons/Doc/Doc.tsx +19 -0
  57. package/src/icons/Doc/doc.svg +7 -0
  58. package/src/icons/Doc/index.tsx +1 -0
  59. package/src/icons/Ebike/Ebike.tsx +8 -0
  60. package/src/icons/Ebike/index.tsx +1 -0
  61. package/src/icons/Ebike/rvf_shared_e-bike.svg +15 -0
  62. package/src/icons/Email/Email.tsx +19 -0
  63. package/src/icons/Email/email.svg +7 -0
  64. package/src/icons/Email/index.tsx +1 -0
  65. package/src/icons/FilePdf/FilePdf.tsx +19 -0
  66. package/src/icons/FilePdf/file-pdf.svg +7 -0
  67. package/src/icons/FilePdf/index.tsx +1 -0
  68. package/src/icons/Image/Image.tsx +24 -0
  69. package/src/icons/Image/index.tsx +1 -0
  70. package/src/icons/InPerson/InPerson.tsx +8 -0
  71. package/src/icons/InPerson/index.tsx +1 -0
  72. package/src/icons/InPerson/rvf_persoenlich.svg +17 -0
  73. package/src/icons/Minus/minus-grey.svg +7 -0
  74. package/src/icons/Ride/Ride.tsx +8 -0
  75. package/src/icons/Ride/index.tsx +1 -0
  76. package/src/icons/Ride/rvf_shared_ride.svg +15 -0
  77. package/src/icons/Scooter/Scooter.tsx +8 -0
  78. package/src/icons/Scooter/index.tsx +1 -0
  79. package/src/icons/Scooter/rvf_shared_scooter.svg +15 -0
  80. package/src/icons/Share/Share.tsx +24 -0
  81. package/src/icons/Share/index.tsx +1 -0
  82. package/src/icons/Stack/Stack.tsx +24 -0
  83. package/src/icons/Stack/index.tsx +1 -0
  84. package/src/icons/Video/Video.tsx +8 -0
  85. package/src/icons/Video/index.tsx +1 -0
  86. package/src/icons/Video/rvf_video.svg +19 -0
  87. package/src/icons/rvf_shared_ride_2.svg +12 -0
  88. package/src/utils/constants.ts +2 -0
  89. package/src/utils/createMobiDataBwWfsLayer.ts +28 -20
  90. package/src/utils/createSharedMobilityLayer.ts +44 -33
  91. package/src/utils/exportPdf.ts +11 -0
  92. package/src/utils/getAllLayers.ts +25 -0
  93. package/src/utils/hooks/useRvfContext.tsx +45 -0
  94. package/tailwind.config.mjs +32 -29
  95. package/src/FloatingMenu/FloatingMenu.tsx +0 -42
  96. package/src/FloatingMenu/index.tsx +0 -1
  97. package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +0 -19
  98. package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +0 -14
  99. package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +0 -27
  100. 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 { useEffect, useMemo, useRef, useState } from "preact/hooks";
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&notificationat=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("&nbsp;|&nbsp;");
313
+ },
314
+ };
315
+ }, []);
316
+
317
+ const stationsLayerProps = useMemo(() => {
318
+ return {
319
+ layersFilter: ({ metadata }) => {
320
+ return metadata?.["rvf.filter"] === "netzplan_stations";
321
+ },
238
322
  };
239
- }, [isExportMenuOpen, selectedFeature, selectedFeatures, isLayerTreeOpen]);
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={{ fontSize: 16 }}
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
- <RvfFloatingMenu
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
- {tenant && <StationsLayer />}
404
+ {
405
+ <StationsLayer
406
+ minZoom={10}
407
+ title="Haltestellen"
408
+ {...stationsLayerProps}
409
+ />
410
+ }
268
411
  {notification === "true" && <NotificationLayer />}
269
- <RvfSellingPointsLayer title="Verkaufsstellen" />
270
- <RvfLineNetworkPlanLayer title="Liniennetz" />
271
- <RvfTarifZonenLayer title="Tarifzonen" />
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 className="bg-slate-50/70" />
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
- <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
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
- <Station className="relative overflow-y-auto overflow-x-hidden" />
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
- <RvfFeatureDetails className="relative overflow-y-auto overflow-x-hidden" />
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
- {isExportMenuOpen && (
314
- <Modal
315
- onClose={() => {
316
- setIsExportMenuOpen(false);
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 h-inputControl w-inputControl rounded-full border-2 border-grey accent-red"
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={`min-w-12 appearance-none rounded-xl border border-grey p-2 text-base focus:outline-none ${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-1" />
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
- layersFilter: ({ metadata, source, "source-layer": sourceLayer }) => {
17
- return (
18
- metadata?.["rvf.filter"] === "selling_points" ||
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";