@geops/rvf-mobility-web-component 0.1.64 → 0.1.66

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 (43) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/docutils.js +125 -51
  3. package/index.html +48 -21
  4. package/index.js +291 -234
  5. package/package.json +12 -12
  6. package/search.html +25 -22
  7. package/src/BaseLayer/BaseLayer.tsx +18 -2
  8. package/src/Copyright/Copyright.tsx +2 -2
  9. package/src/Copyright/index.tsx +1 -1
  10. package/src/LayerTree/TreeItem/TreeItem.tsx +1 -1
  11. package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +1 -0
  12. package/src/Map/Map.tsx +27 -1
  13. package/src/MapLayout/MapLayout.tsx +1 -1
  14. package/src/MapLayout/index.tsx +1 -1
  15. package/src/MobilityMap/MobilityMap.tsx +5 -11
  16. package/src/MobilityMap/MobilityMapAttributes.ts +8 -5
  17. package/src/MobilitySearch/MobilitySearchAttributes.ts +12 -0
  18. package/src/NotificationDetails/NotificationDetails.tsx +75 -57
  19. package/src/OverlayDetailsHeader/OverlayDetailsHeader.tsx +1 -1
  20. package/src/Permalink/Permalink.tsx +17 -6
  21. package/src/PermalinkInput/PermalinkInput.tsx +4 -1
  22. package/src/RealtimeLayer/index.tsx +1 -1
  23. package/src/RvfCopyright/RvfCopyright.tsx +32 -0
  24. package/src/RvfCopyright/index.tsx +1 -0
  25. package/src/RvfInputCopy/RvfInputCopy.tsx +18 -8
  26. package/src/RvfMapLayout/RvfMapLayout.tsx +198 -0
  27. package/src/RvfMapLayout/index.tsx +1 -0
  28. package/src/RvfMobilityMap/RvfMobilityMap.tsx +10 -580
  29. package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +64 -0
  30. package/src/RvfRealtimeLayer/index.tsx +1 -0
  31. package/src/RvfStationsLayer/RvfStationsLayer.tsx +19 -0
  32. package/src/RvfStationsLayer/index.tsx +1 -0
  33. package/src/ShareMenu/ShareMenu.tsx +3 -1
  34. package/src/StationsLayer/index.tsx +1 -1
  35. package/src/ui/InputCopy/InputCopy.tsx +21 -10
  36. package/src/utils/constants.ts +1 -1
  37. package/src/utils/getUrlFromTemplate.test.ts +23 -0
  38. package/src/utils/getUrlFromTemplate.ts +47 -0
  39. package/src/utils/hooks/useI18n.tsx +2 -4
  40. package/src/utils/hooks/useInitialLayersVisiblity.tsx +27 -4
  41. package/src/utils/hooks/useInitialPermalink.tsx +31 -21
  42. package/src/utils/hooks/usePermalink.tsx +25 -0
  43. 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.66",
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/",
package/search.html CHANGED
@@ -24,15 +24,19 @@
24
24
  width: 3px;
25
25
  height: 3px;
26
26
  }
27
+
27
28
  a {
28
29
  text-decoration: underline;
29
30
  }
30
31
  </style>
31
32
  </head>
32
- </head>
33
+
33
34
  <body class="p-8">
34
35
  <!-- tailwind hack to add class used in docutils -->
35
- <div class="border px-4 py-2 table-auto w-full flex gap-4 p-2 bg-black text-white hover:bg-gray-700" style="display:none;"></div>
36
+ <div
37
+ class="flex w-full table-auto gap-4 border bg-black pt-2 mt-2 px-4 py-2 text-white hover:bg-gray-700"
38
+ style="display: none"
39
+ ></div>
36
40
  <div
37
41
  id="doc"
38
42
  style="display: none"
@@ -43,7 +47,9 @@
43
47
  <p>This web component launches a search on the <b>geOps Stops API</b></p>
44
48
  <p>Every parameters of the <b>geOps Stops API</b>:</p>
45
49
  <ul class="pl-8">
46
- <li class="list-disc">can be passed as a string attribute of the web component.</li>
50
+ <li class="list-disc">
51
+ can be passed as a string attribute of the web component.
52
+ </li>
47
53
  <li class="list-disc">
48
54
  can be passed as an URL parameter of this page, they will be
49
55
  automatically apply to the web component.
@@ -51,30 +57,34 @@
51
57
  </ul>
52
58
  <p>
53
59
  The list of parameters of the <b>geOps Stops API</b> can be found
54
- <a href="https://developer.geops.io/apis/stops#parameters" target="_blank">here</a>.
60
+ <a
61
+ href="https://developer.geops.io/apis/stops#parameters"
62
+ target="_blank"
63
+ >here</a
64
+ >.
55
65
  </p>
56
66
 
57
67
  <h2 class="text-xl font-bold">Usage example</h2>
58
- <pre id="code" class="bg-slate-800 text-slate-200 p-4 rounded"></pre>
68
+ <pre id="code" class="rounded bg-slate-800 p-4 text-slate-200"></pre>
59
69
 
60
70
  <geops-mobility-search
61
71
  id="search"
62
- class="max-w-3xl block border"
72
+ class="block max-w-3xl border"
63
73
  limit="5"
64
74
  mots="rail,bus"
65
75
  ></geops-mobility-search>
66
76
 
67
- <br />
68
- <div id="attributesDoc">
77
+ <div id="attributesDoc" class="space-y-4">
69
78
  <h2 class="text-xl font-bold">Attributes</h2>
70
79
  <div id="attributes"></div>
71
80
  </div>
72
- <div id="eventsDoc">
81
+ <div id="eventsDoc" class="space-y-4">
73
82
  <h2 class="text-xl font-bold">Events</h2>
74
83
  <pre class="rounded bg-slate-800 p-4 text-slate-200">
75
84
  document.getElementById('search').addEventListener('mwc:attribute', (event) => {
76
85
  console.log('Display last data received:', event.data);
77
- });</pre
86
+ });
87
+ </pre
78
88
  >
79
89
  <div id="events"></div>
80
90
  </div>
@@ -83,9 +93,11 @@ document.getElementById('search').addEventListener('mwc:attribute', (event) => {
83
93
  <h1 class="text-xl font-bold">More mobility web components</h1>
84
94
  <p>
85
95
  <a href="index.html" target="_blank"
86
- >&gt;&gt; Usage example Map Component</a
87
- >
96
+ >&gt;&gt; Usage example Map Component
97
+ </a>
88
98
  </p>
99
+ <br />
100
+ <br />
89
101
  </div>
90
102
  <br />
91
103
  <br />
@@ -94,24 +106,15 @@ document.getElementById('search').addEventListener('mwc:attribute', (event) => {
94
106
  const wc = document.querySelector("geops-mobility-search");
95
107
 
96
108
  window.addEventListener("load", () => {
97
-
98
109
  const attributes = window.MobilitySearchAttributes;
99
110
  const events = window.MobilitySearchEvents;
100
- const descriptionByEvent = {
111
+ const descriptionByEvent = {
101
112
  "mwc:stopssearchselect":
102
113
  "Only when search attribute is 'true'. Event fired when a stop is selected in the stops search results. The event data contains the selected stop.",
103
114
  "mwc:attribute":
104
115
  "Event fired when an web component's attribute is changed. The event data contains the list of all attributes and their values.",
105
116
  };
106
117
 
107
- // Add special parameters
108
- window.MobilityMapAttributes.fullscreen = {
109
- type: "boolean",
110
- defaultValue: "false",
111
- description: `Load the page in fullscreen mode.`,
112
- reload: true,
113
- };
114
-
115
118
  onLoad(wc, attributes, events, pkgSrc);
116
119
  });
117
120
  </script>
@@ -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";
@@ -129,7 +129,7 @@ function TreeItem({
129
129
  className={`flex-1 cursor-pointer`}
130
130
  htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
131
131
  >
132
- {typeof title === "string" ? t(title) : title}
132
+ {typeof title === "string" ? t(title) || title : title}
133
133
  </label>
134
134
  {renderedLayers.length > 0 && (
135
135
  <button
@@ -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
  },
@@ -25,43 +25,55 @@ export type MobilitySearchAttributes = Record<
25
25
  const attrs: MobilitySearchAttributes = {
26
26
  apikey: {
27
27
  description: `Your ${geopsApiLink}`,
28
+ public: true,
28
29
  },
29
30
  bbox: {
30
31
  description: `The extent where to search the stops (minx,miny,maxx,maxy). See the ${geopsStopsApiLink} documentation.`,
32
+ public: true,
31
33
  },
32
34
  countrycode: {
33
35
  description: "The country code to filter the results (IT, DE, CH ...)",
36
+ public: true,
34
37
  },
35
38
  event: {
36
39
  defaultValue: "mwc:stopssearchselect",
37
40
  description: "The event's name to listen to when a stop is selected.",
41
+ public: true,
38
42
  },
39
43
  field: {
40
44
  description: `Which field to look up, default all of them, Possible values:id, name, coords. See the ${geopsStopsApiLink} documentation.`,
45
+ public: true,
41
46
  },
42
47
  limit: {
43
48
  defaultValue: "5",
44
49
  description: `The number of suggestions to show. See the ${geopsStopsApiLink} documentation.`,
50
+ public: true,
45
51
  },
46
52
  mots: {
47
53
  description: `Commas separated list of mots used to filter the results (rail, bus, coach, foot, tram, subway, gondola, funicular, ferry, car). See the ${geopsStopsApiLink} documentation.`,
54
+ public: true,
48
55
  },
49
56
  onselect: {
50
57
  description: null, //`Function called when a stop is selected. The function receives the selected stop as parameter.`,
58
+ public: false,
51
59
  },
52
60
  params: {
53
61
  description: `JSON string with additional parameters to pass to the request to the API. Ex: {"{ 'key': 'value' }"}. See the ${geopsStopsApiLink} documentation.`,
62
+ public: true,
54
63
  },
55
64
  prefagencies: {
56
65
  description: `comma seperated list, order chooses which agency will be preferred as
57
66
  ident_source (for id and code fields). Possible values: sbb, db. See the ${geopsStopsApiLink} documentation.`,
67
+ public: true,
58
68
  },
59
69
  reflocation: {
60
70
  description: `Coordinates in WGS84 (in lat,lon order) used to rank stops close to this position higher. See the ${geopsStopsApiLink} documentation.`,
71
+ public: true,
61
72
  },
62
73
  url: {
63
74
  defaultValue: "https://api.geops.io/stops/v1/",
64
75
  description: `The URL to the ${geopsStopsApiLink}.`,
76
+ public: true,
65
77
  },
66
78
  };
67
79
 
@@ -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
  },
@@ -24,7 +24,7 @@ function OverlayDetailsHeader({
24
24
  const layerConfig = useLayerConfig(layer?.get("name"));
25
25
  return (
26
26
  <OverlayHeader
27
- title={t(layerConfig?.title || "")}
27
+ title={t(layerConfig?.title || "") || (layerConfig?.title ?? "")}
28
28
  {...props}
29
29
  ></OverlayHeader>
30
30
  );