@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
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,41 +2,43 @@
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.14",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
9
9
  "dependencies": {
10
10
  "jspdf": "^2.5.2",
11
11
  "maplibre-gl": "^4.7.1",
12
- "mobility-toolbox-js": "3.1.0",
12
+ "mobility-toolbox-js": "3.1.1-beta.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",
18
18
  "react-icons": "^5.4.0",
19
- "rosetta": "^1.1.0"
19
+ "react-spatial": "^1.12.2",
20
+ "rosetta": "^1.1.0",
21
+ "tailwind-merge": "^2.6.0"
20
22
  },
21
23
  "devDependencies": {
22
- "@commitlint/cli": "^19.6.0",
24
+ "@commitlint/cli": "^19.6.1",
23
25
  "@commitlint/config-conventional": "^19.6.0",
24
- "@eslint/js": "^9.16.0",
26
+ "@eslint/js": "^9.17.0",
25
27
  "@tailwindcss/container-queries": "^0.1.1",
26
28
  "@testing-library/preact": "^3.2.4",
27
- "@types/geojson": "^7946.0.14",
29
+ "@types/geojson": "^7946.0.15",
28
30
  "@types/jest": "^29.5.14",
29
31
  "@types/preact-custom-element": "^4.0.4",
30
- "concurrently": "^9.1.0",
31
- "esbuild": "^0.24.0",
32
+ "concurrently": "^9.1.2",
33
+ "esbuild": "^0.24.2",
32
34
  "esbuild-sass-plugin": "^3.3.1",
33
- "eslint": "^9.16.0",
35
+ "eslint": "^9.17.0",
34
36
  "eslint-config-prettier": "9.1.0",
35
37
  "eslint-plugin-jsx-a11y": "^6.10.2",
36
- "eslint-plugin-perfectionist": "^4.2.0",
38
+ "eslint-plugin-perfectionist": "^4.5.0",
37
39
  "eslint-plugin-prettier": "^5.2.1",
38
- "eslint-plugin-react": "^7.37.2",
39
- "eslint-plugin-react-hooks": "^5.1.0-rc.0",
40
+ "eslint-plugin-react": "^7.37.3",
41
+ "eslint-plugin-react-hooks": "^5.1.0",
40
42
  "eslint-plugin-tailwindcss": "^3.17.5",
41
43
  "fixpack": "^4.0.0",
42
44
  "generact": "^0.4.0",
@@ -46,13 +48,13 @@
46
48
  "jest-environment-jsdom": "^29.7.0",
47
49
  "jest-preset-preact": "^4.1.1",
48
50
  "next": "15.0.3",
49
- "preact-render-to-string": "^6.5.11",
51
+ "preact-render-to-string": "^6.5.12",
50
52
  "prettier": "^3.4.2",
51
53
  "standard-version": "^9.5.0",
52
- "tailwindcss": "^3.4.16",
54
+ "tailwindcss": "^3.4.17",
53
55
  "ts-jest": "^29.2.5",
54
56
  "typescript": "^5.7.2",
55
- "typescript-eslint": "^8.17.0"
57
+ "typescript-eslint": "^8.19.0"
56
58
  },
57
59
  "scripts": {
58
60
  "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>
@@ -1,6 +1,7 @@
1
1
  import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
3
  import { memo, useMemo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
4
5
 
5
6
  export type RvfButtonProps = {
6
7
  selected?: boolean;
@@ -9,12 +10,12 @@ export type RvfButtonProps = {
9
10
  PreactDOMAttributes;
10
11
 
11
12
  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
+ "flex gap-2 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-left rounded-full border font-semibold text-button";
13
14
 
14
15
  export const themes = {
15
16
  primary: {
16
17
  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
+ "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
19
  selectedClasses: "bg-darkred border-darkred",
19
20
  },
20
21
  secondary: {
@@ -32,7 +33,9 @@ function RvfButton({
32
33
  ...props
33
34
  }: RvfButtonProps) {
34
35
  const classes = useMemo(() => {
35
- return `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`;
36
+ return twMerge(
37
+ `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`,
38
+ );
36
39
  }, [className, selected, theme]);
37
40
 
38
41
  return (
@@ -1,19 +1,26 @@
1
1
  import type { JSX } from "preact";
2
2
 
3
3
  import { memo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
4
5
 
5
6
  // @ts-expect-error - required for htm to resolve the JSX pragma
6
7
  import ok from "../icons/Ok/ok-grey.svg";
7
8
 
8
- export type RvfCheckboxProps = {} & JSX.InputHTMLAttributes<HTMLInputElement>;
9
+ export type RvfCheckboxProps = {
10
+ checkedIconUrl?: string;
11
+ } & JSX.InputHTMLAttributes<HTMLInputElement>;
9
12
 
10
13
  function RvfCheckbox({ className, ...props }: RvfCheckboxProps) {
14
+ const checkedIconUrl = props.checkedIconUrl || ok;
11
15
  return (
12
16
  <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}`}
17
+ className={twMerge(`
18
+ 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
19
  style={{
15
20
  backgroundImage:
16
- props.checked && !props.disabled ? `url('` + ok + `')` : "",
21
+ props.checked && !props.disabled
22
+ ? `url('` + checkedIconUrl + `')`
23
+ : "",
17
24
  }}
18
25
  {...props}
19
26
  type="checkbox"
@@ -2,22 +2,20 @@ import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
3
  import { memo, useId, useState } from "preact/compat";
4
4
 
5
- import Cancel from "../icons/Cancel";
6
5
  import RvfButton from "../RvfButton";
7
6
  import RvfCheckbox from "../RvfCheckbox";
8
- import RvfIconButton from "../RvfIconButton";
9
7
  import RvfSelect from "../RvfSelect";
8
+ import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
10
9
  import exportPdf from "../utils/exportPdf";
10
+ import getAllLayers from "../utils/getAllLayers";
11
11
  import useMapContext from "../utils/hooks/useMapContext";
12
- import useRvfContext from "../utils/hooks/useRvfContext";
13
12
 
14
13
  export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
15
14
  PreactDOMAttributes;
16
15
 
17
- const formats = ["A4", "A1", "A3", "A0"];
16
+ const formats = ["A4", "A3", "A1", "A0"];
18
17
 
19
18
  function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
20
- const { setIsExportMenuOpen } = useRvfContext();
21
19
  const { map } = useMapContext();
22
20
  const [useMaxExtent, setUseMaxExtent] = useState(false);
23
21
  const [format, setFormat] = useState<string>(formats[0]);
@@ -27,75 +25,71 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
27
25
  const [isExportingError, setIsExportingError] = useState(false);
28
26
  return (
29
27
  <div {...props}>
30
- <div className={"flex h-full flex-col gap-2 p-2"}>
31
- {/* <!-- Header --> */}
32
- <div className={"flex flex-row items-center justify-between gap-2"}>
33
- <h1>Export </h1>
34
- <RvfIconButton
35
- onClick={() => {
36
- setIsExportMenuOpen(false);
28
+ {/* <!-- Content --> */}
29
+ <div className="flex flex-1 flex-col gap-2">
30
+ <div className={"flex flex-wrap items-center gap-2"}>
31
+ <RvfCheckbox
32
+ checked={useMaxExtent}
33
+ id={checkboxId}
34
+ onChange={() => {
35
+ return setUseMaxExtent(!useMaxExtent);
37
36
  }}
38
- >
39
- <Cancel />
40
- </RvfIconButton>
41
- </div>
42
- {/* <!-- Content --> */}
43
- <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
- /> */}
53
- <RvfCheckbox
54
- checked={useMaxExtent}
55
- id={checkboxId}
56
- onChange={() => {
57
- return setUseMaxExtent(!useMaxExtent);
58
- }}
59
- />
60
- <label htmlFor={checkboxId}>Ganze Region exportieren</label>
61
- </div>
62
- <div className={"flex items-center gap-2"}>
63
- <label htmlFor={selectId}>Format:</label>
64
- <RvfSelect
65
- className={"w-24"}
66
- id={selectId}
67
- onChange={(evt) => {
68
- setFormat((evt.target as HTMLSelectElement).value);
69
- }}
70
- >
71
- {formats.map((format) => {
72
- return <option key={format}>{format}</option>;
73
- })}
74
- </RvfSelect>
75
- </div>
37
+ />
38
+ <label htmlFor={checkboxId}>Ganze Region exportieren</label>
76
39
  </div>
77
- {/* <!-- Footer --> */}
78
- <div>
79
- <RvfButton
80
- disabled={isExporting}
81
- onClick={async () => {
82
- setIsExportingError(false);
83
- setIsExporting(true);
84
- const result = await exportPdf(map, { format }, { useMaxExtent });
85
- setTimeout(() => {
86
- setIsExporting(false);
87
- setIsExportingError(!result);
88
- }, 1000);
40
+ <div className={"flex flex-wrap items-center gap-2"}>
41
+ <label htmlFor={selectId}>Format:</label>
42
+ <RvfSelect
43
+ className={"w-24"}
44
+ id={selectId}
45
+ onChange={(evt) => {
46
+ setFormat((evt.target as HTMLSelectElement).value);
89
47
  }}
90
48
  >
91
- {isExporting
92
- ? "Exporting..."
93
- : isExportingError
94
- ? "Error"
95
- : "Download"}
96
- </RvfButton>
49
+ {formats.map((format) => {
50
+ return <option key={format}>{format}</option>;
51
+ })}
52
+ </RvfSelect>
97
53
  </div>
98
54
  </div>
55
+ {/* <!-- Footer --> */}
56
+ <div>
57
+ <RvfButton
58
+ disabled={isExporting}
59
+ onClick={async () => {
60
+ setIsExportingError(false);
61
+ setIsExporting(true);
62
+ const result = await exportPdf(
63
+ map,
64
+ { format },
65
+ {
66
+ onAfter: (map, layers) => {
67
+ getAllLayers(layers).forEach((layer) => {
68
+ layer.set(LAYER_PROP_IS_EXPORTING, false);
69
+ });
70
+ },
71
+
72
+ onBefore: (map, layers) => {
73
+ getAllLayers(layers).forEach((layer) => {
74
+ layer.set(LAYER_PROP_IS_EXPORTING, true);
75
+ });
76
+ },
77
+ useMaxExtent,
78
+ },
79
+ );
80
+ setTimeout(() => {
81
+ setIsExporting(false);
82
+ setIsExportingError(!result);
83
+ }, 1000);
84
+ }}
85
+ >
86
+ {isExporting
87
+ ? "Exporting..."
88
+ : isExportingError
89
+ ? "Error"
90
+ : "Download"}
91
+ </RvfButton>
92
+ </div>
99
93
  </div>
100
94
  );
101
95
  }
@@ -24,15 +24,15 @@ 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 />}
33
33
  </button>
34
34
  {!isOpen && (
35
- <div className="flex h-[calc(100%-39px)] w-full flex-1 overflow-y-auto">
35
+ <div className="flex h-[calc(100%-39px)] w-full flex-1 flex-col overflow-y-auto">
36
36
  {children}
37
37
  </div>
38
38
  )}
@@ -1,6 +1,7 @@
1
1
  import type { JSX, PreactDOMAttributes } from "preact";
2
2
 
3
3
  import { memo, SVGProps, useMemo } from "preact/compat";
4
+ import { twMerge } from "tailwind-merge";
4
5
 
5
6
  import { RvfButtonProps, themes } from "../RvfButton/RvfButton";
6
7
 
@@ -11,7 +12,7 @@ export type RvfIconButtonProps = {
11
12
  RvfButtonProps;
12
13
 
13
14
  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
+ "flex size-[32px] @sm/main:size-[36px] @md/main:size-[40px] p-[7px] items-center justify-center rounded-full border";
15
16
 
16
17
  function RvfIconButton({
17
18
  children,
@@ -22,7 +23,9 @@ function RvfIconButton({
22
23
  ...props
23
24
  }: RvfIconButtonProps) {
24
25
  const classes = useMemo(() => {
25
- return `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`;
26
+ return twMerge(
27
+ `${baseClasses} ${themes[theme].classes} ${selected ? themes[theme].selectedClasses : ""} ${className || ""}`,
28
+ );
26
29
  }, [className, selected, theme]);
27
30
 
28
31
  return (
@@ -0,0 +1,57 @@
1
+ import type { JSX, PreactDOMAttributes } from "preact";
2
+
3
+ import { useRef, useState } from "preact/hooks";
4
+
5
+ import Copy from "../icons/Copy";
6
+ import RvfIconButton from "../RvfIconButton";
7
+
8
+ function RvfInputCopy(props: JSX.InputHTMLAttributes & PreactDOMAttributes) {
9
+ const [positionTooltip, setPositionTooltip] = useState<DOMRect>();
10
+ const [isTooptipShowed, setIsTooltipShowed] = useState(false);
11
+ const inputRef = useRef(null);
12
+
13
+ const handleCopyClick = (event) => {
14
+ setPositionTooltip(event.currentTarget.getBoundingClientRect());
15
+ navigator.clipboard.writeText(window?.location.href).then(() => {
16
+ setIsTooltipShowed(true);
17
+ setTimeout(() => {
18
+ setIsTooltipShowed(false);
19
+ }, 1000);
20
+ });
21
+ inputRef.current.select();
22
+ };
23
+
24
+ const handleInputFocus = () => {
25
+ inputRef.current.select();
26
+ };
27
+
28
+ return (
29
+ <div className="flex items-center text-grey">
30
+ <input
31
+ className="h-7 w-full rounded-sm rounded-r-none border border-r-0 border-current p-1 leading-4 outline-none"
32
+ onFocus={handleInputFocus}
33
+ readOnly
34
+ ref={inputRef}
35
+ type="text"
36
+ {...props}
37
+ />
38
+ <RvfIconButton
39
+ className="!size-7 rounded-l-none rounded-r-sm border-current"
40
+ onClick={handleCopyClick}
41
+ >
42
+ <Copy />
43
+ </RvfIconButton>
44
+ <div
45
+ className={`fixed rounded-md bg-grey p-1 text-sm text-white ${isTooptipShowed ? "block" : "hidden"}`}
46
+ style={{
47
+ left: positionTooltip?.left - 30,
48
+ top: positionTooltip?.top - 40,
49
+ }}
50
+ >
51
+ Link kopiert!
52
+ </div>
53
+ </div>
54
+ );
55
+ }
56
+
57
+ export default RvfInputCopy;
@@ -0,0 +1 @@
1
+ export { default } from "./RvfInputCopy";
@@ -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
  );
@@ -11,6 +11,7 @@ import {
11
11
 
12
12
  import ArrowDown from "../../icons/ArrowDown";
13
13
  import ArrowUp from "../../icons/ArrowUp";
14
+ import minusGrey from "../../icons/Minus/minus-grey.svg";
14
15
  import RvfCheckbox from "../../RvfCheckbox";
15
16
  import RvfRadioButton from "../../RvfRadioButton";
16
17
  import { LayersTreeDispatchContext } from "../layersTreeContext";
@@ -76,13 +77,38 @@ function TreeItem({
76
77
  });
77
78
  };
78
79
 
79
- const renderedLayers = childItems.map((item, idx) => {
80
- return <TreeItem key={idx} {...item} />;
81
- });
80
+ const renderedLayers = childItems
81
+ .filter(({ title }) => {
82
+ return !!title;
83
+ })
84
+ .map((item, idx) => {
85
+ return <TreeItem key={idx} {...item} />;
86
+ });
87
+
88
+ if (!title) {
89
+ return null;
90
+ }
91
+
92
+ const isMiddleState = () => {
93
+ if (childItems.length > 0) {
94
+ const checkedItems = childItems.filter((item) => {
95
+ return item.isControlChecked;
96
+ });
97
+ if (checkedItems.length === childItems.length) {
98
+ return false;
99
+ }
100
+
101
+ return childItems.some((item) => {
102
+ return item.isControlChecked;
103
+ });
104
+ }
105
+
106
+ return false;
107
+ };
82
108
 
83
109
  return (
84
110
  <div>
85
- <div className="flex items-center gap-2 border-b py-2">
111
+ <div className="flex items-center gap-2 border-b py-2 pr-1">
86
112
  {selectionType === SelectionType.RADIO ? (
87
113
  <RvfRadioButton
88
114
  checked={isControlChecked}
@@ -92,19 +118,21 @@ function TreeItem({
92
118
  ) : (
93
119
  <RvfCheckbox
94
120
  checked={isControlChecked}
121
+ checkedIconUrl={isMiddleState() ? minusGrey : null}
122
+ className={isMiddleState() ? "bg-[length:18px]" : ""}
95
123
  id={inputId}
96
124
  onChange={handleSelectionChange}
97
125
  />
98
126
  )}
99
127
  <label
100
- className={`cursor-pointer`}
101
- htmlFor={childItems.length > 0 ? buttonId : inputId}
128
+ className={`flex-1 cursor-pointer`}
129
+ htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
102
130
  >
103
131
  {title}
104
132
  </label>
105
- {childItems.length > 0 && (
133
+ {renderedLayers.length > 0 && (
106
134
  <button
107
- className={`flex cursor-pointer items-center gap-2 font-normal`}
135
+ className={`flex cursor-pointer items-center gap-2`}
108
136
  id={buttonId}
109
137
  onClick={handleItemClick}
110
138
  >
@@ -112,7 +140,9 @@ function TreeItem({
112
140
  </button>
113
141
  )}
114
142
  </div>
115
- {isContainerVisible && <div className="pl-6">{renderedLayers}</div>}
143
+ {isContainerVisible && renderedLayers.length > 0 && (
144
+ <div className="pl-6">{renderedLayers}</div>
145
+ )}
116
146
  </div>
117
147
  );
118
148
  }
@@ -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);
@@ -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 Stack from "../icons/Stack";
7
+ import RvfIconButton from "../RvfIconButton";
8
+ import useRvfContext from "../utils/hooks/useRvfContext";
9
+
10
+ export type RvfLayerTreeButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
11
+ PreactDOMAttributes;
12
+
13
+ function RvfLayerTreeButton({ ...props }: RvfLayerTreeButtonProps) {
14
+ const { isLayerTreeOpen, setIsLayerTreeOpen } = useRvfContext();
15
+
16
+ const onClick = useCallback(() => {
17
+ setIsLayerTreeOpen(!isLayerTreeOpen);
18
+ }, [isLayerTreeOpen, setIsLayerTreeOpen]);
19
+
20
+ return (
21
+ <RvfIconButton {...props} onClick={onClick} selected={isLayerTreeOpen}>
22
+ <Stack />
23
+ </RvfIconButton>
24
+ );
25
+ }
26
+
27
+ export default memo(RvfLayerTreeButton);
@@ -0,0 +1 @@
1
+ export { default } from "./RvfLayerTreeButton";
@@ -4,20 +4,20 @@ 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({
16
- layersFilter: ({ metadata, source }) => {
17
- return (
18
- metadata?.["rvf.filter"] === "netowrk_plans" ||
19
- source === "network_plans"
20
- );
18
+ isQueryable: true,
19
+ layersFilter: ({ metadata }) => {
20
+ return metadata?.["rvf.filter"] === "netzplan_lines";
21
21
  },
22
22
  maplibreLayer: baseLayer,
23
23
  minZoom: 10,
@@ -25,6 +25,10 @@ function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
25
25
  });
26
26
  }, [baseLayer, props]);
27
27
 
28
+ useEffect(() => {
29
+ setLineNetworkPlanLayer(layer);
30
+ }, [layer, setLineNetworkPlanLayer]);
31
+
28
32
  useEffect(() => {
29
33
  if (!map || !layer) {
30
34
  return;