@geops/rvf-mobility-web-component 0.1.11 → 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 +36 -0
- package/docutils.js +9 -9
- package/index.html +1 -1
- package/index.js +332 -263
- package/input.css +2 -8
- package/package.json +15 -15
- package/scripts/build.mjs +3 -3
- package/scripts/dev.mjs +3 -1
- package/src/BaseLayer/BaseLayer.tsx +20 -12
- package/src/RealtimeLayer/RealtimeLayer.tsx +1 -2
- 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 +25 -0
- package/src/RvfCheckbox/index.tsx +1 -0
- package/src/RvfExportMenu/RvfExportMenu.tsx +31 -11
- package/src/RvfFloatingMenu/RvfFloatingMenu.tsx +44 -0
- package/src/RvfFloatingMenu/index.tsx +1 -0
- package/src/RvfIconButton/RvfIconButton.tsx +1 -1
- package/src/{LayerTree/LayerTree.tsx → RvfLayerTree/RvfLayerTree.tsx} +11 -18
- package/src/RvfLayerTree/TreeItem/TreeItem.tsx +130 -0
- package/src/RvfLayerTree/index.tsx +1 -0
- package/src/{LayerTree → RvfLayerTree}/layersTreeReducer.ts +1 -4
- package/src/RvfLineNetworkPlanLayer/RvfLineNetworkPlanLayer.tsx +49 -0
- package/src/RvfLineNetworkPlanLayer/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +100 -35
- package/src/RvfPoisLayer/RvfPoisLayer.tsx +6 -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 +45 -0
- package/src/RvfSellingPointsLayer/index.tsx +1 -0
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +93 -44
- package/src/RvfSingleClickListener/RvfSingleClickListener.tsx +20 -3
- package/src/RvfTarifZonenLayer/RvfTarifZonenLayer.tsx +48 -0
- package/src/RvfTarifZonenLayer/index.tsx +1 -0
- package/src/RvfTopics/RvfTopics.tsx +44 -0
- package/src/RvfTopics/index.tsx +1 -0
- 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/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/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 +2 -0
- package/src/utils/createMobiDataBwWfsLayer.ts +31 -23
- package/src/utils/createSharedMobilityLayer.ts +176 -0
- package/src/utils/exportPdf.ts +20 -29
- package/src/utils/getAllLayers.ts +25 -0
- package/src/utils/hooks/useRvfContext.tsx +33 -0
- package/tailwind.config.mjs +31 -50
- package/src/LayerTree/TreeItem/TreeItem.tsx +0 -145
- package/src/LayerTree/TreeItemContainer/TreeItemContainer.tsx +0 -16
- package/src/LayerTree/TreeItemContainer/index.tsx +0 -1
- package/src/LayerTree/index.tsx +0 -1
- package/src/TopicMenu/TopicMenu.tsx +0 -143
- package/src/TopicMenu/index.tsx +0 -1
- /package/src/{LayerTree → RvfLayerTree}/TreeItem/index.tsx +0 -0
- /package/src/{LayerTree → RvfLayerTree}/layersTreeContext.ts +0 -0
package/input.css
CHANGED
package/package.json
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
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",
|
|
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.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
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
1
|
import * as esbuild from "esbuild";
|
|
3
2
|
import { sassPlugin } from "esbuild-sass-plugin";
|
|
4
3
|
|
|
5
4
|
await esbuild.build({
|
|
6
|
-
entryPoints: ["./src/index.js"],
|
|
7
5
|
bundle: true,
|
|
8
|
-
|
|
6
|
+
entryPoints: ["./src/index.js"],
|
|
9
7
|
external: ["mapbox-gl"],
|
|
10
8
|
loader: {
|
|
11
9
|
".png": "dataurl",
|
|
10
|
+
".svg": "dataurl",
|
|
12
11
|
},
|
|
12
|
+
minify: true,
|
|
13
13
|
outfile: "index.js",
|
|
14
14
|
plugins: [sassPlugin({ type: "css-text" })],
|
|
15
15
|
sourcemap: false,
|
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
|
|
|
@@ -8,6 +7,7 @@ const ctx = await esbuild.context({
|
|
|
8
7
|
external: ["mapbox-gl"],
|
|
9
8
|
loader: {
|
|
10
9
|
".png": "dataurl",
|
|
10
|
+
".svg": "dataurl",
|
|
11
11
|
},
|
|
12
12
|
outfile: "index.js",
|
|
13
13
|
plugins: [sassPlugin({ type: "css-text" })],
|
|
@@ -19,6 +19,8 @@ const { host, port } = await ctx.serve({
|
|
|
19
19
|
});
|
|
20
20
|
|
|
21
21
|
await ctx.watch();
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line no-undef
|
|
22
24
|
console.log(
|
|
23
25
|
`watching... and running at ${
|
|
24
26
|
host === "0.0.0.0" ? "http://localhost" : host
|
|
@@ -1,34 +1,42 @@
|
|
|
1
1
|
import { MaplibreLayer } from "mobility-toolbox-js/ol";
|
|
2
2
|
import { MaplibreLayerOptions } from "mobility-toolbox-js/ol/layers/MaplibreLayer";
|
|
3
|
-
import { Layer } from "ol/layer";
|
|
4
3
|
import { memo } from "preact/compat";
|
|
5
|
-
import { useEffect } from "preact/hooks";
|
|
4
|
+
import { useEffect, useMemo } from "preact/hooks";
|
|
6
5
|
|
|
7
6
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
export type BaseLayerProps = MaplibreLayerOptions;
|
|
9
|
+
|
|
10
|
+
function BaseLayer(props: BaseLayerProps) {
|
|
10
11
|
const { apikey, baselayer, map, mapsurl, setBaseLayer } = useMapContext();
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
const layer = useMemo(() => {
|
|
14
|
+
if (!baselayer || !apikey) {
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
|
-
|
|
17
|
+
return new MaplibreLayer({
|
|
16
18
|
apiKey: apikey,
|
|
17
19
|
style: baselayer,
|
|
18
20
|
url: mapsurl,
|
|
21
|
+
zIndex: 0,
|
|
19
22
|
...(props || {}),
|
|
20
23
|
});
|
|
21
|
-
|
|
24
|
+
}, [baselayer, apikey, props, mapsurl]);
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
baseLayer.setZIndex(0);
|
|
25
|
-
map.addLayer(baseLayer);
|
|
26
|
+
useEffect(() => {
|
|
26
27
|
setBaseLayer(layer);
|
|
28
|
+
}, [layer, setBaseLayer]);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!map || !layer) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
map.addLayer(layer);
|
|
27
35
|
|
|
28
36
|
return () => {
|
|
29
|
-
map
|
|
37
|
+
map.removeLayer(layer);
|
|
30
38
|
};
|
|
31
|
-
}, [map,
|
|
39
|
+
}, [map, layer]);
|
|
32
40
|
|
|
33
41
|
return null;
|
|
34
42
|
}
|
|
@@ -50,6 +50,7 @@ function RealtimeLayer(props: RealtimeLayerProps) {
|
|
|
50
50
|
: undefined,
|
|
51
51
|
tenant,
|
|
52
52
|
url: realtimeurl,
|
|
53
|
+
zIndex: 1,
|
|
53
54
|
...props,
|
|
54
55
|
styleOptions: {
|
|
55
56
|
getDelayColor: getDelayColorForVehicle,
|
|
@@ -61,8 +62,6 @@ function RealtimeLayer(props: RealtimeLayerProps) {
|
|
|
61
62
|
},
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
layer.setZIndex(1);
|
|
65
|
-
|
|
66
65
|
return layer;
|
|
67
66
|
}, [apikey, mots, realtimeurl, tenant, props]);
|
|
68
67
|
|
|
@@ -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: {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { JSX } from "preact";
|
|
2
|
+
|
|
3
|
+
import { memo } from "preact/compat";
|
|
4
|
+
|
|
5
|
+
// @ts-expect-error - required for htm to resolve the JSX pragma
|
|
6
|
+
import ok from "../icons/Ok/ok-grey.svg";
|
|
7
|
+
|
|
8
|
+
export type RvfCheckboxProps = {} & JSX.InputHTMLAttributes<HTMLInputElement>;
|
|
9
|
+
|
|
10
|
+
function RvfCheckbox({ className, ...props }: RvfCheckboxProps) {
|
|
11
|
+
return (
|
|
12
|
+
<input
|
|
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}`}
|
|
15
|
+
style={{
|
|
16
|
+
backgroundImage:
|
|
17
|
+
props.checked && !props.disabled ? `url('` + ok + `')` : "",
|
|
18
|
+
}}
|
|
19
|
+
{...props}
|
|
20
|
+
type="checkbox"
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default memo(RvfCheckbox);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfCheckbox";
|
|
@@ -4,15 +4,19 @@ import { memo, useId, useState } from "preact/compat";
|
|
|
4
4
|
|
|
5
5
|
import Cancel from "../icons/Cancel";
|
|
6
6
|
import RvfButton from "../RvfButton";
|
|
7
|
+
import RvfCheckbox from "../RvfCheckbox";
|
|
7
8
|
import RvfIconButton from "../RvfIconButton";
|
|
9
|
+
import RvfSelect from "../RvfSelect";
|
|
10
|
+
import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
|
|
8
11
|
import exportPdf from "../utils/exportPdf";
|
|
12
|
+
import getAllLayers from "../utils/getAllLayers";
|
|
9
13
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
10
14
|
import useRvfContext from "../utils/hooks/useRvfContext";
|
|
11
15
|
|
|
12
16
|
export type RvfExportMenuButtonProps = JSX.HTMLAttributes<HTMLDivElement> &
|
|
13
17
|
PreactDOMAttributes;
|
|
14
18
|
|
|
15
|
-
const formats = ["A4", "
|
|
19
|
+
const formats = ["A4", "A3", "A1", "A0"];
|
|
16
20
|
|
|
17
21
|
function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
|
|
18
22
|
const { setIsExportMenuOpen } = useRvfContext();
|
|
@@ -28,8 +32,9 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
|
|
|
28
32
|
<div className={"flex h-full flex-col gap-2 p-2"}>
|
|
29
33
|
{/* <!-- Header --> */}
|
|
30
34
|
<div className={"flex flex-row items-center justify-between gap-2"}>
|
|
31
|
-
<
|
|
35
|
+
<span className={"font-bold"}>Drucken </span>
|
|
32
36
|
<RvfIconButton
|
|
37
|
+
className={"!size-[32px]"}
|
|
33
38
|
onClick={() => {
|
|
34
39
|
setIsExportMenuOpen(false);
|
|
35
40
|
}}
|
|
@@ -39,31 +44,29 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
|
|
|
39
44
|
</div>
|
|
40
45
|
{/* <!-- Content --> */}
|
|
41
46
|
<div className="flex flex-1 flex-col gap-2">
|
|
42
|
-
<div className={"flex gap-2"}>
|
|
43
|
-
<
|
|
47
|
+
<div className={"flex flex-wrap items-center gap-2"}>
|
|
48
|
+
<RvfCheckbox
|
|
44
49
|
checked={useMaxExtent}
|
|
45
50
|
id={checkboxId}
|
|
46
51
|
onChange={() => {
|
|
47
|
-
setUseMaxExtent(!useMaxExtent);
|
|
52
|
+
return setUseMaxExtent(!useMaxExtent);
|
|
48
53
|
}}
|
|
49
|
-
type="checkbox"
|
|
50
54
|
/>
|
|
51
55
|
<label htmlFor={checkboxId}>Ganze Region exportieren</label>
|
|
52
56
|
</div>
|
|
53
|
-
<div className={"flex gap-2"}>
|
|
57
|
+
<div className={"flex flex-wrap items-center gap-2"}>
|
|
54
58
|
<label htmlFor={selectId}>Format:</label>
|
|
55
|
-
<
|
|
59
|
+
<RvfSelect
|
|
56
60
|
className={"w-24"}
|
|
57
61
|
id={selectId}
|
|
58
62
|
onChange={(evt) => {
|
|
59
63
|
setFormat((evt.target as HTMLSelectElement).value);
|
|
60
64
|
}}
|
|
61
|
-
value={format}
|
|
62
65
|
>
|
|
63
66
|
{formats.map((format) => {
|
|
64
67
|
return <option key={format}>{format}</option>;
|
|
65
68
|
})}
|
|
66
|
-
</
|
|
69
|
+
</RvfSelect>
|
|
67
70
|
</div>
|
|
68
71
|
</div>
|
|
69
72
|
{/* <!-- Footer --> */}
|
|
@@ -73,7 +76,24 @@ function RvfExportMenu({ ...props }: RvfExportMenuButtonProps) {
|
|
|
73
76
|
onClick={async () => {
|
|
74
77
|
setIsExportingError(false);
|
|
75
78
|
setIsExporting(true);
|
|
76
|
-
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
|
+
);
|
|
77
97
|
setTimeout(() => {
|
|
78
98
|
setIsExporting(false);
|
|
79
99
|
setIsExportingError(!result);
|
|
@@ -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 w-48 rounded-lg border border-grey bg-white shadow-lg @lg/main:w-52">
|
|
28
|
+
<button
|
|
29
|
+
className="flex w-full items-center justify-between px-2 py-1.5 font-bold"
|
|
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";
|
|
@@ -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,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { JSX, PreactDOMAttributes } from "preact";
|
|
2
|
+
import { memo } from "preact/compat";
|
|
1
3
|
import { useEffect, useReducer } from "preact/hooks";
|
|
2
4
|
|
|
3
5
|
import {
|
|
@@ -6,39 +8,30 @@ import {
|
|
|
6
8
|
} from "./layersTreeContext";
|
|
7
9
|
import layersTreeReducer from "./layersTreeReducer";
|
|
8
10
|
import TreeItem, { TreeItemProps } from "./TreeItem/TreeItem";
|
|
9
|
-
import TreeItemContainer from "./TreeItemContainer";
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
+
export type RvfLayerTreeProps = {
|
|
12
13
|
layers: TreeItemProps[];
|
|
13
|
-
}
|
|
14
|
+
} & JSX.HTMLAttributes<HTMLDivElement> &
|
|
15
|
+
PreactDOMAttributes;
|
|
14
16
|
|
|
15
|
-
function
|
|
17
|
+
function RvfLayerTree({ layers, ...props }: RvfLayerTreeProps) {
|
|
16
18
|
const [tree, dispatch] = useReducer(layersTreeReducer, layers);
|
|
17
19
|
|
|
18
20
|
useEffect(() => {
|
|
19
21
|
dispatch({ payload: layers, type: "INIT" });
|
|
20
|
-
console.log("INIT", layers);
|
|
21
22
|
}, [layers]);
|
|
22
23
|
|
|
23
|
-
const renderedLayers = layers.map((item, idx) => {
|
|
24
|
-
return (
|
|
25
|
-
<div className="border-b border-grey" key={idx}>
|
|
26
|
-
<TreeItem {...item} />
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
24
|
return (
|
|
32
25
|
<LayersTreeContext.Provider value={tree}>
|
|
33
26
|
<LayersTreeDispatchContext.Provider value={dispatch}>
|
|
34
|
-
<div
|
|
35
|
-
|
|
36
|
-
{
|
|
37
|
-
|
|
27
|
+
<div {...props}>
|
|
28
|
+
{layers.map((item) => {
|
|
29
|
+
return <TreeItem className="w-full" key={item.id} {...item} />;
|
|
30
|
+
})}
|
|
38
31
|
</div>
|
|
39
32
|
</LayersTreeDispatchContext.Provider>
|
|
40
33
|
</LayersTreeContext.Provider>
|
|
41
34
|
);
|
|
42
35
|
}
|
|
43
36
|
|
|
44
|
-
export default
|
|
37
|
+
export default memo(RvfLayerTree);
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
|
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
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div>
|
|
93
|
+
<div className="flex items-center gap-2 border-b py-2 pr-1">
|
|
94
|
+
{selectionType === SelectionType.RADIO ? (
|
|
95
|
+
<RvfRadioButton
|
|
96
|
+
checked={isControlChecked}
|
|
97
|
+
id={inputId}
|
|
98
|
+
onChange={handleSelectionChange}
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<RvfCheckbox
|
|
102
|
+
checked={isControlChecked}
|
|
103
|
+
id={inputId}
|
|
104
|
+
onChange={handleSelectionChange}
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
<label
|
|
108
|
+
className={`flex-1 cursor-pointer`}
|
|
109
|
+
htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
|
|
110
|
+
>
|
|
111
|
+
{title}
|
|
112
|
+
</label>
|
|
113
|
+
{renderedLayers.length > 0 && (
|
|
114
|
+
<button
|
|
115
|
+
className={`flex cursor-pointer items-center gap-2`}
|
|
116
|
+
id={buttonId}
|
|
117
|
+
onClick={handleItemClick}
|
|
118
|
+
>
|
|
119
|
+
{isContainerVisible ? <ArrowUp /> : <ArrowDown />}
|
|
120
|
+
</button>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
{isContainerVisible && renderedLayers.length > 0 && (
|
|
124
|
+
<div className="pl-6">{renderedLayers}</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export default TreeItem;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfLayerTree";
|
|
@@ -10,8 +10,6 @@ const initTree = (tree) => {
|
|
|
10
10
|
initializedTree.childItems = tree;
|
|
11
11
|
mapNode(initializedTree.childItems, initializedTree);
|
|
12
12
|
|
|
13
|
-
console.log("initializedTree", initializedTree);
|
|
14
|
-
|
|
15
13
|
return initializedTree;
|
|
16
14
|
};
|
|
17
15
|
|
|
@@ -109,6 +107,7 @@ const updateRadioChildNodes = (parent) => {
|
|
|
109
107
|
const updateCheckboxChildNodes = (parent) => {
|
|
110
108
|
for (const child of parent.childItems) {
|
|
111
109
|
child.isControlChecked = parent.isControlChecked;
|
|
110
|
+
child.layer.setVisible(child.isControlChecked);
|
|
112
111
|
|
|
113
112
|
if (child.childItems.length) {
|
|
114
113
|
updateChildNodes(child);
|
|
@@ -146,8 +145,6 @@ function layersTreeReducer(state = ROOT, action) {
|
|
|
146
145
|
return initTree(action.payload);
|
|
147
146
|
case "SELECT_ITEM":
|
|
148
147
|
return setNewControlCheckedStatus(state, action.payload);
|
|
149
|
-
case "SELECT_RADIO_ITEM":
|
|
150
|
-
return setNewControlCheckedStatus(state, action.payload);
|
|
151
148
|
default:
|
|
152
149
|
return state;
|
|
153
150
|
}
|