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