@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/docutils.js +29 -19
  3. package/index.html +0 -1
  4. package/index.js +276 -235
  5. package/package.json +12 -12
  6. package/src/BaseLayer/BaseLayer.tsx +18 -2
  7. package/src/Copyright/Copyright.tsx +2 -2
  8. package/src/Copyright/index.tsx +1 -1
  9. package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +1 -0
  10. package/src/Map/Map.tsx +27 -1
  11. package/src/MapLayout/MapLayout.tsx +1 -1
  12. package/src/MapLayout/index.tsx +1 -1
  13. package/src/MobilityMap/MobilityMap.tsx +5 -11
  14. package/src/MobilityMap/MobilityMapAttributes.ts +8 -5
  15. package/src/NotificationDetails/NotificationDetails.tsx +75 -57
  16. package/src/Permalink/Permalink.tsx +17 -6
  17. package/src/PermalinkInput/PermalinkInput.tsx +4 -1
  18. package/src/RealtimeLayer/index.tsx +1 -1
  19. package/src/RvfCopyright/RvfCopyright.tsx +32 -0
  20. package/src/RvfCopyright/index.tsx +1 -0
  21. package/src/RvfInputCopy/RvfInputCopy.tsx +18 -8
  22. package/src/RvfMapLayout/RvfMapLayout.tsx +198 -0
  23. package/src/RvfMapLayout/index.tsx +1 -0
  24. package/src/RvfMobilityMap/RvfMobilityMap.tsx +10 -580
  25. package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +64 -0
  26. package/src/RvfRealtimeLayer/index.tsx +1 -0
  27. package/src/RvfStationsLayer/RvfStationsLayer.tsx +19 -0
  28. package/src/RvfStationsLayer/index.tsx +1 -0
  29. package/src/ShareMenu/ShareMenu.tsx +3 -1
  30. package/src/StationsLayer/index.tsx +1 -1
  31. package/src/ui/InputCopy/InputCopy.tsx +21 -10
  32. package/src/utils/constants.ts +1 -1
  33. package/src/utils/getUrlFromTemplate.test.ts +23 -0
  34. package/src/utils/getUrlFromTemplate.ts +47 -0
  35. package/src/utils/hooks/useI18n.tsx +2 -4
  36. package/src/utils/hooks/useInitialLayersVisiblity.tsx +27 -4
  37. package/src/utils/hooks/useInitialPermalink.tsx +31 -21
  38. package/src/utils/hooks/usePermalink.tsx +25 -0
  39. 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.64",
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.8.0",
15
- "mobility-toolbox-js": "3.4.4-beta.0",
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.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.36.0",
29
- "@geops/eslint-config-react": "^1.5.0",
30
- "@tailwindcss/cli": "^4.1.13",
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.36.0",
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.2",
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.13",
56
- "ts-jest": "^29.4.4",
55
+ "tailwindcss": "^4.1.14",
56
+ "ts-jest": "^29.4.5",
57
57
  "typescript": "^5.9.3",
58
- "typescript-eslint": "^8.45.0"
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 } = useMapContext();
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, mapsurl]);
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 { JSX, PreactDOMAttributes } from "preact";
10
+ import type { HTMLAttributes, PreactDOMAttributes } from "preact";
11
11
 
12
12
  export type CopyrightProps = {
13
13
  options?: CopyrightControlOptions;
14
- } & JSX.HTMLAttributes<HTMLDivElement> &
14
+ } & HTMLAttributes<HTMLDivElement> &
15
15
  PreactDOMAttributes;
16
16
 
17
17
  function Copyright({ options, ...props }: CopyrightProps) {
@@ -1 +1 @@
1
- export { default } from "./Copyright";
1
+ export { default } from "../RvfCopyright";
@@ -33,6 +33,7 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) {
33
33
  );
34
34
  },
35
35
  maplibreLayer: baseLayer,
36
+ visible: false,
36
37
  ...(props || {}),
37
38
  });
38
39
  }, [baseLayer, props]);
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-80",
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
  >
@@ -1 +1 @@
1
- export { default } from "./MapLayout";
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
- // Apply initial value from permalink, only x,y,z
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: "geopstest",
199
+ defaultValue: "rvf",
197
200
  description: `The ${geopsMapsetApiLink} tenant to get the mapset from.`,
198
- public: true,
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
- "Update some url parameters x,y,z,layers to the current window location. These parameters are used to store the current state of the map. They will be used on page load to configure the web-component.",
256
- public: false,
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
- className="mt-4"
240
- dangerouslySetInnerHTML={{
241
- __html:
242
- textualContent?.description || t("no_details_available"),
243
- }}
244
- />
245
- {!!textualContentMultilingual?.infoLinks?.length && (
246
- <div className="mt-4">
247
- {textualContentMultilingual.infoLinks.map(
248
- ({ label, uri }) => {
249
- const title = label[locale()] || label.de || uri;
250
- return (
251
- <Link href={uri} key={uri} title={title}>
252
- {title}
253
- </Link>
254
- );
255
- },
256
- )}
257
- </div>
258
- )}
259
- {!!pubLines?.length && (
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
- </div>
277
- )}
278
- {!!stations?.length && (
279
- <div className="mt-4">
280
- <div className={"font-bold"}>{t("affected_stops")}:</div>
281
- <div className={"flex flex-wrap gap-1 text-sm"}>
282
- {stations.map((name) => {
283
- return (
284
- <div
285
- className={
286
- "rounded-md bg-black px-2 py-1 font-bold text-white"
287
- }
288
- key={name}
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
- </div>
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, replaceState, setPermalinkUrlSearchParams],
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={window?.location.href} {...inputProps} />
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 "./RealtimeLayer";
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("&nbsp;|&nbsp;");
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 { useRef, useState } from "preact/hooks";
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 inputRef = useRef(null);
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
- void navigator.clipboard.writeText(window?.location.href).then(() => {
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
- inputRef.current.select();
25
+ input?.select();
22
26
  };
23
27
 
24
28
  const handleInputFocus = () => {
25
- inputRef.current.select();
29
+ const input: HTMLInputElement | null = node.querySelector(`#${inputId}`);
30
+ input?.select();
26
31
  };
27
32
 
28
33
  return (
29
- <div className="text-grey flex items-center">
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
- Link kopiert!
61
+ {t("input_copy_success")}
52
62
  </div>
53
63
  </div>
54
64
  );