@geops/rvf-mobility-web-component 0.1.12 → 0.1.13

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 (35) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/index.html +1 -1
  3. package/index.js +192 -176
  4. package/input.css +2 -8
  5. package/package.json +14 -14
  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 +2 -2
  11. package/src/RvfCheckbox/RvfCheckbox.tsx +2 -1
  12. package/src/RvfExportMenu/RvfExportMenu.tsx +25 -13
  13. package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +2 -2
  14. package/src/RvfIconButton/RvfIconButton.tsx +1 -1
  15. package/src/RvfLayerTree/RvfLayerTree.tsx +5 -9
  16. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +19 -9
  17. package/src/RvfLayerTree/layersTreeReducer.ts +1 -0
  18. package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +7 -0
  19. package/src/RvfMobilityMap/RvfMobilityMap.tsx +84 -24
  20. package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -0
  21. package/src/RvfRadioButton/RvfRadioButton.tsx +1 -1
  22. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +9 -5
  23. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +87 -50
  24. package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +10 -2
  25. package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +7 -0
  26. package/src/RvfTopics/RvfTopics.tsx +21 -24
  27. package/src/utils/constants.ts +2 -0
  28. package/src/utils/createMobiDataBwWfsLayer.ts +28 -20
  29. package/src/utils/createSharedMobilityLayer.ts +24 -13
  30. package/src/utils/exportPdf.ts +11 -0
  31. package/src/utils/getAllLayers.ts +25 -0
  32. package/src/utils/hooks/useRvfContext.tsx +33 -0
  33. package/tailwind.config.mjs +30 -30
  34. package/src/FloatingMenu/FloatingMenu.tsx +0 -42
  35. package/src/FloatingMenu/index.tsx +0 -1
package/input.css CHANGED
@@ -35,14 +35,8 @@ html {
35
35
  h4 {
36
36
  @apply text-lg;
37
37
  }
38
- button {
38
+ /* button {
39
39
  @apply text-button font-semibold;
40
- }
41
- }
42
-
43
- .button-map {
44
- @apply bg-blue-500 text-white;
45
- background: lightgray;
46
- z-index: 5;
40
+ } */
47
41
  }
48
42
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@geops/rvf-mobility-web-component",
3
3
  "license": "UNLICENSED",
4
4
  "description": "Web components for rvf in the domains of mobility and logistics.",
5
- "version": "0.1.12",
5
+ "version": "0.1.13",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
@@ -11,7 +11,7 @@
11
11
  "maplibre-gl": "^4.7.1",
12
12
  "mobility-toolbox-js": "3.1.0",
13
13
  "ol": "^10.3.1",
14
- "preact": "^10.25.1",
14
+ "preact": "^10.25.4",
15
15
  "preact-custom-element": "^4.3.0",
16
16
  "react": "npm:@preact/compat@^18.3.1",
17
17
  "react-dom": "npm:@preact/compat@^18.3.1",
@@ -19,24 +19,24 @@
19
19
  "rosetta": "^1.1.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@commitlint/cli": "^19.6.0",
22
+ "@commitlint/cli": "^19.6.1",
23
23
  "@commitlint/config-conventional": "^19.6.0",
24
- "@eslint/js": "^9.16.0",
24
+ "@eslint/js": "^9.17.0",
25
25
  "@tailwindcss/container-queries": "^0.1.1",
26
26
  "@testing-library/preact": "^3.2.4",
27
- "@types/geojson": "^7946.0.14",
27
+ "@types/geojson": "^7946.0.15",
28
28
  "@types/jest": "^29.5.14",
29
29
  "@types/preact-custom-element": "^4.0.4",
30
- "concurrently": "^9.1.0",
31
- "esbuild": "^0.24.0",
30
+ "concurrently": "^9.1.2",
31
+ "esbuild": "^0.24.2",
32
32
  "esbuild-sass-plugin": "^3.3.1",
33
- "eslint": "^9.16.0",
33
+ "eslint": "^9.17.0",
34
34
  "eslint-config-prettier": "9.1.0",
35
35
  "eslint-plugin-jsx-a11y": "^6.10.2",
36
- "eslint-plugin-perfectionist": "^4.2.0",
36
+ "eslint-plugin-perfectionist": "^4.5.0",
37
37
  "eslint-plugin-prettier": "^5.2.1",
38
- "eslint-plugin-react": "^7.37.2",
39
- "eslint-plugin-react-hooks": "^5.1.0-rc.0",
38
+ "eslint-plugin-react": "^7.37.3",
39
+ "eslint-plugin-react-hooks": "^5.1.0",
40
40
  "eslint-plugin-tailwindcss": "^3.17.5",
41
41
  "fixpack": "^4.0.0",
42
42
  "generact": "^0.4.0",
@@ -46,13 +46,13 @@
46
46
  "jest-environment-jsdom": "^29.7.0",
47
47
  "jest-preset-preact": "^4.1.1",
48
48
  "next": "15.0.3",
49
- "preact-render-to-string": "^6.5.11",
49
+ "preact-render-to-string": "^6.5.12",
50
50
  "prettier": "^3.4.2",
51
51
  "standard-version": "^9.5.0",
52
- "tailwindcss": "^3.4.16",
52
+ "tailwindcss": "^3.4.17",
53
53
  "ts-jest": "^29.2.5",
54
54
  "typescript": "^5.7.2",
55
- "typescript-eslint": "^8.17.0"
55
+ "typescript-eslint": "^8.19.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "yarn build:css && yarn build:js",
package/scripts/build.mjs CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/no-extraneous-dependencies */
2
1
  import * as esbuild from "esbuild";
3
2
  import { sassPlugin } from "esbuild-sass-plugin";
4
3
 
package/scripts/dev.mjs CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable import/no-extraneous-dependencies */
2
1
  import * as esbuild from "esbuild";
3
2
  import { sassPlugin } from "esbuild-sass-plugin";
4
3
 
@@ -20,6 +19,8 @@ const { host, port } = await ctx.serve({
20
19
  });
21
20
 
22
21
  await ctx.watch();
22
+
23
+ // eslint-disable-next-line no-undef
23
24
  console.log(
24
25
  `watching... and running at ${
25
26
  host === "0.0.0.0" ? "http://localhost" : host
@@ -24,8 +24,10 @@ function RouteSchedule(props: RouteScheduleProps) {
24
24
  }
25
25
  const nextStation = elt.querySelector("[data-station-passed=false]");
26
26
  if (nextStation) {
27
- nextStation.scrollIntoView({
27
+ // We use scrollTo avoid scrolling the entire window.
28
+ (nextStation.parentNode as Element).scrollTo({
28
29
  behavior: "smooth",
30
+ top: (nextStation as HTMLElement).offsetTop || 0,
29
31
  });
30
32
  }
31
33
  clearInterval(interval);
@@ -110,7 +110,7 @@ function RouteStop({
110
110
  <RouteStopTime className="ml-4 flex w-10 shrink-0 flex-col justify-center text-xs" />
111
111
  <RouteStopDelay className="flex w-8 shrink-0 flex-col justify-center text-[0.6rem]" />
112
112
  <RouteStopProgress className="relative flex w-8 shrink-0 items-center" />
113
- <RouteStopStation className="flex grow flex-col items-start justify-center pr-2 text-sm font-medium" />
113
+ <RouteStopStation className="flex grow flex-col items-start justify-center pr-2" />
114
114
  </>
115
115
  )}
116
116
  </button>
@@ -9,12 +9,12 @@ export type RvfButtonProps = {
9
9
  PreactDOMAttributes;
10
10
 
11
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";
12
+ "flex text-[14px] @sm/main:text-[16px] @md/main:text-[18px] h-[32px] @sm/main:h-[36px] @md/main:h-[40px] px-[22px] @sm/main:px-[22.5px] @md/main:px-[27.5px] pt-[7px] pb-[5px] @sm/main:pt-[8px] @sm/main:pb-[6px] @md/main:pt-[8.5px] @md/main:pb-[6.5px] items-center justify-center rounded-full border font-semibold text-button";
13
13
 
14
14
  export const themes = {
15
15
  primary: {
16
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",
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
18
  selectedClasses: "bg-darkred border-darkred",
19
19
  },
20
20
  secondary: {
@@ -10,7 +10,8 @@ export type RvfCheckboxProps = {} & JSX.InputHTMLAttributes<HTMLInputElement>;
10
10
  function RvfCheckbox({ className, ...props }: RvfCheckboxProps) {
11
11
  return (
12
12
  <input
13
- className={`box-border size-[20px] cursor-pointer appearance-none rounded border-2 border-grey bg-white bg-contain bg-center text-grey disabled:cursor-default disabled:border-lightgrey ${className}`}
13
+ className={`
14
+ box-border size-[20px] flex-none cursor-pointer appearance-none rounded border-2 border-grey bg-white bg-contain bg-center text-grey disabled:cursor-default disabled:border-lightgrey ${className}`}
14
15
  style={{
15
16
  backgroundImage:
16
17
  props.checked && !props.disabled ? `url('` + ok + `')` : "",
@@ -7,14 +7,16 @@ import RvfButton from "../RvfButton";
7
7
  import RvfCheckbox from "../RvfCheckbox";
8
8
  import RvfIconButton from "../RvfIconButton";
9
9
  import RvfSelect from "../RvfSelect";
10
+ import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
10
11
  import exportPdf from "../utils/exportPdf";
12
+ import getAllLayers from "../utils/getAllLayers";
11
13
  import useMapContext from "../utils/hooks/useMapContext";
12
14
  import useRvfContext from "../utils/hooks/useRvfContext";
13
15
 
14
16
  export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
15
17
  PreactDOMAttributes;
16
18
 
17
- const formats = ["A4", "A1", "A3", "A0"];
19
+ const formats = ["A4", "A3", "A1", "A0"];
18
20
 
19
21
  function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
20
22
  const { setIsExportMenuOpen } = useRvfContext();
@@ -30,8 +32,9 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
30
32
  <div className={"flex h-full flex-col gap-2 p-2"}>
31
33
  {/* <!-- Header --> */}
32
34
  <div className={"flex flex-row items-center justify-between gap-2"}>
33
- <h1>Export </h1>
35
+ <span className={"font-bold"}>Drucken </span>
34
36
  <RvfIconButton
37
+ className={"!size-[32px]"}
35
38
  onClick={() => {
36
39
  setIsExportMenuOpen(false);
37
40
  }}
@@ -41,15 +44,7 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
41
44
  </div>
42
45
  {/* <!-- Content --> */}
43
46
  <div className="flex flex-1 flex-col gap-2">
44
- <div className={"flex items-center gap-2"}>
45
- {/* <input
46
- checked={useMaxExtent}
47
- id={checkboxId}
48
- onChange={() => {
49
- setUseMaxExtent(!useMaxExtent);
50
- }}
51
- type="checkbox"
52
- /> */}
47
+ <div className={"flex flex-wrap items-center gap-2"}>
53
48
  <RvfCheckbox
54
49
  checked={useMaxExtent}
55
50
  id={checkboxId}
@@ -59,7 +54,7 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
59
54
  />
60
55
  <label htmlFor={checkboxId}>Ganze Region exportieren</label>
61
56
  </div>
62
- <div className={"flex items-center gap-2"}>
57
+ <div className={"flex flex-wrap items-center gap-2"}>
63
58
  <label htmlFor={selectId}>Format:</label>
64
59
  <RvfSelect
65
60
  className={"w-24"}
@@ -81,7 +76,24 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
81
76
  onClick={async () => {
82
77
  setIsExportingError(false);
83
78
  setIsExporting(true);
84
- const result = await exportPdf(map, { format }, { useMaxExtent });
79
+ const result = await exportPdf(
80
+ map,
81
+ { format },
82
+ {
83
+ onAfter: (map, layers) => {
84
+ getAllLayers(layers).forEach((layer) => {
85
+ layer.set(LAYER_PROP_IS_EXPORTING, false);
86
+ });
87
+ },
88
+
89
+ onBefore: (map, layers) => {
90
+ getAllLayers(layers).forEach((layer) => {
91
+ layer.set(LAYER_PROP_IS_EXPORTING, true);
92
+ });
93
+ },
94
+ useMaxExtent,
95
+ },
96
+ );
85
97
  setTimeout(() => {
86
98
  setIsExporting(false);
87
99
  setIsExportingError(!result);
@@ -24,9 +24,9 @@ function RvfFloatingMenu({
24
24
 
25
25
  return (
26
26
  <div className="pointer-events-none absolute bottom-8 left-2 top-2 z-10 flex flex-col overflow-hidden rounded-lg">
27
- <div className="pointer-events-auto max-h-full rounded-lg border bg-white shadow-lg medium:w-largeMenu large:w-mediumMenu">
27
+ <div className="pointer-events-auto max-h-full w-48 rounded-lg border border-grey bg-white shadow-lg @lg/main:w-52">
28
28
  <button
29
- className="flex w-full items-center justify-between px-2 py-1.5 "
29
+ className="flex w-full items-center justify-between px-2 py-1.5 font-bold"
30
30
  onClick={onClick}
31
31
  >
32
32
  {title} {isOpen ? <ArrowDown /> : <ArrowUp />}
@@ -11,7 +11,7 @@ export type RvfIconButtonProps = {
11
11
  RvfButtonProps;
12
12
 
13
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";
14
+ "flex size-[32px] @sm/main:size-[36px] @md/main:size-[40px] p-[7px] items-center justify-center rounded-full border";
15
15
 
16
16
  function RvfIconButton({
17
17
  children,
@@ -21,18 +21,14 @@ function RvfLayerTree({ layers, ...props }: RvfLayerTreeProps) {
21
21
  dispatch({ payload: layers, type: "INIT" });
22
22
  }, [layers]);
23
23
 
24
- const renderedLayers = layers.map((item, idx) => {
25
- return (
26
- <div className="w-full" key={idx}>
27
- <TreeItem className="w-full" {...item} />
28
- </div>
29
- );
30
- });
31
-
32
24
  return (
33
25
  <LayersTreeContext.Provider value={tree}>
34
26
  <LayersTreeDispatchContext.Provider value={dispatch}>
35
- <div {...props}>{renderedLayers}</div>
27
+ <div {...props}>
28
+ {layers.map((item) => {
29
+ return <TreeItem className="w-full" key={item.id} {...item} />;
30
+ })}
31
+ </div>
36
32
  </LayersTreeDispatchContext.Provider>
37
33
  </LayersTreeContext.Provider>
38
34
  );
@@ -76,13 +76,21 @@ function TreeItem({
76
76
  });
77
77
  };
78
78
 
79
- const renderedLayers = childItems.map((item, idx) => {
80
- return <TreeItem key={idx} {...item} />;
81
- });
79
+ const renderedLayers = childItems
80
+ .filter(({ title }) => {
81
+ return !!title;
82
+ })
83
+ .map((item, idx) => {
84
+ return <TreeItem key={idx} {...item} />;
85
+ });
86
+
87
+ if (!title) {
88
+ return null;
89
+ }
82
90
 
83
91
  return (
84
92
  <div>
85
- <div className="flex items-center gap-2 border-b py-2">
93
+ <div className="flex items-center gap-2 border-b py-2 pr-1">
86
94
  {selectionType === SelectionType.RADIO ? (
87
95
  <RvfRadioButton
88
96
  checked={isControlChecked}
@@ -97,14 +105,14 @@ function TreeItem({
97
105
  />
98
106
  )}
99
107
  <label
100
- className={`cursor-pointer`}
101
- htmlFor={childItems.length > 0 ? buttonId : inputId}
108
+ className={`flex-1 cursor-pointer`}
109
+ htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
102
110
  >
103
111
  {title}
104
112
  </label>
105
- {childItems.length > 0 && (
113
+ {renderedLayers.length > 0 && (
106
114
  <button
107
- className={`flex cursor-pointer items-center gap-2 font-normal`}
115
+ className={`flex cursor-pointer items-center gap-2`}
108
116
  id={buttonId}
109
117
  onClick={handleItemClick}
110
118
  >
@@ -112,7 +120,9 @@ function TreeItem({
112
120
  </button>
113
121
  )}
114
122
  </div>
115
- {isContainerVisible && <div className="pl-6">{renderedLayers}</div>}
123
+ {isContainerVisible && renderedLayers.length > 0 && (
124
+ <div className="pl-6">{renderedLayers}</div>
125
+ )}
116
126
  </div>
117
127
  );
118
128
  }
@@ -107,6 +107,7 @@ const updateRadioChildNodes = (parent) => {
107
107
  const updateCheckboxChildNodes = (parent) => {
108
108
  for (const child of parent.childItems) {
109
109
  child.isControlChecked = parent.isControlChecked;
110
+ child.layer.setVisible(child.isControlChecked);
110
111
 
111
112
  if (child.childItems.length) {
112
113
  updateChildNodes(child);
@@ -4,15 +4,18 @@ 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 RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
9
10
  const { baseLayer, map } = useMapContext();
11
+ const { setLineNetworkPlanLayer } = useRvfContext();
10
12
 
11
13
  const layer = useMemo(() => {
12
14
  if (!baseLayer) {
13
15
  return null;
14
16
  }
15
17
  return new MaplibreStyleLayer({
18
+ isQueryable: true,
16
19
  layersFilter: ({ metadata, source }) => {
17
20
  return (
18
21
  metadata?.["rvf.filter"] === "netowrk_plans" ||
@@ -25,6 +28,10 @@ function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
25
28
  });
26
29
  }, [baseLayer, props]);
27
30
 
31
+ useEffect(() => {
32
+ setLineNetworkPlanLayer(layer);
33
+ }, [layer, setLineNetworkPlanLayer]);
34
+
28
35
  useEffect(() => {
29
36
  if (!map || !layer) {
30
37
  return;
@@ -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";
@@ -58,11 +65,18 @@ const bbox = RVF_EXTENT_3857.join(",");
58
65
 
59
66
  const baseLayerProps = {
60
67
  mapLibreOptions: {
61
- maxCanvasSize: [20000, 20000], // remove 4096 limitations
68
+ maxCanvasSize: [20000, 20000] as [number, number], // remove 4096 limitations
62
69
  preserveDrawingBuffer: true,
63
70
  },
64
71
  };
65
72
 
73
+ const styleProps = {
74
+ //fontSize: 16
75
+ };
76
+ const scrollableHandlerProps = {
77
+ style: { width: "calc(100% - 60px)" },
78
+ };
79
+
66
80
  function RvfMobilityMap({
67
81
  apikey = "5cc87b12d7c5370001c1d655820abcc37dfd4d968d7bab5b2a74a935",
68
82
  baselayer = "de.rvf",
@@ -101,6 +115,14 @@ function RvfMobilityMap({
101
115
  const [selectedFeature, setSelectedFeature] = useState<Feature>();
102
116
  const [selectedFeatures, setSelectedFeatures] = useState<Feature[]>();
103
117
  const [isLayerTreeOpen, setIsLayerTreeOpen] = useState<boolean>(false);
118
+ const [sellingPointsLayer, setSellingPointsLayer] =
119
+ useState<MaplibreStyleLayer>();
120
+ const [tarifZonenLayer, setTarifZonenLayer] = useState<MaplibreStyleLayer>();
121
+ const [poisLayer, setPoisLayer] = useState<MaplibreStyleLayer>();
122
+ const [lineNetworkPlanLayer, setLineNetworkPlanLayer] =
123
+ useState<MaplibreStyleLayer>();
124
+ const [sharedMobilityLayerGroup, setSharedMobilityLayerGroup] =
125
+ useState<Group>();
104
126
 
105
127
  // TODO: this should be removed. The parent application should be responsible to do this
106
128
  // or we should find something that fit more usecases
@@ -229,14 +251,56 @@ function RvfMobilityMap({
229
251
  return {
230
252
  isExportMenuOpen,
231
253
  isLayerTreeOpen,
254
+ lineNetworkPlanLayer,
255
+ poisLayer,
232
256
  selectedFeature,
233
257
  selectedFeatures,
258
+ sellingPointsLayer,
234
259
  setIsExportMenuOpen,
235
260
  setIsLayerTreeOpen,
261
+ setLineNetworkPlanLayer,
262
+ setPoisLayer,
236
263
  setSelectedFeature,
237
264
  setSelectedFeatures,
265
+ setSellingPointsLayer,
266
+ setSharedMobilityLayerGroup,
267
+ setTarifZonenLayer,
268
+ sharedMobilityLayerGroup,
269
+ tarifZonenLayer,
238
270
  };
239
- }, [isExportMenuOpen, selectedFeature, selectedFeatures, isLayerTreeOpen]);
271
+ }, [
272
+ isExportMenuOpen,
273
+ isLayerTreeOpen,
274
+ lineNetworkPlanLayer,
275
+ poisLayer,
276
+ selectedFeature,
277
+ selectedFeatures,
278
+ sellingPointsLayer,
279
+ sharedMobilityLayerGroup,
280
+ tarifZonenLayer,
281
+ ]);
282
+
283
+ const onLayerTreeMenuClick = useCallback(() => {
284
+ setIsLayerTreeOpen(!isLayerTreeOpen);
285
+ }, [isLayerTreeOpen]);
286
+
287
+ const onExportMenuClose = useCallback(() => {
288
+ setIsExportMenuOpen(false);
289
+ }, []);
290
+
291
+ const copyrightOptions = useMemo(() => {
292
+ return {
293
+ format: (copyrights) => {
294
+ const newCopyrights = [];
295
+ copyrights.forEach((copyright) => {
296
+ if (!/(sbb|rvf)/i.test(copyright)) {
297
+ newCopyrights.push(copyright);
298
+ }
299
+ });
300
+ return newCopyrights.join("&nbsp;|&nbsp;");
301
+ },
302
+ };
303
+ }, []);
240
304
 
241
305
  return (
242
306
  <I18nContext.Provider value={i18n}>
@@ -247,20 +311,11 @@ function RvfMobilityMap({
247
311
  <div
248
312
  className="relative size-full border font-sans @container/main"
249
313
  ref={eventNodeRef}
250
- style={{ fontSize: 16 }}
314
+ style={styleProps}
251
315
  >
252
- <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
316
+ <div className="relative flex size-full flex-col text-base @lg/main:flex-row-reverse">
253
317
  <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 />
318
+ <BaseLayer {...baseLayerProps} />
264
319
  <SingleClickListener />
265
320
 
266
321
  {realtime === "true" && <RealtimeLayer title="Echtzeit" />}
@@ -274,7 +329,10 @@ function RvfMobilityMap({
274
329
 
275
330
  <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
276
331
  <ScaleLine className="bg-slate-50/70" />
277
- <Copyright className="bg-slate-50/70" />
332
+ <Copyright
333
+ className="bg-slate-50/70"
334
+ options={copyrightOptions}
335
+ />
278
336
  </div>
279
337
 
280
338
  <div className="absolute right-2 top-2 z-10 flex flex-col gap-2">
@@ -291,13 +349,19 @@ function RvfMobilityMap({
291
349
  <div className="absolute bottom-10 right-2 z-10 flex flex-col justify-between gap-2">
292
350
  <RvfZoomButtons />
293
351
  </div>
352
+
353
+ <RvfFloatingMenu
354
+ isOpen={isLayerTreeOpen}
355
+ onClick={onLayerTreeMenuClick}
356
+ title="Kartendaten"
357
+ >
358
+ <Topics className={"w-full px-2"} />
359
+ </RvfFloatingMenu>
294
360
  </Map>
295
361
 
296
362
  <Overlay
297
363
  className={"z-50"}
298
- ScrollableHandlerProps={{
299
- style: { width: "calc(100% - 60px)" },
300
- }}
364
+ ScrollableHandlerProps={scrollableHandlerProps}
301
365
  >
302
366
  {realtime === "true" && trainId && (
303
367
  <RouteSchedule className="relative overflow-y-auto overflow-x-hidden" />
@@ -311,11 +375,7 @@ function RvfMobilityMap({
311
375
  </Overlay>
312
376
 
313
377
  {isExportMenuOpen && (
314
- <Modal
315
- onClose={() => {
316
- setIsExportMenuOpen(false);
317
- }}
318
- >
378
+ <Modal onClose={onExportMenuClose}>
319
379
  <RvfExportMenu className="relative flex h-full flex-col overflow-y-auto overflow-x-hidden" />
320
380
  </Modal>
321
381
  )}
@@ -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
  />
@@ -4,26 +4,30 @@ 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 RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
9
10
  const { baseLayer, map } = useMapContext();
11
+ const { setSellingPointsLayer } = useRvfContext();
10
12
 
11
13
  const layer = useMemo(() => {
12
14
  if (!baseLayer) {
13
15
  return null;
14
16
  }
15
17
  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
- );
18
+ isQueryable: true,
19
+ layersFilter: ({ metadata }) => {
20
+ return metadata?.["general.filter"] === "selling_points";
21
21
  },
22
22
  maplibreLayer: baseLayer,
23
23
  ...(props || {}),
24
24
  });
25
25
  }, [baseLayer, props]);
26
26
 
27
+ useEffect(() => {
28
+ setSellingPointsLayer(layer);
29
+ }, [layer, setSellingPointsLayer]);
30
+
27
31
  useEffect(() => {
28
32
  if (!map || !layer) {
29
33
  return;