@geops/rvf-mobility-web-component 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/index.html +1 -1
- package/index.js +340 -219
- package/input.css +2 -8
- package/package.json +18 -16
- package/scripts/build.mjs +0 -1
- package/scripts/dev.mjs +2 -1
- package/src/RouteSchedule/RouteSchedule.tsx +3 -1
- package/src/RouteStop/RouteStop.tsx +1 -1
- package/src/RvfButton/RvfButton.tsx +6 -3
- package/src/RvfCheckbox/RvfCheckbox.tsx +10 -3
- package/src/RvfExportMenu/RvfExportMenu.tsx +62 -68
- package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +3 -3
- package/src/RvfIconButton/RvfIconButton.tsx +5 -2
- package/src/RvfInputCopy/RvfInputCopy.tsx +57 -0
- package/src/RvfInputCopy/index.tsx +1 -0
- package/src/RvfLayerTree/RvfLayerTree.tsx +5 -9
- package/src/RvfLayerTree/TreeItem/TreeItem.tsx +39 -9
- package/src/RvfLayerTree/layersTreeReducer.ts +1 -0
- package/src/RvfLayerTreeButton/RvfLayerTreeButton.tsx +27 -0
- package/src/RvfLayerTreeButton/index.tsx +1 -0
- package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +9 -5
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +253 -35
- package/src/RvfOverlayHeader/RvfOverlayHeader.tsx +40 -0
- package/src/RvfOverlayHeader/index.tsx +1 -0
- package/src/RvfPermalink/RvfPermalink.tsx +18 -0
- package/src/RvfPermalink/index.tsx +1 -0
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -0
- package/src/RvfRadioButton/RvfRadioButton.tsx +1 -1
- package/src/RvfSelect/RvfSelect.tsx +2 -2
- package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +25 -6
- package/src/RvfShare/RvfPermalinkButton/RvfPermalinkButton.tsx +61 -0
- package/src/RvfShare/RvfPermalinkButton/index.tsx +1 -0
- package/src/RvfShare/RvfShare.tsx +40 -0
- package/src/RvfShare/index.tsx +1 -0
- package/src/RvfShareMenuButton/RvfShareMenuButton.tsx +27 -0
- package/src/RvfShareMenuButton/index.tsx +1 -0
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +137 -51
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +10 -2
- package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +9 -5
- package/src/RvfTopics/RvfTopics.tsx +21 -24
- package/src/icons/Automat/Automat.tsx +8 -0
- package/src/icons/Automat/index.tsx +1 -0
- package/src/icons/Automat/rvf_automat.svg +15 -0
- package/src/icons/Bike/Bike.tsx +8 -0
- package/src/icons/Bike/index.tsx +1 -0
- package/src/icons/Bike/rvf_shared_bike.svg +15 -0
- package/src/icons/Car/Car.tsx +8 -0
- package/src/icons/Car/index.tsx +1 -0
- package/src/icons/Car/rvf_shared_car.svg +16 -0
- package/src/icons/CargoBike/CargoBike.tsx +8 -0
- package/src/icons/CargoBike/index.tsx +1 -0
- package/src/icons/CargoBike/rvf_shared_cargo_bike.svg +16 -0
- package/src/icons/Copy/Copy.tsx +25 -0
- package/src/icons/Copy/index.tsx +1 -0
- package/src/icons/Doc/Doc.tsx +19 -0
- package/src/icons/Doc/doc.svg +7 -0
- package/src/icons/Doc/index.tsx +1 -0
- package/src/icons/Ebike/Ebike.tsx +8 -0
- package/src/icons/Ebike/index.tsx +1 -0
- package/src/icons/Ebike/rvf_shared_e-bike.svg +15 -0
- package/src/icons/Email/Email.tsx +19 -0
- package/src/icons/Email/email.svg +7 -0
- package/src/icons/Email/index.tsx +1 -0
- package/src/icons/FilePdf/FilePdf.tsx +19 -0
- package/src/icons/FilePdf/file-pdf.svg +7 -0
- package/src/icons/FilePdf/index.tsx +1 -0
- package/src/icons/Image/Image.tsx +24 -0
- package/src/icons/Image/index.tsx +1 -0
- package/src/icons/InPerson/InPerson.tsx +8 -0
- package/src/icons/InPerson/index.tsx +1 -0
- package/src/icons/InPerson/rvf_persoenlich.svg +17 -0
- package/src/icons/Minus/minus-grey.svg +7 -0
- package/src/icons/Ride/Ride.tsx +8 -0
- package/src/icons/Ride/index.tsx +1 -0
- package/src/icons/Ride/rvf_shared_ride.svg +15 -0
- package/src/icons/Scooter/Scooter.tsx +8 -0
- package/src/icons/Scooter/index.tsx +1 -0
- package/src/icons/Scooter/rvf_shared_scooter.svg +15 -0
- package/src/icons/Share/Share.tsx +24 -0
- package/src/icons/Share/index.tsx +1 -0
- package/src/icons/Stack/Stack.tsx +24 -0
- package/src/icons/Stack/index.tsx +1 -0
- package/src/icons/Video/Video.tsx +8 -0
- package/src/icons/Video/index.tsx +1 -0
- package/src/icons/Video/rvf_video.svg +19 -0
- package/src/icons/rvf_shared_ride_2.svg +12 -0
- package/src/utils/constants.ts +2 -0
- package/src/utils/createMobiDataBwWfsLayer.ts +28 -20
- package/src/utils/createSharedMobilityLayer.ts +44 -33
- package/src/utils/exportPdf.ts +11 -0
- package/src/utils/getAllLayers.ts +25 -0
- package/src/utils/hooks/useRvfContext.tsx +45 -0
- package/tailwind.config.mjs +32 -29
- package/src/FloatingMenu/FloatingMenu.tsx +0 -42
- package/src/FloatingMenu/index.tsx +0 -1
- package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +0 -19
- package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +0 -14
- package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +0 -27
- package/src/icons/Scooter/scooter.svg +0 -10
package/input.css
CHANGED
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.
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
24
|
+
"@commitlint/cli": "^19.6.1",
|
|
23
25
|
"@commitlint/config-conventional": "^19.6.0",
|
|
24
|
-
"@eslint/js": "^9.
|
|
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.
|
|
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.
|
|
31
|
-
"esbuild": "^0.24.
|
|
32
|
+
"concurrently": "^9.1.2",
|
|
33
|
+
"esbuild": "^0.24.2",
|
|
32
34
|
"esbuild-sass-plugin": "^3.3.1",
|
|
33
|
-
"eslint": "^9.
|
|
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.
|
|
38
|
+
"eslint-plugin-perfectionist": "^4.5.0",
|
|
37
39
|
"eslint-plugin-prettier": "^5.2.1",
|
|
38
|
-
"eslint-plugin-react": "^7.37.
|
|
39
|
-
"eslint-plugin-react-hooks": "^5.1.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.
|
|
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.
|
|
54
|
+
"tailwindcss": "^3.4.17",
|
|
53
55
|
"ts-jest": "^29.2.5",
|
|
54
56
|
"typescript": "^5.7.2",
|
|
55
|
-
"typescript-eslint": "^8.
|
|
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
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 = {
|
|
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={`
|
|
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
|
|
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", "
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
<div className={"flex flex-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
78
|
-
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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
|
|
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
|
|
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}>
|
|
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
|
|
80
|
-
|
|
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={
|
|
128
|
+
className={`flex-1 cursor-pointer`}
|
|
129
|
+
htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
|
|
102
130
|
>
|
|
103
131
|
{title}
|
|
104
132
|
</label>
|
|
105
|
-
{
|
|
133
|
+
{renderedLayers.length > 0 && (
|
|
106
134
|
<button
|
|
107
|
-
className={`flex cursor-pointer items-center gap-2
|
|
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 &&
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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;
|