@geops/rvf-mobility-web-component 0.1.10 → 0.1.12
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 +52 -0
- package/docutils.js +198 -0
- package/index.html +48 -217
- package/index.js +680 -87
- package/input.css +11 -1
- package/jest-setup.js +3 -2
- package/package.json +4 -3
- package/scripts/build.mjs +3 -2
- package/scripts/dev.mjs +2 -1
- package/search.html +38 -69
- package/src/BaseLayer/BaseLayer.tsx +20 -12
- package/src/FloatingMenu/FloatingMenu.tsx +42 -0
- package/src/FloatingMenu/index.tsx +1 -0
- package/src/GeolocationButton/GeolocationButton.tsx +6 -5
- package/src/Map/Map.tsx +1 -0
- package/src/MobilityMap/MobilityMap.tsx +10 -9
- package/src/MobilityMap/index.css +0 -13
- package/src/RealtimeLayer/RealtimeLayer.tsx +2 -3
- package/src/RvfButton/RvfButton.tsx +28 -21
- package/src/RvfCheckbox/RvfCheckbox.tsx +24 -0
- package/src/RvfCheckbox/index.tsx +1 -0
- package/src/RvfExportMenu/RvfExportMenu.tsx +103 -0
- package/src/RvfExportMenu/index.tsx +1 -0
- package/src/RvfExportMenuButton/RvfExportMenuButton.tsx +27 -0
- package/src/RvfExportMenuButton/index.tsx +1 -0
- package/src/RvfFeatureDetails/RvfFeatureDetails.tsx +29 -0
- package/src/RvfFeatureDetails/index.tsx +1 -0
- package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +44 -0
- package/src/RvfFloatingMenu/index.tsx +1 -0
- package/src/RvfIconButton/RvfIconButton.tsx +35 -0
- package/src/RvfIconButton/index.tsx +1 -0
- package/src/RvfLayerTree/RvfLayerTree.tsx +41 -0
- package/src/RvfLayerTree/TreeItem/TreeItem.tsx +120 -0
- package/src/RvfLayerTree/TreeItem/index.tsx +1 -0
- package/src/RvfLayerTree/index.tsx +1 -0
- package/src/RvfLayerTree/layersTreeContext.ts +4 -0
- package/src/RvfLayerTree/layersTreeReducer.ts +152 -0
- package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +42 -0
- package/src/RvfLineNetworkPlanLayer/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +122 -83
- package/src/RvfMobilityMap/index.css +0 -13
- package/src/RvfModal/RvfModal.tsx +52 -0
- package/src/RvfModal/index.tsx +1 -0
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +39 -0
- package/src/RvfPoisLayer/index.tsx +1 -0
- package/src/RvfRadioButton/RvfRadioButton.tsx +16 -0
- package/src/RvfRadioButton/index.tsx +1 -0
- package/src/RvfSelect/RvfSelect.tsx +22 -0
- package/src/RvfSelect/index.tsx +1 -0
- package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +41 -0
- package/src/RvfSellingPointsLayer/index.tsx +1 -0
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +100 -0
- package/src/RvfSharedMobilityLayerGroup/index.tsx +1 -0
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +146 -0
- package/src/RvfSingleClickListener/index.tsx +1 -0
- package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +41 -0
- package/src/RvfTarifZonenLayer/index.tsx +1 -0
- package/src/RvfTopics/RvfTopics.tsx +47 -0
- package/src/RvfTopics/index.tsx +1 -0
- package/src/RvfZoomButtons/RvfZoomButtons.tsx +36 -29
- package/src/Search/Search.tsx +11 -9
- package/src/SingleClickListener/index.tsx +1 -1
- package/src/StationsLayer/StationsLayer.tsx +0 -1
- package/src/StopsSearch/StopsSearch.tsx +38 -6
- package/src/icons/ArrowDown/ArrowDown.tsx +22 -0
- package/src/icons/ArrowDown/down-open.svg +7 -0
- package/src/icons/ArrowDown/index.tsx +1 -0
- package/src/icons/ArrowUp/ArrowUp.tsx +22 -0
- package/src/icons/ArrowUp/index.tsx +1 -0
- package/src/icons/ArrowUp/up-open.svg +7 -0
- package/src/icons/Bicycle/verkehrstraeger-rad-2px-white.svg +19 -0
- package/src/icons/Cancel/Cancel.tsx +21 -0
- package/src/icons/Cancel/cancel.svg +7 -0
- package/src/icons/Cancel/index.tsx +1 -0
- package/src/icons/Car/verkehrstraeger-auto-2px-white.svg +14 -0
- package/src/icons/CargoBicycle/verkehrstraeger-lastenrad-2px-white.svg +27 -0
- package/src/icons/DownOpen/DownOpen.tsx +24 -0
- package/src/icons/DownOpen/down-open.svg +7 -0
- package/src/icons/DownOpen/index.tsx +1 -0
- package/src/icons/Download/Download.tsx +20 -0
- package/src/icons/Download/download.svg +15 -0
- package/src/icons/Download/index.tsx +1 -0
- package/src/icons/Elevator/Elevator.tsx +1 -1
- package/src/icons/Menu/Menu.tsx +32 -0
- package/src/icons/Menu/index.tsx +1 -0
- package/src/icons/Menu/menu.svg +9 -0
- package/src/icons/Ok/ok-grey.svg +7 -0
- package/src/icons/Ok/ok.svg +4 -0
- package/src/icons/Scooter/scooter.svg +10 -0
- package/src/utils/constants.ts +9 -0
- package/src/utils/createMobiDataBwWfsLayer.ts +120 -0
- package/src/utils/createSharedMobilityLayer.ts +165 -0
- package/src/utils/exportPdf.ts +657 -0
- package/src/utils/hooks/useRvfContext.tsx +37 -0
- package/src/utils/hooks/useUpdatePermalink.tsx +2 -9
- package/tailwind.config.mjs +41 -19
- package/src/RvfSharedMobilityLayer/RvfSharedMobilityLayer.tsx +0 -147
- package/src/RvfSharedMobilityLayer/index.tsx +0 -1
|
@@ -0,0 +1,103 @@
|
|
|
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 RvfCheckbox from "../RvfCheckbox";
|
|
8
|
+
import RvfIconButton from "../RvfIconButton";
|
|
9
|
+
import RvfSelect from "../RvfSelect";
|
|
10
|
+
import exportPdf from "../utils/exportPdf";
|
|
11
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
12
|
+
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
13
|
+
|
|
14
|
+
export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
|
|
15
|
+
PreactDOMAttributes;
|
|
16
|
+
|
|
17
|
+
const formats = ["A4", "A1", "A3", "A0"];
|
|
18
|
+
|
|
19
|
+
function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
|
|
20
|
+
const { setIsExportMenuOpen } = useRvfContext();
|
|
21
|
+
const { map } = useMapContext();
|
|
22
|
+
const [useMaxExtent, setUseMaxExtent] = useState(false);
|
|
23
|
+
const [format, setFormat] = useState<string>(formats[0]);
|
|
24
|
+
const checkboxId = useId();
|
|
25
|
+
const selectId = useId();
|
|
26
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
27
|
+
const [isExportingError, setIsExportingError] = useState(false);
|
|
28
|
+
return (
|
|
29
|
+
<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);
|
|
37
|
+
}}
|
|
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>
|
|
76
|
+
</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);
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{isExporting
|
|
92
|
+
? "Exporting..."
|
|
93
|
+
: isExportingError
|
|
94
|
+
? "Error"
|
|
95
|
+
: "Download"}
|
|
96
|
+
</RvfButton>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
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,44 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import ArrowDown from "../icons/ArrowDown";
|
|
4
|
+
import ArrowUp from "../icons/ArrowUp";
|
|
5
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
6
|
+
|
|
7
|
+
export type RvfFloatingMenuProps = {
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
onClick: () => void;
|
|
10
|
+
} & JSX.HTMLAttributes<HTMLDivElement> &
|
|
11
|
+
PreactDOMAttributes;
|
|
12
|
+
|
|
13
|
+
function RvfFloatingMenu({
|
|
14
|
+
children,
|
|
15
|
+
isOpen,
|
|
16
|
+
onClick,
|
|
17
|
+
title,
|
|
18
|
+
}: RvfFloatingMenuProps) {
|
|
19
|
+
const { map } = useMapContext();
|
|
20
|
+
|
|
21
|
+
if (!map) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
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">
|
|
28
|
+
<button
|
|
29
|
+
className="flex w-full items-center justify-between px-2 py-1.5 "
|
|
30
|
+
onClick={onClick}
|
|
31
|
+
>
|
|
32
|
+
{title} {isOpen ? <ArrowDown /> : <ArrowUp />}
|
|
33
|
+
</button>
|
|
34
|
+
{!isOpen && (
|
|
35
|
+
<div className="flex h-[calc(100%-39px)] w-full flex-1 overflow-y-auto">
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default RvfFloatingMenu;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfFloatingMenu";
|
|
@@ -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";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
import { memo } from "preact/compat";
|
|
3
|
+
import { useEffect, useReducer } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
LayersTreeContext,
|
|
7
|
+
LayersTreeDispatchContext,
|
|
8
|
+
} from "./layersTreeContext";
|
|
9
|
+
import layersTreeReducer from "./layersTreeReducer";
|
|
10
|
+
import TreeItem, { TreeItemProps } from "./TreeItem/TreeItem";
|
|
11
|
+
|
|
12
|
+
export type RvfLayerTreeProps = {
|
|
13
|
+
layers: TreeItemProps[];
|
|
14
|
+
} & JSX.HTMLAttributes<HTMLDivElement> &
|
|
15
|
+
PreactDOMAttributes;
|
|
16
|
+
|
|
17
|
+
function RvfLayerTree({ layers, ...props }: RvfLayerTreeProps) {
|
|
18
|
+
const [tree, dispatch] = useReducer(layersTreeReducer, layers);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
dispatch({ payload: layers, type: "INIT" });
|
|
22
|
+
}, [layers]);
|
|
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
|
+
return (
|
|
33
|
+
<LayersTreeContext.Provider value={tree}>
|
|
34
|
+
<LayersTreeDispatchContext.Provider value={dispatch}>
|
|
35
|
+
<div {...props}>{renderedLayers}</div>
|
|
36
|
+
</LayersTreeDispatchContext.Provider>
|
|
37
|
+
</LayersTreeContext.Provider>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default memo(RvfLayerTree);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
|
|
3
|
+
import BaseLayer from "ol/layer/Base";
|
|
4
|
+
import {
|
|
5
|
+
SVGProps,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useId,
|
|
9
|
+
useState,
|
|
10
|
+
} from "preact/compat";
|
|
11
|
+
|
|
12
|
+
import ArrowDown from "../../icons/ArrowDown";
|
|
13
|
+
import ArrowUp from "../../icons/ArrowUp";
|
|
14
|
+
import RvfCheckbox from "../../RvfCheckbox";
|
|
15
|
+
import RvfRadioButton from "../../RvfRadioButton";
|
|
16
|
+
import { LayersTreeDispatchContext } from "../layersTreeContext";
|
|
17
|
+
|
|
18
|
+
export enum SelectionType {
|
|
19
|
+
CHECKBOX = "checkbox",
|
|
20
|
+
RADIO = "radio",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TreeItemProps = {
|
|
24
|
+
childItems: TreeItemProps[];
|
|
25
|
+
Icon?: (props: SVGProps<SVGSVGElement>) => preact.JSX.Element;
|
|
26
|
+
id: string;
|
|
27
|
+
isCollapsedOnControlClick?: boolean;
|
|
28
|
+
isControlChecked?: boolean;
|
|
29
|
+
layer?: BaseLayer;
|
|
30
|
+
onIconClick?: () => void;
|
|
31
|
+
selectionType: SelectionType;
|
|
32
|
+
title: string;
|
|
33
|
+
} & JSX.HTMLAttributes<HTMLButtonElement> &
|
|
34
|
+
PreactDOMAttributes;
|
|
35
|
+
|
|
36
|
+
function TreeItem({
|
|
37
|
+
childItems,
|
|
38
|
+
isCollapsedOnControlClick,
|
|
39
|
+
isControlChecked,
|
|
40
|
+
layer,
|
|
41
|
+
selectionType,
|
|
42
|
+
title,
|
|
43
|
+
}: TreeItemProps) {
|
|
44
|
+
const [isContainerVisible, setIsContainerVisible] = useState(true);
|
|
45
|
+
const dispatch = useContext(LayersTreeDispatchContext);
|
|
46
|
+
const inputId = useId();
|
|
47
|
+
const buttonId = useId();
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (isCollapsedOnControlClick) {
|
|
51
|
+
setIsContainerVisible(isControlChecked);
|
|
52
|
+
}
|
|
53
|
+
}, [isControlChecked, isCollapsedOnControlClick, layer]);
|
|
54
|
+
|
|
55
|
+
const handleItemClick = () => {
|
|
56
|
+
setIsContainerVisible(!isContainerVisible);
|
|
57
|
+
|
|
58
|
+
if (isCollapsedOnControlClick && !isContainerVisible) {
|
|
59
|
+
dispatch({
|
|
60
|
+
payload: {
|
|
61
|
+
...this["props"],
|
|
62
|
+
isControlChecked: true,
|
|
63
|
+
},
|
|
64
|
+
type: "SELECT_ITEM",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const handleSelectionChange = (event) => {
|
|
70
|
+
dispatch({
|
|
71
|
+
payload: {
|
|
72
|
+
...this["props"],
|
|
73
|
+
isControlChecked: event.target.checked,
|
|
74
|
+
},
|
|
75
|
+
type: "SELECT_ITEM",
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const renderedLayers = childItems.map((item, idx) => {
|
|
80
|
+
return <TreeItem key={idx} {...item} />;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<div className="flex items-center gap-2 border-b py-2">
|
|
86
|
+
{selectionType === SelectionType.RADIO ? (
|
|
87
|
+
<RvfRadioButton
|
|
88
|
+
checked={isControlChecked}
|
|
89
|
+
id={inputId}
|
|
90
|
+
onChange={handleSelectionChange}
|
|
91
|
+
/>
|
|
92
|
+
) : (
|
|
93
|
+
<RvfCheckbox
|
|
94
|
+
checked={isControlChecked}
|
|
95
|
+
id={inputId}
|
|
96
|
+
onChange={handleSelectionChange}
|
|
97
|
+
/>
|
|
98
|
+
)}
|
|
99
|
+
<label
|
|
100
|
+
className={`cursor-pointer`}
|
|
101
|
+
htmlFor={childItems.length > 0 ? buttonId : inputId}
|
|
102
|
+
>
|
|
103
|
+
{title}
|
|
104
|
+
</label>
|
|
105
|
+
{childItems.length > 0 && (
|
|
106
|
+
<button
|
|
107
|
+
className={`flex cursor-pointer items-center gap-2 font-normal`}
|
|
108
|
+
id={buttonId}
|
|
109
|
+
onClick={handleItemClick}
|
|
110
|
+
>
|
|
111
|
+
{isContainerVisible ? <ArrowUp /> : <ArrowDown />}
|
|
112
|
+
</button>
|
|
113
|
+
)}
|
|
114
|
+
</div>
|
|
115
|
+
{isContainerVisible && <div className="pl-6">{renderedLayers}</div>}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default TreeItem;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./TreeItem";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfLayerTree";
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { SelectionType } from "./TreeItem/TreeItem";
|
|
2
|
+
|
|
3
|
+
const ROOT = {
|
|
4
|
+
childItems: [],
|
|
5
|
+
parent: null,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const initTree = (tree) => {
|
|
9
|
+
const initializedTree = { ...ROOT };
|
|
10
|
+
initializedTree.childItems = tree;
|
|
11
|
+
mapNode(initializedTree.childItems, initializedTree);
|
|
12
|
+
|
|
13
|
+
return initializedTree;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mapNode = (childItems, parent) => {
|
|
17
|
+
for (const child of childItems) {
|
|
18
|
+
child.parent = parent;
|
|
19
|
+
|
|
20
|
+
if (child.childItems.length) {
|
|
21
|
+
mapNode(child.childItems, child);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const findNodeInTree = (node, nodeToFind) => {
|
|
27
|
+
if (node.id === nodeToFind.id) {
|
|
28
|
+
return node;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (node.childItems.length) {
|
|
32
|
+
for (const child of node.childItems) {
|
|
33
|
+
const res = findNodeInTree(child, nodeToFind);
|
|
34
|
+
if (res) {
|
|
35
|
+
return res;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const setNewControlCheckedStatus = (tree, newItem) => {
|
|
42
|
+
const currentItem = findNodeInTree(tree, newItem);
|
|
43
|
+
|
|
44
|
+
if (currentItem) {
|
|
45
|
+
updateCheckedControlStatus(currentItem, newItem, false);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { ...tree };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const updateCheckedControlStatus = (currentItem, newItem, isParentUpdate) => {
|
|
52
|
+
if (newItem.isControlChecked) {
|
|
53
|
+
if (newItem.selectionType === SelectionType.CHECKBOX) {
|
|
54
|
+
currentItem.isControlChecked = newItem.isControlChecked;
|
|
55
|
+
currentItem.layer.setVisible(currentItem.isControlChecked);
|
|
56
|
+
} else {
|
|
57
|
+
for (const child of currentItem.parent.childItems) {
|
|
58
|
+
child.isControlChecked = child.id === currentItem.id;
|
|
59
|
+
|
|
60
|
+
if (!child.isControlChecked) {
|
|
61
|
+
updateRadioChildNodes(child);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
if (newItem.selectionType === SelectionType.CHECKBOX) {
|
|
67
|
+
currentItem.isControlChecked = newItem.isControlChecked;
|
|
68
|
+
currentItem.layer.setVisible(currentItem.isControlChecked);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// check all children
|
|
73
|
+
if (currentItem.childItems.length && !isParentUpdate) {
|
|
74
|
+
updateChildNodes(currentItem);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// check all parents
|
|
78
|
+
updateParentNodes(currentItem);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const updateChildNodes = (node) => {
|
|
82
|
+
if (node.childItems[0].selectionType === SelectionType.RADIO) {
|
|
83
|
+
updateRadioChildNodes(node);
|
|
84
|
+
} else {
|
|
85
|
+
updateCheckboxChildNodes(node);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const updateRadioChildNodes = (parent) => {
|
|
90
|
+
if (parent.isControlChecked) {
|
|
91
|
+
for (let i = 0; i < parent.childItems.length; i++) {
|
|
92
|
+
parent.childItems[i].isControlChecked = i === 0;
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
for (const child of parent.childItems) {
|
|
96
|
+
child.isControlChecked = false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (parent.childItems.length) {
|
|
101
|
+
if (parent.childItems[0].childItems.length) {
|
|
102
|
+
updateChildNodes(parent.childItems[0]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const updateCheckboxChildNodes = (parent) => {
|
|
108
|
+
for (const child of parent.childItems) {
|
|
109
|
+
child.isControlChecked = parent.isControlChecked;
|
|
110
|
+
|
|
111
|
+
if (child.childItems.length) {
|
|
112
|
+
updateChildNodes(child);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const updateParentNodes = (node) => {
|
|
118
|
+
if (node.parent) {
|
|
119
|
+
if (node.parent.selectionType === SelectionType.CHECKBOX) {
|
|
120
|
+
const newItem = {
|
|
121
|
+
...node.parent,
|
|
122
|
+
isControlChecked: node.parent.childItems.some((child) => {
|
|
123
|
+
return child.isControlChecked;
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
updateCheckedControlStatus(node.parent, newItem, true);
|
|
127
|
+
} else {
|
|
128
|
+
if (node?.parent?.parent) {
|
|
129
|
+
const newItem = {
|
|
130
|
+
...node.parent,
|
|
131
|
+
isControlChecked: node.parent.childItems.some((child) => {
|
|
132
|
+
return child.isControlChecked;
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
135
|
+
updateCheckedControlStatus(node.parent, newItem, true);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function layersTreeReducer(state = ROOT, action) {
|
|
142
|
+
switch (action.type) {
|
|
143
|
+
case "INIT":
|
|
144
|
+
return initTree(action.payload);
|
|
145
|
+
case "SELECT_ITEM":
|
|
146
|
+
return setNewControlCheckedStatus(state, action.payload);
|
|
147
|
+
default:
|
|
148
|
+
return state;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default layersTreeReducer;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { MaplibreStyleLayer } from "mobility-toolbox-js/ol";
|
|
2
|
+
import { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/MaplibreStyleLayer";
|
|
3
|
+
import { memo } from "preact/compat";
|
|
4
|
+
import { useEffect, useMemo } from "preact/hooks";
|
|
5
|
+
|
|
6
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
7
|
+
|
|
8
|
+
function RvfLineNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
|
|
9
|
+
const { baseLayer, map } = useMapContext();
|
|
10
|
+
|
|
11
|
+
const layer = useMemo(() => {
|
|
12
|
+
if (!baseLayer) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return new MaplibreStyleLayer({
|
|
16
|
+
layersFilter: ({ metadata, source }) => {
|
|
17
|
+
return (
|
|
18
|
+
metadata?.["rvf.filter"] === "netowrk_plans" ||
|
|
19
|
+
source === "network_plans"
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
maplibreLayer: baseLayer,
|
|
23
|
+
minZoom: 10,
|
|
24
|
+
...(props || {}),
|
|
25
|
+
});
|
|
26
|
+
}, [baseLayer, props]);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!map || !layer) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
map.addLayer(layer);
|
|
34
|
+
return () => {
|
|
35
|
+
map.removeLayer(layer);
|
|
36
|
+
};
|
|
37
|
+
}, [map, layer]);
|
|
38
|
+
|
|
39
|
+
return null; // <RegisterForSelectFeaturesOnClick />;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default memo(RvfLineNetworkPlanLayer);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfLineNetworkPlanLayer";
|