@geops/rvf-mobility-web-component 0.1.10 → 0.1.11

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 (66) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/docutils.js +198 -0
  3. package/index.html +48 -217
  4. package/index.js +627 -87
  5. package/input.css +11 -1
  6. package/jest-setup.js +3 -2
  7. package/package.json +4 -3
  8. package/scripts/dev.mjs +1 -1
  9. package/search.html +38 -69
  10. package/src/GeolocationButton/GeolocationButton.tsx +6 -5
  11. package/src/LayerTree/LayerTree.tsx +44 -0
  12. package/src/LayerTree/TreeItem/TreeItem.tsx +145 -0
  13. package/src/LayerTree/TreeItem/index.tsx +1 -0
  14. package/src/LayerTree/TreeItemContainer/TreeItemContainer.tsx +16 -0
  15. package/src/LayerTree/TreeItemContainer/index.tsx +1 -0
  16. package/src/LayerTree/index.tsx +1 -0
  17. package/src/LayerTree/layersTreeContext.ts +4 -0
  18. package/src/LayerTree/layersTreeReducer.ts +156 -0
  19. package/src/Map/Map.tsx +1 -0
  20. package/src/MobilityMap/MobilityMap.tsx +10 -9
  21. package/src/MobilityMap/index.css +0 -13
  22. package/src/RealtimeLayer/RealtimeLayer.tsx +1 -1
  23. package/src/RvfButton/RvfButton.tsx +28 -21
  24. package/src/RvfExportMenu/RvfExportMenu.tsx +95 -0
  25. package/src/RvfExportMenu/index.tsx +1 -0
  26. package/src/RvfExportMenuButton/RvfExportMenuButton.tsx +27 -0
  27. package/src/RvfExportMenuButton/index.tsx +1 -0
  28. package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +29 -0
  29. package/src/RvfFeatureDetails/index.tsx +1 -0
  30. package/src/RvfIconButton/RvfIconButton.tsx +35 -0
  31. package/src/RvfIconButton/index.tsx +1 -0
  32. package/src/RvfMobilityMap/RvfMobilityMap.tsx +117 -83
  33. package/src/RvfMobilityMap/index.css +0 -13
  34. package/src/RvfModal/RvfModal.tsx +52 -0
  35. package/src/RvfModal/index.tsx +1 -0
  36. package/src/RvfPoisLayer/RvfPoisLayer.tsx +39 -0
  37. package/src/RvfPoisLayer/index.tsx +1 -0
  38. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +88 -0
  39. package/src/RvfSharedMobilityLayerGroup/index.tsx +1 -0
  40. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +137 -0
  41. package/src/RvfSingleClickListener/index.tsx +1 -0
  42. package/src/RvfZoomButtons/RvfZoomButtons.tsx +36 -29
  43. package/src/Search/Search.tsx +11 -9
  44. package/src/SingleClickListener/index.tsx +1 -1
  45. package/src/StationsLayer/StationsLayer.tsx +0 -1
  46. package/src/StopsSearch/StopsSearch.tsx +38 -6
  47. package/src/TopicMenu/TopicMenu.tsx +143 -0
  48. package/src/TopicMenu/index.tsx +1 -0
  49. package/src/icons/Cancel/Cancel.tsx +21 -0
  50. package/src/icons/Cancel/cancel.svg +7 -0
  51. package/src/icons/Cancel/index.tsx +1 -0
  52. package/src/icons/Download/Download.tsx +20 -0
  53. package/src/icons/Download/download.svg +15 -0
  54. package/src/icons/Download/index.tsx +1 -0
  55. package/src/icons/Elevator/Elevator.tsx +1 -1
  56. package/src/icons/Menu/Menu.tsx +32 -0
  57. package/src/icons/Menu/index.tsx +1 -0
  58. package/src/icons/Menu/menu.svg +9 -0
  59. package/src/utils/constants.ts +9 -0
  60. package/src/utils/createMobiDataBwWfsLayer.ts +120 -0
  61. package/src/utils/exportPdf.ts +677 -0
  62. package/src/utils/hooks/useRvfContext.tsx +37 -0
  63. package/src/utils/hooks/useUpdatePermalink.tsx +2 -9
  64. package/tailwind.config.mjs +41 -0
  65. package/src/RvfSharedMobilityLayer/RvfSharedMobilityLayer.tsx +0 -147
  66. package/src/RvfSharedMobilityLayer/index.tsx +0 -1
@@ -11,7 +11,7 @@ import {
11
11
  } from "mobility-toolbox-js/types";
12
12
  import { Map as OlMap } from "ol";
13
13
  import { memo } from "preact/compat";
14
- import { useEffect, useMemo, useState } from "preact/hooks";
14
+ import { useEffect, useMemo, useRef, useState } from "preact/hooks";
15
15
 
16
16
  import BaseLayer from "../BaseLayer";
17
17
  import Copyright from "../Copyright";
@@ -84,6 +84,7 @@ function MobilityMap({
84
84
  tenant = null,
85
85
  zoom = "13",
86
86
  }: MobilityMapProps) {
87
+ const eventNodeRef = useRef<HTMLDivElement>();
87
88
  const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
88
89
  const [isFollowing, setIsFollowing] = useState(false);
89
90
  const [isTracking, setIsTracking] = useState(false);
@@ -97,7 +98,7 @@ function MobilityMap({
97
98
 
98
99
  // TODO: this should be removed. The parent application should be responsible to do this
99
100
  // or we should find something that fit more usecases
100
- const { x, y, z } = useUpdatePermalink(map, permalink === "true");
101
+ useUpdatePermalink(map, permalink === "true");
101
102
 
102
103
  const mapContextValue = useMemo(() => {
103
104
  return {
@@ -175,10 +176,10 @@ function MobilityMap({
175
176
  ]);
176
177
 
177
178
  useEffect(() => {
178
- dispatchEvent(
179
+ eventNodeRef.current?.dispatchEvent(
179
180
  new MobilityEvent<MobilityMapProps>("mwc:attribute", {
180
181
  baselayer,
181
- center: x && y ? `${x},${y}` : center,
182
+ center,
182
183
  extent,
183
184
  geolocation,
184
185
  mapsurl,
@@ -194,7 +195,7 @@ function MobilityMap({
194
195
  realtimeurl,
195
196
  search,
196
197
  tenant,
197
- zoom: z || zoom,
198
+ zoom,
198
199
  }),
199
200
  );
200
201
  }, [
@@ -216,9 +217,6 @@ function MobilityMap({
216
217
  search,
217
218
  tenant,
218
219
  zoom,
219
- x,
220
- y,
221
- z,
222
220
  ]);
223
221
 
224
222
  return (
@@ -226,7 +224,10 @@ function MobilityMap({
226
224
  <style>{tailwind}</style>
227
225
  <style>{style}</style>
228
226
  <MapContext.Provider value={mapContextValue}>
229
- <div className="relative size-full border font-sans @container/main">
227
+ <div
228
+ className="relative size-full border font-sans @container/main"
229
+ ref={eventNodeRef}
230
+ >
230
231
  <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
231
232
  <Map className="relative flex-1 overflow-visible ">
232
233
  <BaseLayer />
@@ -1,13 +0,0 @@
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
- }
@@ -16,7 +16,7 @@ import useMapContext from "../utils/hooks/useMapContext";
16
16
 
17
17
  const TRACKING_ZOOM = 16;
18
18
 
19
- export type RealtimeLayerProps = RealtimeLayerOptions;
19
+ export type RealtimeLayerProps = RealtimeLayerOptions & Record<string, unknown>;
20
20
 
21
21
  function RealtimeLayer(props: RealtimeLayerProps) {
22
22
  const {
@@ -1,36 +1,43 @@
1
1
  import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
- import { memo, SVGProps } from "preact/compat";
3
+ import { memo, useMemo } from "preact/compat";
4
4
 
5
- export type RvfButtonProps = ButtonProps &
6
- JSX.ButtonHTMLAttributes<HTMLButtonElement> &
5
+ export type RvfButtonProps = {
6
+ selected?: boolean;
7
+ theme?: "primary" | "secondary";
8
+ } & JSX.ButtonHTMLAttributes<HTMLButtonElement> &
7
9
  PreactDOMAttributes;
8
10
 
9
- interface ButtonProps {
10
- Icon?: (props: SVGProps<SVGSVGElement>) => preact.JSX.Element;
11
- theme?: "primary" | "secondary";
12
- }
11
+ const baseClasses =
12
+ "flex h-8 md:h-9 lg:h-10 px-5 py-1.75 max-h-button items-center justify-center rounded-full border";
13
+
14
+ export const themes = {
15
+ primary: {
16
+ classes:
17
+ "border-red bg-red text-white disabled:bg-lightgrey disabled:border-lightgrey hover:bg-darkred hover:border-darkred active:bg-lightred active:border-lightred",
18
+ selectedClasses: "bg-darkred border-darkred",
19
+ },
20
+ secondary: {
21
+ classes:
22
+ "border border-current bg-white text-grey hover:text-red disabled:text-lightgrey active:text-lightred",
23
+ selectedClasses: "text-red",
24
+ },
25
+ };
13
26
 
14
27
  function RvfButton({
15
28
  children,
16
29
  className,
17
- disabled,
18
- Icon,
19
- onClick,
20
- theme,
30
+ selected = false,
31
+ theme = "secondary",
32
+ ...props
21
33
  }: RvfButtonProps) {
22
- const baseClasses =
23
- "flex h-8 w-8 md:h-9 md:w-9 lg:w-10 lg:h-10 p-1.75 max-w-button max-h-button items-center justify-center rounded-full border";
24
- const themeClasses =
25
- theme === "primary"
26
- ? "border-red bg-red text-white"
27
- : "border-grey bg-white text-grey";
28
-
29
- const classes = `${baseClasses} ${themeClasses} ${className || ""}`;
34
+ const classes = useMemo(() => {
35
+ return `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`;
36
+ }, [className, selected, theme]);
30
37
 
31
38
  return (
32
- <button className={classes} disabled={disabled} onClick={onClick}>
33
- {children || <Icon height={"100%"} width={"100%"} />}
39
+ <button className={classes} {...props}>
40
+ {children}
34
41
  </button>
35
42
  );
36
43
  }
@@ -0,0 +1,95 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo, useId, useState } from "preact/compat";
4
+
5
+ import Cancel from "../icons/Cancel";
6
+ import RvfButton from "../RvfButton";
7
+ import RvfIconButton from "../RvfIconButton";
8
+ import exportPdf from "../utils/exportPdf";
9
+ import useMapContext from "../utils/hooks/useMapContext";
10
+ import useRvfContext from "../utils/hooks/useRvfContext";
11
+
12
+ export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
13
+ PreactDOMAttributes;
14
+
15
+ const formats = ["A4", "A1", "A3", "A0"];
16
+
17
+ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
18
+ const { setIsExportMenuOpen } = useRvfContext();
19
+ const { map } = useMapContext();
20
+ const [useMaxExtent, setUseMaxExtent] = useState(false);
21
+ const [format, setFormat] = useState<string>(formats[0]);
22
+ const checkboxId = useId();
23
+ const selectId = useId();
24
+ const [isExporting, setIsExporting] = useState(false);
25
+ const [isExportingError, setIsExportingError] = useState(false);
26
+ return (
27
+ <div {...props}>
28
+ <div className={"flex h-full flex-col gap-2 p-2"}>
29
+ {/* <!-- Header --> */}
30
+ <div className={"flex flex-row items-center justify-between gap-2"}>
31
+ <h1>Export </h1>
32
+ <RvfIconButton
33
+ onClick={() => {
34
+ setIsExportMenuOpen(false);
35
+ }}
36
+ >
37
+ <Cancel />
38
+ </RvfIconButton>
39
+ </div>
40
+ {/* <!-- Content --> */}
41
+ <div className="flex flex-1 flex-col gap-2">
42
+ <div className={"flex gap-2"}>
43
+ <input
44
+ checked={useMaxExtent}
45
+ id={checkboxId}
46
+ onChange={() => {
47
+ setUseMaxExtent(!useMaxExtent);
48
+ }}
49
+ type="checkbox"
50
+ />
51
+ <label htmlFor={checkboxId}>Ganze Region exportieren</label>
52
+ </div>
53
+ <div className={"flex gap-2"}>
54
+ <label htmlFor={selectId}>Format:</label>
55
+ <select
56
+ className={"w-24"}
57
+ id={selectId}
58
+ onChange={(evt) => {
59
+ setFormat((evt.target as HTMLSelectElement).value);
60
+ }}
61
+ value={format}
62
+ >
63
+ {formats.map((format) => {
64
+ return <option key={format}>{format}</option>;
65
+ })}
66
+ </select>
67
+ </div>
68
+ </div>
69
+ {/* <!-- Footer --> */}
70
+ <div>
71
+ <RvfButton
72
+ disabled={isExporting}
73
+ onClick={async () => {
74
+ setIsExportingError(false);
75
+ setIsExporting(true);
76
+ const result = await exportPdf(map, { format }, { useMaxExtent });
77
+ setTimeout(() => {
78
+ setIsExporting(false);
79
+ setIsExportingError(!result);
80
+ }, 1000);
81
+ }}
82
+ >
83
+ {isExporting
84
+ ? "Exporting..."
85
+ : isExportingError
86
+ ? "Error"
87
+ : "Download"}
88
+ </RvfButton>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ );
93
+ }
94
+
95
+ export default memo(RvfExportMenu);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfExportMenu";
@@ -0,0 +1,27 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+ import { useCallback } from "preact/hooks";
5
+
6
+ import Download from "../icons/Download";
7
+ import RvfIconButton from "../RvfIconButton";
8
+ import useRvfContext from "../utils/hooks/useRvfContext";
9
+
10
+ export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function RvfExportMenuButton({ ...props }: RvfExportMenuButtonProps) {
14
+ const { isExportMenuOpen, setIsExportMenuOpen } = useRvfContext();
15
+
16
+ const onClick = useCallback(() => {
17
+ setIsExportMenuOpen(!isExportMenuOpen);
18
+ }, [isExportMenuOpen, setIsExportMenuOpen]);
19
+
20
+ return (
21
+ <RvfIconButton {...props} onClick={onClick} selected={isExportMenuOpen}>
22
+ <Download />
23
+ </RvfIconButton>
24
+ );
25
+ }
26
+
27
+ export default memo(RvfExportMenuButton);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfExportMenuButton";
@@ -0,0 +1,29 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo } from "preact/compat";
4
+
5
+ import useRvfContext from "../utils/hooks/useRvfContext";
6
+
7
+ export type RvfFeatureDetailsProps = JSX.HTMLAttributes<HTMLDivElement> &
8
+ PreactDOMAttributes;
9
+
10
+ function RvfFeatureDetails(props: RvfFeatureDetailsProps) {
11
+ const { selectedFeature } = useRvfContext();
12
+
13
+ return (
14
+ <div {...props}>
15
+ {Object.entries(selectedFeature.getProperties()).map(([key, value]) => {
16
+ return (
17
+ <div className="flex gap-2" key={key}>
18
+ <span>
19
+ <b>{key}:</b>
20
+ </span>
21
+ <div>{value}</div>
22
+ </div>
23
+ );
24
+ })}
25
+ </div>
26
+ );
27
+ }
28
+
29
+ export default memo(RvfFeatureDetails);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfFeatureDetails";
@@ -0,0 +1,35 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { memo, SVGProps, useMemo } from "preact/compat";
4
+
5
+ import { RvfButtonProps, themes } from "../RvfButton/RvfButton";
6
+
7
+ export type RvfIconButtonProps = {
8
+ Icon?: (props: SVGProps<SVGSVGElement>) => preact.JSX.Element;
9
+ } & JSX.ButtonHTMLAttributes<HTMLButtonElement> &
10
+ PreactDOMAttributes &
11
+ RvfButtonProps;
12
+
13
+ const baseClasses =
14
+ "flex h-8 w-8 md:h-9 md:w-9 lg:w-10 lg:h-10 p-1.75 max-w-button max-h-button items-center justify-center rounded-full border";
15
+
16
+ function RvfIconButton({
17
+ children,
18
+ className,
19
+ Icon,
20
+ selected = false,
21
+ theme = "secondary",
22
+ ...props
23
+ }: RvfIconButtonProps) {
24
+ const classes = useMemo(() => {
25
+ return `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`;
26
+ }, [className, selected, theme]);
27
+
28
+ return (
29
+ <button className={classes} {...props}>
30
+ {children || <Icon height={"100%"} width={"100%"} />}
31
+ </button>
32
+ );
33
+ }
34
+
35
+ export default memo(RvfIconButton);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfIconButton";
@@ -9,47 +9,57 @@ import {
9
9
  RealtimeStopSequence,
10
10
  RealtimeTrainId,
11
11
  } from "mobility-toolbox-js/types";
12
- import { Map as OlMap } from "ol";
13
- import { transformExtent } from "ol/proj";
12
+ import { Feature, Map as OlMap } from "ol";
14
13
  import { memo } from "preact/compat";
15
- import { useEffect, useMemo, useState } from "preact/hooks";
14
+ import { useEffect, useMemo, useRef, useState } from "preact/hooks";
16
15
 
17
16
  import BaseLayer from "../BaseLayer";
18
17
  import Copyright from "../Copyright";
19
18
  import GeolocationButton from "../GeolocationButton";
19
+ import Cancel from "../icons/Cancel";
20
+ import Menu from "../icons/Menu";
20
21
  import Map from "../Map";
21
22
  import { MobilityMapProps } from "../MobilityMap/MobilityMap";
22
23
  import NotificationLayer from "../NotificationLayer";
23
24
  import Overlay from "../Overlay";
24
25
  import RealtimeLayer from "../RealtimeLayer";
25
26
  import RouteSchedule from "../RouteSchedule";
26
- import RvfSharedMobilityLayer from "../RvfSharedMobilityLayer";
27
+ import RvfExportMenu from "../RvfExportMenu";
28
+ import RvfExportMenuButton from "../RvfExportMenuButton";
29
+ import RvfFeatureDetails from "../RvfFeatureDetails";
30
+ // 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
31
+ import RvfIconButton from "../RvfIconButton";
32
+ import Modal from "../RvfModal";
33
+ import RvfPoisLayer from "../RvfPoisLayer";
34
+ import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
27
35
  import RvfZoomButtons from "../RvfZoomButtons";
28
36
  import ScaleLine from "../ScaleLine";
29
37
  import Search from "../Search";
30
- import SingleClickListener from "../SingleClickListener/SingleClickListener";
38
+ import SingleClickListener from "../SingleClickListener";
31
39
  import Station from "../Station";
32
40
  import StationsLayer from "../StationsLayer";
33
41
  // @ts-expect-error bad type definition
34
42
  import tailwind from "../style.css";
43
+ import TopicMenu from "../TopicMenu";
44
+ import { RVF_EXTENT_3857 } from "../utils/constants";
35
45
  import { I18nContext } from "../utils/hooks/useI18n";
36
46
  import { MapContext } from "../utils/hooks/useMapContext";
47
+ import { RvfContext } from "../utils/hooks/useRvfContext";
37
48
  import useUpdatePermalink from "../utils/hooks/useUpdatePermalink";
38
49
  import i18n from "../utils/i18n";
39
50
  import MobilityEvent from "../utils/MobilityEvent";
40
51
  // @ts-expect-error bad type definition
41
52
  import style from "./index.css";
42
- // 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
43
53
 
44
54
  export type RvfMobilityMapProps = {} & MobilityMapProps;
45
55
 
46
- const rvfExtent = [7.5, 47.7, 8.45, 48.4];
47
- const rvfExtentTransformed = transformExtent(
48
- rvfExtent,
49
- "EPSG:4326",
50
- "EPSG:3857",
51
- );
52
- const bbox = rvfExtentTransformed.join(",");
56
+ const bbox = RVF_EXTENT_3857.join(",");
57
+
58
+ const baseLayerProps = {
59
+ mapLibreOptions: {
60
+ preserveDrawingBuffer: true,
61
+ },
62
+ };
53
63
 
54
64
  function RvfMobilityMap({
55
65
  apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
@@ -59,7 +69,7 @@ function RvfMobilityMap({
59
69
  geolocation = "true",
60
70
  mapsurl = "https://maps.geops.io",
61
71
  maxextent = bbox,
62
- maxzoom = null,
72
+ maxzoom = "20",
63
73
  minzoom = null,
64
74
  mots = null,
65
75
  notification = "true",
@@ -74,6 +84,7 @@ function RvfMobilityMap({
74
84
  tenant = null,
75
85
  zoom = null,
76
86
  }: RvfMobilityMapProps) {
87
+ const eventNodeRef = useRef<HTMLDivElement>();
77
88
  const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
78
89
  const [isFollowing, setIsFollowing] = useState(false);
79
90
  const [isTracking, setIsTracking] = useState(false);
@@ -84,10 +95,14 @@ function RvfMobilityMap({
84
95
  const [map, setMap] = useState<OlMap>();
85
96
  const [stationId, setStationId] = useState<RealtimeStationId>();
86
97
  const [trainId, setTrainId] = useState<RealtimeTrainId>();
98
+ const [isExportMenuOpen, setIsExportMenuOpen] = useState<boolean>(false);
99
+ const [selectedFeature, setSelectedFeature] = useState<Feature>();
100
+ const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
101
+ const [isLayerTreeOpen, setIsLayerTreeOpen] = useState(false);
87
102
 
88
103
  // TODO: this should be removed. The parent application should be responsible to do this
89
104
  // or we should find something that fit more usecases
90
- const { x, y, z } = useUpdatePermalink(map, permalink === "true");
105
+ useUpdatePermalink(map, permalink === "true");
91
106
 
92
107
  const mapContextValue = useMemo(() => {
93
108
  return {
@@ -165,10 +180,10 @@ function RvfMobilityMap({
165
180
  ]);
166
181
 
167
182
  useEffect(() => {
168
- dispatchEvent(
183
+ eventNodeRef.current?.dispatchEvent(
169
184
  new MobilityEvent<RvfMobilityMapProps>("mwc:attribute", {
170
185
  baselayer,
171
- center: x && y ? `${x},${y}` : center,
186
+ center,
172
187
  extent,
173
188
  geolocation,
174
189
  mapsurl,
@@ -184,7 +199,7 @@ function RvfMobilityMap({
184
199
  realtimeurl,
185
200
  search,
186
201
  tenant,
187
- zoom: z || zoom,
202
+ zoom,
188
203
  }),
189
204
  );
190
205
  }, [
@@ -204,85 +219,104 @@ function RvfMobilityMap({
204
219
  search,
205
220
  tenant,
206
221
  zoom,
207
- x,
208
- y,
209
- z,
210
222
  extent,
211
223
  maxextent,
212
224
  ]);
213
225
 
226
+ const rvfContextValue = useMemo(() => {
227
+ return {
228
+ isExportMenuOpen,
229
+ selectedFeature,
230
+ selectedFeatures,
231
+ setIsExportMenuOpen,
232
+ setSelectedFeature,
233
+ setSelectedFeatures,
234
+ };
235
+ }, [isExportMenuOpen, selectedFeature, selectedFeatures]);
236
+
214
237
  return (
215
238
  <I18nContext.Provider value={i18n}>
216
239
  <style>{tailwind}</style>
217
240
  <style>{style}</style>
218
241
  <MapContext.Provider value={mapContextValue}>
219
- <div className="relative size-full border font-sans @container/main">
220
- <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
221
- <Map className="relative flex-1 overflow-visible ">
222
- <BaseLayer />
223
- <SingleClickListener />
224
- {realtime === "true" && <RealtimeLayer />}
225
- {tenant && <StationsLayer />}
226
- {notification === "true" && <NotificationLayer />}
227
- <RvfSharedMobilityLayer
228
- color="red"
229
- name="MobiData-BW:sharing_stations_bicycle"
230
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_bicycle/ows"
231
- />
232
- <RvfSharedMobilityLayer
233
- color="blue"
234
- name="MobiData-BW:sharing_stations_cargo_bicycle"
235
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_cargo_bicycle/ows"
236
- />
237
- <RvfSharedMobilityLayer
238
- color="green"
239
- name="MobiData-BW:sharing_stations_car"
240
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_car/ows"
241
- />
242
- <RvfSharedMobilityLayer
243
- color="orange"
244
- name="MobiData-BW:sharing_stations_scooters_standing"
245
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_stations_scooters_standing/ows"
246
- />
247
- <RvfSharedMobilityLayer
248
- color="orange"
249
- minZoom={14.999}
250
- name="MobiData-BW:sharing_vehicles"
251
- url="https://api.mobidata-bw.de/geoserver/MobiData-BW/sharing_vehicles/ows"
252
- />
242
+ <RvfContext.Provider value={rvfContextValue}>
243
+ <div
244
+ className="relative size-full border font-sans @container/main"
245
+ ref={eventNodeRef}
246
+ >
247
+ <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
248
+ <Map className="relative flex-1 overflow-visible ">
249
+ <BaseLayer {...baseLayerProps} isNotInLayerTree />
250
+ <SingleClickListener />
251
+
252
+ {realtime === "true" && <RealtimeLayer title="Realtime data" />}
253
+ {tenant && <StationsLayer />}
254
+ {notification === "true" && <NotificationLayer />}
255
+ {/* <RvfLnpLayer />
256
+ <RvfVerkaufStellenLayer />
257
+ <RvfTarifZonenLayer /> */}
258
+ <RvfPoisLayer title="POIs" />
253
259
 
254
- <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
255
- <ScaleLine className="bg-slate-50/70" />
256
- <Copyright className="bg-slate-50/70" />
257
- </div>
258
- <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
259
- {geolocation === "true" && <GeolocationButton />}
260
- </div>
261
- {search === "true" && (
262
- <div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
263
- <Search />
260
+ <RvfSharedMobilityLayerGroup title="Shared Mobility" />
261
+
262
+ <div className="absolute left-2 top-2 z-10">
263
+ <RvfIconButton
264
+ onClick={() => {
265
+ return setIsLayerTreeOpen(!isLayerTreeOpen);
266
+ }}
267
+ selected={isLayerTreeOpen}
268
+ >
269
+ {isLayerTreeOpen ? <Cancel /> : <Menu />}
270
+ </RvfIconButton>
271
+ {isLayerTreeOpen && <TopicMenu map={map} />}
264
272
  </div>
265
- )}
266
- <div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
267
- <RvfZoomButtons />
268
- </div>
269
- </Map>
273
+ <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
274
+ <ScaleLine className="bg-slate-50/70" />
275
+ <Copyright className="bg-slate-50/70" />
276
+ </div>
277
+ <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
278
+ {geolocation === "true" && <GeolocationButton />}
279
+ </div>
280
+ {search === "true" && (
281
+ <div className="absolute left-2 right-12 top-2 z-10 flex max-h-[90%] min-w-64 max-w-96 flex-col">
282
+ <Search />
283
+ </div>
284
+ )}
285
+ <div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
286
+ <RvfExportMenuButton />
287
+ <RvfZoomButtons />
288
+ </div>
289
+ </Map>
270
290
 
271
- <Overlay
272
- className={"z-50"}
273
- ScrollableHandlerProps={{
274
- style: { width: "calc(100% - 60px)" },
275
- }}
276
- >
277
- {realtime === "true" && trainId && (
278
- <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
279
- )}
280
- {tenant && stationId && (
281
- <Station className="relative overflow-y-auto overflow-x-hidden" />
291
+ <Overlay
292
+ className={"z-50"}
293
+ ScrollableHandlerProps={{
294
+ style: { width: "calc(100% - 60px)" },
295
+ }}
296
+ >
297
+ {realtime === "true" && trainId && (
298
+ <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
299
+ )}
300
+ {tenant && stationId && (
301
+ <Station className="relative overflow-y-auto overflow-x-hidden" />
302
+ )}
303
+ {selectedFeature && (
304
+ <RvfFeatureDetails className="relative overflow-y-auto overflow-x-hidden" />
305
+ )}
306
+ </Overlay>
307
+
308
+ {isExportMenuOpen && (
309
+ <Modal
310
+ onClose={() => {
311
+ setIsExportMenuOpen(false);
312
+ }}
313
+ >
314
+ <RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
315
+ </Modal>
282
316
  )}
283
- </Overlay>
317
+ </div>
284
318
  </div>
285
- </div>
319
+ </RvfContext.Provider>
286
320
  </MapContext.Provider>
287
321
  </I18nContext.Provider>
288
322
  );