@geops/rvf-mobility-web-component 0.1.64 → 0.1.65
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 +25 -0
- package/docutils.js +29 -19
- package/index.html +0 -1
- package/index.js +276 -235
- package/package.json +12 -12
- package/src/BaseLayer/BaseLayer.tsx +18 -2
- package/src/Copyright/Copyright.tsx +2 -2
- package/src/Copyright/index.tsx +1 -1
- package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +1 -0
- package/src/Map/Map.tsx +27 -1
- package/src/MapLayout/MapLayout.tsx +1 -1
- package/src/MapLayout/index.tsx +1 -1
- package/src/MobilityMap/MobilityMap.tsx +5 -11
- package/src/MobilityMap/MobilityMapAttributes.ts +8 -5
- package/src/NotificationDetails/NotificationDetails.tsx +75 -57
- package/src/Permalink/Permalink.tsx +17 -6
- package/src/PermalinkInput/PermalinkInput.tsx +4 -1
- package/src/RealtimeLayer/index.tsx +1 -1
- package/src/RvfCopyright/RvfCopyright.tsx +32 -0
- package/src/RvfCopyright/index.tsx +1 -0
- package/src/RvfInputCopy/RvfInputCopy.tsx +18 -8
- package/src/RvfMapLayout/RvfMapLayout.tsx +198 -0
- package/src/RvfMapLayout/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +10 -580
- package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +64 -0
- package/src/RvfRealtimeLayer/index.tsx +1 -0
- package/src/RvfStationsLayer/RvfStationsLayer.tsx +19 -0
- package/src/RvfStationsLayer/index.tsx +1 -0
- package/src/ShareMenu/ShareMenu.tsx +3 -1
- package/src/StationsLayer/index.tsx +1 -1
- package/src/ui/InputCopy/InputCopy.tsx +21 -10
- package/src/utils/constants.ts +1 -1
- package/src/utils/getUrlFromTemplate.test.ts +23 -0
- package/src/utils/getUrlFromTemplate.ts +47 -0
- package/src/utils/hooks/useI18n.tsx +2 -4
- package/src/utils/hooks/useInitialLayersVisiblity.tsx +27 -4
- package/src/utils/hooks/useInitialPermalink.tsx +31 -21
- package/src/utils/hooks/usePermalink.tsx +25 -0
- package/src/utils/translations.ts +4 -0
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.65",
|
|
6
6
|
"homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "index.js",
|
|
@@ -11,23 +11,23 @@
|
|
|
11
11
|
"graphql-request": "^7.1.2",
|
|
12
12
|
"jspdf": "^3.0.3",
|
|
13
13
|
"lodash.debounce": "^4.0.8",
|
|
14
|
-
"maplibre-gl": "^5.
|
|
15
|
-
"mobility-toolbox-js": "3.4.4
|
|
14
|
+
"maplibre-gl": "^5.9.0",
|
|
15
|
+
"mobility-toolbox-js": "3.4.4",
|
|
16
16
|
"ol": "^10.6.1",
|
|
17
17
|
"preact": "^10.27.2",
|
|
18
18
|
"preact-custom-element": "^4.5.1",
|
|
19
19
|
"react": "npm:@preact/compat@^18.3.1",
|
|
20
20
|
"react-dom": "npm:@preact/compat@^18.3.1",
|
|
21
21
|
"react-icons": "^5.5.0",
|
|
22
|
-
"react-spatial": "^2.0.
|
|
22
|
+
"react-spatial": "^2.0.1",
|
|
23
23
|
"rosetta": "^1.1.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@commitlint/cli": "^20.1.0",
|
|
27
27
|
"@commitlint/config-conventional": "^20.0.0",
|
|
28
|
-
"@eslint/js": "^9.
|
|
29
|
-
"@geops/eslint-config-react": "^1.
|
|
30
|
-
"@tailwindcss/cli": "^4.1.
|
|
28
|
+
"@eslint/js": "^9.37.0",
|
|
29
|
+
"@geops/eslint-config-react": "^1.6.0-beta.1",
|
|
30
|
+
"@tailwindcss/cli": "^4.1.14",
|
|
31
31
|
"@tailwindcss/container-queries": "^0.1.1",
|
|
32
32
|
"@testing-library/preact": "^3.2.4",
|
|
33
33
|
"@types/geojson": "^7946.0.16",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"concurrently": "^9.2.1",
|
|
38
38
|
"esbuild": "^0.25.10",
|
|
39
39
|
"esbuild-sass-plugin": "^3.3.1",
|
|
40
|
-
"eslint": "^9.
|
|
40
|
+
"eslint": "^9.37.0",
|
|
41
41
|
"eslint-plugin-tailwindcss": "^4.0.0-beta.0",
|
|
42
42
|
"fixpack": "^4.0.0",
|
|
43
43
|
"generact": "^0.4.0",
|
|
@@ -46,16 +46,16 @@
|
|
|
46
46
|
"jest-canvas-mock": "^2.5.2",
|
|
47
47
|
"jest-environment-jsdom": "^30.2.0",
|
|
48
48
|
"jest-preset-preact": "^4.1.1",
|
|
49
|
-
"next": "15.5.
|
|
49
|
+
"next": "15.5.5",
|
|
50
50
|
"preact-render-to-string": "^6.6.2",
|
|
51
51
|
"prettier": "^3.6.2",
|
|
52
52
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
|
53
53
|
"standard-version": "^9.5.0",
|
|
54
54
|
"tailwind-merge": "^3.3.1",
|
|
55
|
-
"tailwindcss": "^4.1.
|
|
56
|
-
"ts-jest": "^29.4.
|
|
55
|
+
"tailwindcss": "^4.1.14",
|
|
56
|
+
"ts-jest": "^29.4.5",
|
|
57
57
|
"typescript": "^5.9.3",
|
|
58
|
-
"typescript-eslint": "^8.
|
|
58
|
+
"typescript-eslint": "^8.46.1"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "yarn build:css && yarn build:js && cp index*.js* doc/public/",
|
|
@@ -9,7 +9,13 @@ import type { MaplibreLayerOptions } from "mobility-toolbox-js/ol/layers/Maplibr
|
|
|
9
9
|
export type BaseLayerProps = MaplibreLayerOptions;
|
|
10
10
|
|
|
11
11
|
function BaseLayer(props: BaseLayerProps) {
|
|
12
|
-
const { apikey, baselayer, map, mapsurl, setBaseLayer } =
|
|
12
|
+
const { apikey, baselayer, hasPrint, hasShare, map, mapsurl, setBaseLayer } =
|
|
13
|
+
useMapContext();
|
|
14
|
+
|
|
15
|
+
// For printing purpose and bild saving purpose otherwise can be false.
|
|
16
|
+
const preserveDrawingBuffer = useMemo(() => {
|
|
17
|
+
return hasShare || hasPrint;
|
|
18
|
+
}, [hasShare, hasPrint]);
|
|
13
19
|
|
|
14
20
|
const layer = useMemo(() => {
|
|
15
21
|
if (!baselayer || !apikey) {
|
|
@@ -20,9 +26,19 @@ function BaseLayer(props: BaseLayerProps) {
|
|
|
20
26
|
style: baselayer,
|
|
21
27
|
url: mapsurl,
|
|
22
28
|
zIndex: 0,
|
|
29
|
+
|
|
23
30
|
...(props || {}),
|
|
31
|
+
mapLibreOptions: {
|
|
32
|
+
// For printing purpose
|
|
33
|
+
maxCanvasSize: [20000, 20000], // remove 4096 limitations
|
|
34
|
+
...(props?.mapLibreOptions || {}),
|
|
35
|
+
canvasContextAttributes: {
|
|
36
|
+
preserveDrawingBuffer: preserveDrawingBuffer,
|
|
37
|
+
...(props?.mapLibreOptions?.canvasContextAttributes || {}),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
24
40
|
});
|
|
25
|
-
}, [baselayer, apikey, props,
|
|
41
|
+
}, [baselayer, apikey, mapsurl, props, preserveDrawingBuffer]);
|
|
26
42
|
|
|
27
43
|
useEffect(() => {
|
|
28
44
|
setBaseLayer(layer);
|
|
@@ -7,11 +7,11 @@ import useMapContext from "../utils/hooks/useMapContext";
|
|
|
7
7
|
import style from "./index.css";
|
|
8
8
|
|
|
9
9
|
import type { CopyrightControlOptions } from "mobility-toolbox-js/ol/controls/CopyrightControl";
|
|
10
|
-
import type {
|
|
10
|
+
import type { HTMLAttributes, PreactDOMAttributes } from "preact";
|
|
11
11
|
|
|
12
12
|
export type CopyrightProps = {
|
|
13
13
|
options?: CopyrightControlOptions;
|
|
14
|
-
} &
|
|
14
|
+
} & HTMLAttributes<HTMLDivElement> &
|
|
15
15
|
PreactDOMAttributes;
|
|
16
16
|
|
|
17
17
|
function Copyright({ options, ...props }: CopyrightProps) {
|
package/src/Copyright/index.tsx
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from "
|
|
1
|
+
export { default } from "../RvfCopyright";
|
package/src/Map/Map.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { unByKey } from "ol/Observable";
|
|
|
3
3
|
import { memo } from "preact/compat";
|
|
4
4
|
import { useEffect, useMemo, useRef } from "preact/hooks";
|
|
5
5
|
|
|
6
|
+
import useInitialPermalink from "../utils/hooks/useInitialPermalink";
|
|
6
7
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
7
8
|
|
|
8
9
|
// @ts-expect-error bad type definition
|
|
@@ -10,6 +11,8 @@ import olStyle from "ol/ol.css";
|
|
|
10
11
|
|
|
11
12
|
import type { JSX, PreactDOMAttributes } from "preact";
|
|
12
13
|
|
|
14
|
+
import type { MobilityMapProps } from "../MobilityMap/MobilityMap";
|
|
15
|
+
|
|
13
16
|
export type RealtimeMapProps = JSX.HTMLAttributes<HTMLDivElement> &
|
|
14
17
|
PreactDOMAttributes;
|
|
15
18
|
|
|
@@ -18,6 +21,10 @@ function Map({ children, ...props }: RealtimeMapProps) {
|
|
|
18
21
|
const { center, extent, map, maxextent, maxzoom, minzoom, setMap, zoom } =
|
|
19
22
|
useMapContext();
|
|
20
23
|
|
|
24
|
+
const propsFromPermalinkRef = useRef<null | Partial<MobilityMapProps>>(
|
|
25
|
+
useInitialPermalink(),
|
|
26
|
+
);
|
|
27
|
+
|
|
21
28
|
const view = useMemo(() => {
|
|
22
29
|
if (!maxextent) {
|
|
23
30
|
if (maxextent === "") {
|
|
@@ -67,7 +74,7 @@ function Map({ children, ...props }: RealtimeMapProps) {
|
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
return () => {
|
|
70
|
-
newMap?.setTarget();
|
|
77
|
+
newMap?.setTarget(undefined);
|
|
71
78
|
setMap();
|
|
72
79
|
};
|
|
73
80
|
}, [setMap]);
|
|
@@ -127,6 +134,25 @@ function Map({ children, ...props }: RealtimeMapProps) {
|
|
|
127
134
|
}
|
|
128
135
|
}, [map, minzoom]);
|
|
129
136
|
|
|
137
|
+
// Apply permalink parameters when the view is set after that the default attribute is set
|
|
138
|
+
// Order of useEffect is important here.
|
|
139
|
+
// This use effect should be called only when the view is set
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const curr = propsFromPermalinkRef.current;
|
|
142
|
+
const mapView = map?.getView();
|
|
143
|
+
if (mapView && curr.center) {
|
|
144
|
+
mapView.setCenter(
|
|
145
|
+
curr.center.split(",").map((c) => {
|
|
146
|
+
return parseFloat(c);
|
|
147
|
+
}),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (mapView && curr.zoom) {
|
|
152
|
+
mapView.setZoom(parseFloat(curr.zoom));
|
|
153
|
+
}
|
|
154
|
+
}, [map]);
|
|
155
|
+
|
|
130
156
|
return (
|
|
131
157
|
<>
|
|
132
158
|
<style>{olStyle}</style>
|
|
@@ -128,7 +128,7 @@ function MapLayout({
|
|
|
128
128
|
{isOverlayOpen && (
|
|
129
129
|
<div
|
|
130
130
|
className={twMerge(
|
|
131
|
-
"flex w-0 flex-1 flex-col overflow-hidden rounded-2xl @lg:min-w-
|
|
131
|
+
"flex w-0 flex-1 flex-col overflow-hidden rounded-2xl @lg:min-w-[320px]",
|
|
132
132
|
)}
|
|
133
133
|
style={{ containerType: "normal" }}
|
|
134
134
|
>
|
package/src/MapLayout/index.tsx
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from "
|
|
1
|
+
export { default } from "../RvfMapLayout";
|
|
@@ -16,7 +16,6 @@ import SingleClickListener from "../SingleClickListener";
|
|
|
16
16
|
import StationsLayer from "../StationsLayer";
|
|
17
17
|
import { I18nContext } from "../utils/hooks/useI18n";
|
|
18
18
|
import useInitialLayersVisiblity from "../utils/hooks/useInitialLayersVisiblity";
|
|
19
|
-
import useInitialPermalink from "../utils/hooks/useInitialPermalink";
|
|
20
19
|
import { MapContext } from "../utils/hooks/useMapContext";
|
|
21
20
|
import i18n from "../utils/i18n";
|
|
22
21
|
import WindowMessageListener from "../WindowMessageListener";
|
|
@@ -103,10 +102,10 @@ function MobilityMap(props: MobilityMapProps) {
|
|
|
103
102
|
const [previewNotifications, setPreviewNotifications] =
|
|
104
103
|
useState<SituationType[]>();
|
|
105
104
|
|
|
106
|
-
const { lang, layers, lines } = props;
|
|
105
|
+
const { lang, layers, lines, permalinktemplate } = props;
|
|
107
106
|
|
|
108
107
|
// Apply initial visibility of layers
|
|
109
|
-
useInitialLayersVisiblity(map, layers);
|
|
108
|
+
useInitialLayersVisiblity(map, layers, permalinktemplate);
|
|
110
109
|
|
|
111
110
|
// Object representing all the web-component attributes and the map context values.
|
|
112
111
|
const mapContextValue = useMemo(() => {
|
|
@@ -329,6 +328,8 @@ function MobilityMap(props: MobilityMapProps) {
|
|
|
329
328
|
);
|
|
330
329
|
}
|
|
331
330
|
|
|
331
|
+
const MemoMobilityMap = memo(MobilityMap);
|
|
332
|
+
|
|
332
333
|
// We creates a wrapper to inject the default props values from MobilityMapAttributes.
|
|
333
334
|
const defaultProps: Partial<MobilityMapProps> = {};
|
|
334
335
|
Object.entries(MobilityMapAttributes).forEach(([key]) => {
|
|
@@ -336,14 +337,7 @@ Object.entries(MobilityMapAttributes).forEach(([key]) => {
|
|
|
336
337
|
});
|
|
337
338
|
|
|
338
339
|
function MobilityMapWithDefaultProps(props: MobilityMapProps) {
|
|
339
|
-
|
|
340
|
-
const { permalinktemplate } = props;
|
|
341
|
-
const { permalinktemplate: defaultPermalinkTemplate } = defaultProps;
|
|
342
|
-
const propsFromPermalink = useInitialPermalink(
|
|
343
|
-
permalinktemplate || defaultPermalinkTemplate,
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
return <MobilityMap {...defaultProps} {...props} {...propsFromPermalink} />;
|
|
340
|
+
return <MemoMobilityMap {...defaultProps} {...props} />;
|
|
347
341
|
}
|
|
348
342
|
|
|
349
343
|
export default memo(MobilityMapWithDefaultProps);
|
|
@@ -173,9 +173,11 @@ where:
|
|
|
173
173
|
mainlink: {
|
|
174
174
|
description:
|
|
175
175
|
"A link displayed on bottom left of the map. The link can be a template, for example you can use {{x}} {{y}} {{z}} to insert the current position of the map in the url.<br/>Ex: http://mywebsite/mypage#map/{{x}}/{{y}}/{{z}}.",
|
|
176
|
+
public: true,
|
|
176
177
|
},
|
|
177
178
|
mainlinktitle: {
|
|
178
179
|
description: "A title for the mainlink, used as tooltip.",
|
|
180
|
+
public: true,
|
|
179
181
|
},
|
|
180
182
|
mapset: {
|
|
181
183
|
defaultValue: "false",
|
|
@@ -184,6 +186,7 @@ where:
|
|
|
184
186
|
type: "boolean",
|
|
185
187
|
},
|
|
186
188
|
mapsetbbox: {
|
|
189
|
+
defaultValue: MAX_EXTENT.join(","),
|
|
187
190
|
description:
|
|
188
191
|
"The BBOX to constrain the boundary of the mapset layer in EPSG:3857 coordinates. Mandatory for mapset layer. <br/>Ex: 831634,5933959,940649,6173660 .",
|
|
189
192
|
public: false,
|
|
@@ -193,9 +196,9 @@ where:
|
|
|
193
196
|
public: false,
|
|
194
197
|
},
|
|
195
198
|
mapsettenants: {
|
|
196
|
-
defaultValue: "
|
|
199
|
+
defaultValue: "rvf",
|
|
197
200
|
description: `The ${geopsMapsetApiLink} tenant to get the mapset from.`,
|
|
198
|
-
public:
|
|
201
|
+
public: false,
|
|
199
202
|
},
|
|
200
203
|
mapseturl: {
|
|
201
204
|
defaultValue: "https://editor.mapset.io/api/v1",
|
|
@@ -252,14 +255,14 @@ where:
|
|
|
252
255
|
permalink: {
|
|
253
256
|
defaultValue: "false",
|
|
254
257
|
description:
|
|
255
|
-
"
|
|
256
|
-
public:
|
|
258
|
+
"if true, the current browser window url will be updated automatically with the parameters defined in the `permalinktemplate` attribute.",
|
|
259
|
+
public: true,
|
|
257
260
|
type: "boolean",
|
|
258
261
|
},
|
|
259
262
|
permalinktemplate: {
|
|
260
263
|
defaultValue: "#map/{{x}}/{{y}}/{{z}}",
|
|
261
264
|
description: `A template string to read the current browser url. Hash (starting with #) and URL search parameters (starting with ?) are supported.<br/>
|
|
262
|
-
The template supports {{x}}, {{y}}, {{z}} variables.<br/>
|
|
265
|
+
The template supports {{x}}, {{y}}, {{z}} and {{layers}} variables.<br/>
|
|
263
266
|
Ex: "?x={{x}}&y={{y}}&z={{z}}" or "#map/{{x}}/{{y}}/{{z}}" .`,
|
|
264
267
|
public: true,
|
|
265
268
|
},
|
|
@@ -235,65 +235,83 @@ function NotificationDetails({
|
|
|
235
235
|
);
|
|
236
236
|
},
|
|
237
237
|
)}
|
|
238
|
-
<div
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
<div className="mt-4">
|
|
261
|
-
<div className={"font-bold"}>{t("affected_lines")}:</div>
|
|
262
|
-
<div className={"flex flex-wrap gap-1 text-sm"}>
|
|
263
|
-
{pubLines?.map((name) => {
|
|
264
|
-
return (
|
|
265
|
-
<div
|
|
266
|
-
className={
|
|
267
|
-
"rounded-md bg-black px-2 py-1 font-bold text-white"
|
|
268
|
-
}
|
|
269
|
-
key={name}
|
|
270
|
-
>
|
|
271
|
-
{name}
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
})}
|
|
238
|
+
<div className={"my-4 flex flex-col gap-4"}>
|
|
239
|
+
<div
|
|
240
|
+
dangerouslySetInnerHTML={{
|
|
241
|
+
__html:
|
|
242
|
+
textualContent?.description ||
|
|
243
|
+
t("no_details_available"),
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
{!!textualContentMultilingual?.images?.length && (
|
|
247
|
+
<div className="flex flex-wrap gap-2">
|
|
248
|
+
{textualContentMultilingual.images.map(
|
|
249
|
+
({ image: { absoluteUrl, label } }) => {
|
|
250
|
+
return (
|
|
251
|
+
<img
|
|
252
|
+
alt={label}
|
|
253
|
+
key={absoluteUrl + label}
|
|
254
|
+
src={absoluteUrl}
|
|
255
|
+
title={label}
|
|
256
|
+
/>
|
|
257
|
+
);
|
|
258
|
+
},
|
|
259
|
+
)}
|
|
275
260
|
</div>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
>
|
|
290
|
-
{name}
|
|
291
|
-
</div>
|
|
292
|
-
);
|
|
293
|
-
})}
|
|
261
|
+
)}
|
|
262
|
+
{!!textualContentMultilingual?.infoLinks?.length && (
|
|
263
|
+
<div>
|
|
264
|
+
{textualContentMultilingual.infoLinks.map(
|
|
265
|
+
({ label, uri }) => {
|
|
266
|
+
const title = label[locale()] || label.de || uri;
|
|
267
|
+
return (
|
|
268
|
+
<Link href={uri} key={uri} title={title}>
|
|
269
|
+
{title}
|
|
270
|
+
</Link>
|
|
271
|
+
);
|
|
272
|
+
},
|
|
273
|
+
)}
|
|
294
274
|
</div>
|
|
295
|
-
|
|
296
|
-
|
|
275
|
+
)}
|
|
276
|
+
{!!pubLines?.length && (
|
|
277
|
+
<div>
|
|
278
|
+
<div className={"font-bold"}>{t("affected_lines")}:</div>
|
|
279
|
+
<div className={"flex flex-wrap gap-1 text-sm"}>
|
|
280
|
+
{pubLines?.map((name) => {
|
|
281
|
+
return (
|
|
282
|
+
<div
|
|
283
|
+
className={
|
|
284
|
+
"rounded-md bg-black px-2 py-1 font-bold text-white"
|
|
285
|
+
}
|
|
286
|
+
key={name}
|
|
287
|
+
>
|
|
288
|
+
{name}
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
})}
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
)}
|
|
295
|
+
{!!stations?.length && (
|
|
296
|
+
<div>
|
|
297
|
+
<div className={"font-bold"}>{t("affected_stops")}:</div>
|
|
298
|
+
<div className={"flex flex-wrap gap-1 text-sm"}>
|
|
299
|
+
{stations.map((name) => {
|
|
300
|
+
return (
|
|
301
|
+
<div
|
|
302
|
+
className={
|
|
303
|
+
"rounded-md bg-black px-2 py-1 font-bold text-white"
|
|
304
|
+
}
|
|
305
|
+
key={name}
|
|
306
|
+
>
|
|
307
|
+
{name}
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
})}
|
|
311
|
+
</div>
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
297
315
|
</div>
|
|
298
316
|
);
|
|
299
317
|
},
|
|
@@ -6,8 +6,8 @@ import { useCallback, useEffect } from "preact/hooks";
|
|
|
6
6
|
|
|
7
7
|
import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
|
|
8
8
|
import getPermalinkParameters from "../utils/getPermalinkParameters";
|
|
9
|
-
// import getLayersAsFlatArray from "../getLayersAsFlatArray";
|
|
10
9
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
10
|
+
import usePermalink from "../utils/hooks/usePermalink";
|
|
11
11
|
|
|
12
12
|
import type { Map } from "ol";
|
|
13
13
|
import type { EventsKey } from "ol/events";
|
|
@@ -20,6 +20,9 @@ import type { EventsKey } from "ol/events";
|
|
|
20
20
|
const Permalink = ({ replaceState = false }: { replaceState?: boolean }) => {
|
|
21
21
|
const { map, setPermalinkUrlSearchParams } = useMapContext();
|
|
22
22
|
|
|
23
|
+
const permalink = usePermalink();
|
|
24
|
+
|
|
25
|
+
// Update the search parameters when the map moves
|
|
23
26
|
const updatePermalink = useCallback(
|
|
24
27
|
(currentMap: Map) => {
|
|
25
28
|
// No update when exporting
|
|
@@ -28,15 +31,23 @@ const Permalink = ({ replaceState = false }: { replaceState?: boolean }) => {
|
|
|
28
31
|
}
|
|
29
32
|
const currentUrlParams = new URLSearchParams(window.location.search);
|
|
30
33
|
const urlParams = getPermalinkParameters(currentMap, currentUrlParams);
|
|
31
|
-
urlParams.set("permalink", "true");
|
|
32
34
|
setPermalinkUrlSearchParams(urlParams);
|
|
33
|
-
if (replaceState) {
|
|
34
|
-
window.history.replaceState(null, null, `?${urlParams.toString()}`);
|
|
35
|
-
}
|
|
36
35
|
},
|
|
37
|
-
[map,
|
|
36
|
+
[map, setPermalinkUrlSearchParams],
|
|
38
37
|
);
|
|
39
38
|
|
|
39
|
+
// Replace the window url when permalink changes
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (
|
|
42
|
+
replaceState &&
|
|
43
|
+
permalink &&
|
|
44
|
+
permalink !== window.location.href &&
|
|
45
|
+
!permalink.includes(encodeURIComponent("{{"))
|
|
46
|
+
) {
|
|
47
|
+
window.history.replaceState(null, null, permalink);
|
|
48
|
+
}
|
|
49
|
+
}, [map, permalink, replaceState]);
|
|
50
|
+
|
|
40
51
|
useEffect(() => {
|
|
41
52
|
let moveEndKey: EventsKey;
|
|
42
53
|
let loadEndKey: EventsKey;
|
|
@@ -2,6 +2,7 @@ import { memo } from "preact/compat";
|
|
|
2
2
|
|
|
3
3
|
import InputCopy from "../ui/InputCopy";
|
|
4
4
|
import useI18n from "../utils/hooks/useI18n";
|
|
5
|
+
import usePermalink from "../utils/hooks/usePermalink";
|
|
5
6
|
|
|
6
7
|
import type { HTMLAttributes, PreactDOMAttributes } from "preact";
|
|
7
8
|
|
|
@@ -19,9 +20,11 @@ function PermalinkInput({
|
|
|
19
20
|
...props
|
|
20
21
|
}: PermalinkInputProps) {
|
|
21
22
|
const { t } = useI18n();
|
|
23
|
+
const permalink = usePermalink();
|
|
24
|
+
|
|
22
25
|
return (
|
|
23
26
|
<div {...props}>
|
|
24
|
-
<InputCopy value={
|
|
27
|
+
<InputCopy value={permalink} {...inputProps} readonly={true} />
|
|
25
28
|
<p className="py-2">{t("permalink_input_hint")}</p>
|
|
26
29
|
</div>
|
|
27
30
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from "
|
|
1
|
+
export { default } from "../RvfRealtimeLayer";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Copyright from "../Copyright/Copyright";
|
|
2
|
+
|
|
3
|
+
import type { CopyrightProps } from "../Copyright/Copyright";
|
|
4
|
+
|
|
5
|
+
const format = (copyrights) => {
|
|
6
|
+
const newCopyrights = [];
|
|
7
|
+
let alreadyAGeopsLink = false;
|
|
8
|
+
copyrights.forEach((copyright) => {
|
|
9
|
+
if (/geops/i.test(copyright)) {
|
|
10
|
+
// Hack until all geOps tiles are the same
|
|
11
|
+
// Currently there is some geops.com or geops.ch links
|
|
12
|
+
if (!alreadyAGeopsLink) {
|
|
13
|
+
alreadyAGeopsLink = true;
|
|
14
|
+
newCopyrights.push(copyright);
|
|
15
|
+
}
|
|
16
|
+
} else if (!/(sbb|rvf)/i.test(copyright)) {
|
|
17
|
+
newCopyrights.push(copyright);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return newCopyrights.join(" | ");
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const options = {
|
|
25
|
+
format,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function RvfCopyright(props: CopyrightProps) {
|
|
29
|
+
return <Copyright options={options} {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default RvfCopyright;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfCopyright";
|
|
@@ -1,37 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useId, useState } from "preact/hooks";
|
|
2
2
|
|
|
3
3
|
import Copy from "../icons/Copy";
|
|
4
4
|
import IconButton from "../ui/IconButton";
|
|
5
|
+
import useI18n from "../utils/hooks/useI18n";
|
|
5
6
|
|
|
6
7
|
import type { JSX, PreactDOMAttributes } from "preact";
|
|
7
8
|
|
|
8
9
|
function RvfInputCopy(props: JSX.InputHTMLAttributes & PreactDOMAttributes) {
|
|
10
|
+
const { t } = useI18n();
|
|
9
11
|
const [positionTooltip, setPositionTooltip] = useState<DOMRect>();
|
|
10
12
|
const [isTooptipShowed, setIsTooltipShowed] = useState(false);
|
|
11
|
-
const
|
|
13
|
+
const inputId = useId();
|
|
14
|
+
const [node, setNode] = useState<HTMLDivElement | null>(null);
|
|
12
15
|
|
|
13
16
|
const handleCopyClick = (event) => {
|
|
14
17
|
setPositionTooltip(event.currentTarget.getBoundingClientRect());
|
|
15
|
-
|
|
18
|
+
const input: HTMLInputElement | null = node.querySelector(`#${inputId}`);
|
|
19
|
+
void navigator.clipboard.writeText(input?.value).then(() => {
|
|
16
20
|
setIsTooltipShowed(true);
|
|
17
21
|
setTimeout(() => {
|
|
18
22
|
setIsTooltipShowed(false);
|
|
19
23
|
}, 1000);
|
|
20
24
|
});
|
|
21
|
-
|
|
25
|
+
input?.select();
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
const handleInputFocus = () => {
|
|
25
|
-
|
|
29
|
+
const input: HTMLInputElement | null = node.querySelector(`#${inputId}`);
|
|
30
|
+
input?.select();
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
return (
|
|
29
|
-
<div
|
|
34
|
+
<div
|
|
35
|
+
className="text-grey flex items-center"
|
|
36
|
+
ref={(elt) => {
|
|
37
|
+
setNode(elt);
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
30
40
|
<input
|
|
31
41
|
className="h-7 w-full rounded-sm rounded-r-none border border-r-0 border-current p-1 leading-4 outline-none"
|
|
42
|
+
id={inputId}
|
|
32
43
|
onFocus={handleInputFocus}
|
|
33
44
|
readOnly
|
|
34
|
-
ref={inputRef}
|
|
35
45
|
type="text"
|
|
36
46
|
{...props}
|
|
37
47
|
/>
|
|
@@ -48,7 +58,7 @@ function RvfInputCopy(props: JSX.InputHTMLAttributes & PreactDOMAttributes) {
|
|
|
48
58
|
top: positionTooltip?.top - 40,
|
|
49
59
|
}}
|
|
50
60
|
>
|
|
51
|
-
|
|
61
|
+
{t("input_copy_success")}
|
|
52
62
|
</div>
|
|
53
63
|
</div>
|
|
54
64
|
);
|