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