@geops/rvf-mobility-web-component 0.1.59 → 0.1.61

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 (57) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +16 -123
  3. package/docutils.js +1 -1
  4. package/index.html +4 -0
  5. package/index.js +154 -151
  6. package/package.json +1 -1
  7. package/src/ExportMenu/ExportMenu.tsx +7 -5
  8. package/src/ExportMenuButton/ExportMenuButton.tsx +8 -1
  9. package/src/GeolocationButton/GeolocationButton.tsx +9 -3
  10. package/src/LayerTree/TreeItem/TreeItem.tsx +3 -1
  11. package/src/LayerTreeButton/LayerTreeButton.tsx +10 -4
  12. package/src/LayerTreeMenu/LayerTreeMenu.tsx +15 -5
  13. package/src/LinesNetworkPlanLayer/LinesNetworkPlanLayer.tsx +5 -2
  14. package/src/MapDispatchEvents/MapDispatchEvents.tsx +7 -0
  15. package/src/MapLayout/MapLayout.tsx +176 -0
  16. package/src/MapLayout/index.tsx +1 -0
  17. package/src/MobilityMap/MobilityMap.tsx +25 -139
  18. package/src/MobilityMap/MobilityMapAttributes.ts +15 -3
  19. package/src/NotificationDetails/NotificationDetails.tsx +23 -8
  20. package/src/OverlayContent/OverlayContent.tsx +5 -3
  21. package/src/OverlayDetails/OverlayDetails.tsx +29 -11
  22. package/src/OverlayDetailsHeader/OverlayDetailsHeader.tsx +11 -14
  23. package/src/Permalink/Permalink.tsx +5 -5
  24. package/src/PermalinkInput/PermalinkInput.tsx +6 -4
  25. package/src/RealtimeLayer/RealtimeLayer.tsx +10 -9
  26. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +1 -1
  27. package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +23 -8
  28. package/src/RvfMobilityMap/RvfMobilityMap.tsx +44 -41
  29. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +6 -6
  30. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +76 -58
  31. package/src/Search/Search.tsx +1 -1
  32. package/src/SearchButton/SearchButton.tsx +10 -4
  33. package/src/ShareMenu/ShareMenu.tsx +4 -2
  34. package/src/ShareMenuButton/ShareMenuButton.tsx +10 -3
  35. package/src/StationsLayer/StationsLayer.tsx +4 -1
  36. package/src/ZoomButtons/ZoomButtons.tsx +12 -2
  37. package/src/index.tsx +13 -5
  38. package/src/utils/constants.ts +20 -40
  39. package/src/utils/getPermalinkParameters.ts +12 -4
  40. package/src/utils/hooks/useInitialPermalink.tsx +83 -0
  41. package/src/utils/hooks/useLayerConfig.tsx +1 -3
  42. package/src/utils/hooks/useLayersConfig.tsx +2 -2
  43. package/src/utils/i18n.ts +0 -10
  44. package/src/utils/translations.ts +165 -0
  45. package/src/RvfFeatureDetailsFooter/RvfFeatureDetailsFooter.tsx +0 -43
  46. package/src/RvfFeatureDetailsFooter/index.tsx +0 -1
  47. package/src/RvfFeatureDetailsTitle/RvfFeatureDetailsTitle.tsx +0 -81
  48. package/src/RvfFeatureDetailsTitle/index.tsx +0 -1
  49. package/src/RvfLayerTree/RvfLayerTree.tsx +0 -40
  50. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +0 -145
  51. package/src/RvfLayerTree/TreeItem/index.tsx +0 -1
  52. package/src/RvfLayerTree/index.tsx +0 -1
  53. package/src/RvfLayerTree/layersTreeContext.ts +0 -4
  54. package/src/RvfLayerTree/layersTreeReducer.ts +0 -158
  55. package/src/RvfOverlayContent/RvfOverlayContent.tsx +0 -128
  56. package/src/RvfOverlayContent/index.ts +0 -0
  57. package/src/utils/hooks/useUpdatePermalink.tsx +0 -76
@@ -1,34 +1,23 @@
1
1
  import { memo } from "preact/compat";
2
- import { useMemo, useRef, useState } from "preact/hooks";
3
- import { twMerge } from "tailwind-merge";
2
+ import { useEffect, useMemo, useRef, useState } from "preact/hooks";
4
3
 
5
4
  import BaseLayer from "../BaseLayer";
6
- import Copyright from "../Copyright";
7
- import EmbedNavigation from "../EmbedNavigation";
8
- import ExportMenuButton from "../ExportMenuButton";
9
5
  import FeaturesInfosListener from "../FeaturesInfosListener";
10
- import GeolocationButton from "../GeolocationButton";
11
- import LayerTreeButton from "../LayerTreeButton";
12
6
  import LayoutState from "../LayoutState";
13
7
  import LinesNetworkPlanLayer from "../LinesNetworkPlanLayer";
14
- import Map from "../Map";
15
8
  import MapDispatchEvents from "../MapDispatchEvents";
9
+ import MapLayout from "../MapLayout";
16
10
  import NotificationsLayer from "../NotificationsLayer";
17
- import Overlay from "../Overlay";
18
- import OverlayContent from "../OverlayContent";
19
11
  import Permalink from "../Permalink";
20
12
  import RealtimeLayer from "../RealtimeLayer";
21
- import ScaleLine from "../ScaleLine";
22
- import Search from "../Search";
23
- import SearchButton from "../SearchButton";
24
- import ShareMenuButton from "../ShareMenuButton";
25
13
  import SingleClickListener from "../SingleClickListener";
26
14
  import StationsLayer from "../StationsLayer";
27
15
  import { I18nContext } from "../utils/hooks/useI18n";
16
+ import useInitialLayersVisiblity from "../utils/hooks/useInitialLayersVisiblity";
17
+ import useInitialPermalink from "../utils/hooks/useInitialPermalink";
28
18
  import { MapContext } from "../utils/hooks/useMapContext";
29
19
  import i18n from "../utils/i18n";
30
20
  import WindowMessageListener from "../WindowMessageListener";
31
- import ZoomButtons from "../ZoomButtons";
32
21
 
33
22
  import MobilityMapAttributes from "./MobilityMapAttributes";
34
23
 
@@ -60,10 +49,6 @@ export type MobilityMapProps = Record<
60
49
  null | string | undefined
61
50
  >;
62
51
 
63
- const scrollableHandlerProps = {
64
- style: { width: "calc(100% - 60px)" },
65
- };
66
-
67
52
  function MobilityMap(props: MobilityMapProps) {
68
53
  const eventNodeRef = useRef<HTMLDivElement>();
69
54
  const [baseLayer, setBaseLayer] = useState<MaplibreLayer>();
@@ -111,6 +96,11 @@ function MobilityMap(props: MobilityMapProps) {
111
96
  const [previewNotifications, setPreviewNotifications] =
112
97
  useState<SituationType[]>();
113
98
 
99
+ const { lang, layers } = props;
100
+
101
+ // Apply initial visibility of layers
102
+ useInitialLayersVisiblity(map, layers);
103
+
114
104
  // Object representing all the web-component attributes and the map context values.
115
105
  const mapContextValue = useMemo(() => {
116
106
  return {
@@ -130,6 +120,7 @@ function MobilityMap(props: MobilityMapProps) {
130
120
  hasRealtime,
131
121
  hasSearch,
132
122
  hasShare,
123
+ hasStations,
133
124
  hasToolbar,
134
125
  isEmbed,
135
126
  isExportMenuOpen,
@@ -195,6 +186,7 @@ function MobilityMap(props: MobilityMapProps) {
195
186
  featuresInfos,
196
187
  featuresInfosHovered,
197
188
  hasDetails,
189
+ hasStations,
198
190
  hasGeolocation,
199
191
  hasLayerTree,
200
192
  hasLnp,
@@ -228,6 +220,10 @@ function MobilityMap(props: MobilityMapProps) {
228
220
  trainId,
229
221
  ]);
230
222
 
223
+ useEffect(() => {
224
+ i18n.locale(lang);
225
+ }, [lang]);
226
+
231
227
  return (
232
228
  <I18nContext.Provider value={i18n}>
233
229
  {/* There is a bug in tailwindcss@4 , variables are not imported in the shadow dom
@@ -304,124 +300,7 @@ function MobilityMap(props: MobilityMapProps) {
304
300
  className="@container/main relative size-full border font-sans"
305
301
  ref={eventNodeRef}
306
302
  >
307
- <div className="relative flex size-full flex-col @lg/main:flex-row-reverse">
308
- <Map className="relative flex-1 overflow-visible">
309
- <EmbedNavigation />
310
-
311
- <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
312
- <ScaleLine className="bg-slate-50/70" />
313
- <Copyright className="bg-slate-50/70" />
314
- </div>
315
-
316
- <div className="absolute right-2 bottom-10 z-10 flex flex-col justify-between gap-2">
317
- <ZoomButtons />
318
- </div>
319
-
320
- {hasGeolocation && (
321
- <div className="absolute top-2 right-2 z-10 flex flex-col gap-2">
322
- <GeolocationButton />
323
- </div>
324
- )}
325
-
326
- {!hasToolbar && hasSearch && (
327
- <div className="absolute top-2 right-12 left-2 z-10 flex max-h-[90%] max-w-96 min-w-64 flex-col">
328
- <Search />
329
- </div>
330
- )}
331
- </Map>
332
-
333
- <div className="pointer-events-none absolute top-2 bottom-2 left-2 z-10 flex flex-col gap-2 *:pointer-events-auto">
334
- <div
335
- className={
336
- "relative z-10 w-fit rounded-2xl bg-black/10 p-0 backdrop-blur-sm"
337
- }
338
- >
339
- {hasToolbar && (
340
- <div
341
- className={twMerge(
342
- "border-grey relative z-10 flex gap-[1px] overflow-hidden rounded-2xl border",
343
- "*:size-[46px] *:rounded-none *:border-none",
344
- "*:first:!rounded-l-2xl",
345
- "*:last:!rounded-r-2xl",
346
- isSearchOpen
347
- ? "@sm:rounded-r-none @sm:border-r-0 @sm:*:last:!rounded-r-none @sm:*:last:border-r-0"
348
- : "",
349
- )}
350
- >
351
- {hasPrint && <ExportMenuButton title={"Drucken"} />}
352
- {hasShare && <ShareMenuButton title={"Share"} />}
353
- {hasLayerTree && <LayerTreeButton title={"Layers"} />}
354
- {hasSearch && <SearchButton title={"Suche"} />}
355
- </div>
356
- )}
357
-
358
- {hasToolbar && hasSearch && (
359
- <div
360
- className={twMerge(
361
- "absolute top-14 left-0 z-5 h-[48px] w-0 p-0 opacity-0 transition-all @sm:top-0 @sm:left-[calc(100%-47px)]",
362
- isSearchOpen ? "w-64 opacity-100" : "",
363
- )}
364
- >
365
- <Search
366
- className={
367
- "border-grey @container m-0 h-[40px] gap-4 rounded-2xl border p-2 px-4 text-base @sm/main:h-[48px] @sm/main:rounded-l-none @sm/main:rounded-r-2xl"
368
- }
369
- // inputClassName="h-6 text-base"
370
- inputContainerClassName="border-none"
371
- resultClassName="text-base **:hover:cursor-pointer p-2"
372
- resultsContainerClassName="@container rounded-b-2xl max-h-[200px] overflow-y-auto border border-t-0 "
373
- withResultsClassName="text-base !rounded-b-none"
374
- />
375
- </div>
376
- )}
377
- </div>
378
-
379
- {/* Desktop (>= lg) */}
380
- {isOverlayOpen && (
381
- <div
382
- className={twMerge(
383
- "flex w-0 flex-1 flex-col overflow-hidden rounded-2xl @lg:min-w-64",
384
- )}
385
- style={{ containerType: "normal" }}
386
- >
387
- <Overlay
388
- className={
389
- "border-grey @container/overlay pointer-events-auto relative hidden flex-col overflow-hidden rounded-2xl border bg-white text-base shadow-lg @lg:flex"
390
- }
391
- ScrollableHandlerProps={scrollableHandlerProps}
392
- >
393
- <OverlayContent
394
- hasDetails={hasDetails}
395
- hasLayerTree={hasLayerTree}
396
- hasPrint={hasPrint}
397
- hasRealtime={hasRealtime}
398
- hasSearch={false}
399
- hasShare={hasShare}
400
- />
401
- </Overlay>
402
- </div>
403
- )}
404
- </div>
405
-
406
- {/* Mobile (< lg) */}
407
- {isOverlayOpen && (
408
- <Overlay
409
- className={
410
- "absolute bottom-0 z-20 flex max-h-[70%] min-h-[75px] w-full flex-col border-t bg-white @lg:hidden"
411
- }
412
- ScrollableHandlerProps={scrollableHandlerProps}
413
- >
414
- <OverlayContent
415
- hasDetails={hasDetails}
416
- hasLayerTree={hasLayerTree}
417
- hasPrint={hasPrint}
418
- hasRealtime={hasRealtime}
419
- hasSearch={false}
420
- hasShare={hasShare}
421
- />
422
- </Overlay>
423
- )}
424
- </div>
303
+ <MapLayout />
425
304
  </div>
426
305
  </MapContext.Provider>
427
306
  </I18nContext.Provider>
@@ -429,13 +308,20 @@ function MobilityMap(props: MobilityMapProps) {
429
308
  }
430
309
 
431
310
  // We creates a wrapper to inject the default props values from MobilityMapAttributes.
432
- const defaultProps = {};
311
+ const defaultProps: Partial<MobilityMapProps> = {};
433
312
  Object.entries(MobilityMapAttributes).forEach(([key]) => {
434
313
  defaultProps[key] = MobilityMapAttributes[key].defaultValue || null;
435
314
  });
436
315
 
437
316
  function MobilityMapWithDefaultProps(props: MobilityMapProps) {
438
- return <MobilityMap {...defaultProps} {...props} />;
317
+ // Apply initial value from permalink, only x,y,z
318
+ const { permalinktemplate } = props;
319
+ const { permalinktemplate: defaultPermalinkTemplate } = defaultProps;
320
+ const propsFromPermalink = useInitialPermalink(
321
+ permalinktemplate || defaultPermalinkTemplate,
322
+ );
323
+
324
+ return <MobilityMap {...defaultProps} {...propsFromPermalink} {...props} />;
439
325
  }
440
326
 
441
327
  export default memo(MobilityMapWithDefaultProps);
@@ -29,6 +29,7 @@ export type MobilityMapAttributeName =
29
29
  | "embed"
30
30
  | "extent"
31
31
  | "geolocation"
32
+ | "lang"
32
33
  | "layers"
33
34
  | "layersconfig"
34
35
  | "layertree"
@@ -45,6 +46,7 @@ export type MobilityMapAttributeName =
45
46
  | "notificationtenant"
46
47
  | "notificationurl"
47
48
  | "permalink"
49
+ | "permalinktemplate"
48
50
  | "print"
49
51
  | "queryablelayers"
50
52
  | "realtime"
@@ -99,6 +101,11 @@ const attrs: MobilityMapAttributes = {
99
101
  description: "Toggle the display of the geolocation button or not.",
100
102
  type: "boolean",
101
103
  },
104
+ lang: {
105
+ defaultValue: "de",
106
+ description: null,
107
+ // "The language to use for the map. Supported languages are : de, en, fr, it.",
108
+ },
102
109
  layers: {
103
110
  defaultValue: null,
104
111
  description: `A comma separated list of layers's name to make visible on load, others are hidden. If empty, all layers will be hidden except the baselayer.<br/>Layers available are ${Object.values(LAYERS_NAMES).join(", ")}.`,
@@ -141,7 +148,7 @@ where:
141
148
  },
142
149
  mainlink: {
143
150
  description:
144
- "A link displayed on bottom left of the map. The link can be 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#{{x}}/{{y}}/{{z}}.",
151
+ "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}}.",
145
152
  },
146
153
  mainlinktitle: {
147
154
  description: "A title for the mainlink, used as tooltip.",
@@ -184,10 +191,15 @@ where:
184
191
  },
185
192
  permalink: {
186
193
  defaultValue: "false",
187
- description:
188
- "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.",
194
+ description: null, //"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.",
189
195
  type: "boolean",
190
196
  },
197
+ permalinktemplate: {
198
+ defaultValue: "#map/{{x}}/{{y}}/{{z}}",
199
+ description: `A template string to read the current browser url. Hash (starting with #) and URL search parameters (starting with ?) are supported.<br/>
200
+ The template supports {{x}}, {{y}}, {{z}} variables.<br/>
201
+ Ex: "?x={{x}}&y={{y}}&z={{z}}" or "#map/{{x}}/{{y}}/{{z}}" .`,
202
+ },
191
203
  print: {
192
204
  defaultValue: "true",
193
205
  description: "Show/hide the print button in the toolbar.",
@@ -361,6 +361,7 @@ function NotificationDetails({
361
361
  <Warning />
362
362
  </span>
363
363
  <span
364
+ className={"*:inline"}
364
365
  dangerouslySetInnerHTML={{
365
366
  __html: textualContent?.summary,
366
367
  }}
@@ -390,12 +391,26 @@ function NotificationDetails({
390
391
  key={startTime}
391
392
  >
392
393
  <span>
393
- {`von ${toShortDate(new Date(startTime), !hasDailyTime, !isStartCurrentYear)}`}
394
- {!isEndInfinite &&
395
- ` bis ${toShortDate(new Date(endTime), !hasDailyTime, !isEndCurrentYear)}`}
394
+ {t("from_to", {
395
+ from: toShortDate(
396
+ new Date(startTime),
397
+ !hasDailyTime,
398
+ !isStartCurrentYear,
399
+ ),
400
+ to: !isEndInfinite
401
+ ? toShortDate(
402
+ new Date(endTime),
403
+ !hasDailyTime,
404
+ !isEndCurrentYear,
405
+ )
406
+ : undefined,
407
+ })}
396
408
  </span>
397
409
  {hasDailyTime && (
398
- <span>{` (täglich von ${dailyStartTime} bis ${dailyEndTime})`}</span>
410
+ <span>{` (${t("daily_from_to", {
411
+ from: dailyStartTime,
412
+ to: dailyEndTime,
413
+ })})`}</span>
399
414
  )}
400
415
  </div>
401
416
  );
@@ -405,13 +420,13 @@ function NotificationDetails({
405
420
  className="mt-4"
406
421
  dangerouslySetInnerHTML={{
407
422
  __html:
408
- textualContent?.description || "Keine Details verfügbar",
423
+ textualContent?.description || t("no_details_available"),
409
424
  }}
410
425
  />
411
426
  {!!pubLines?.length && (
412
427
  <div>
413
428
  <br />
414
- <div className={"font-bold"}>Betroffene Lines:</div>
429
+ <div className={"font-bold"}>{t("affected_lines")}:</div>
415
430
  <div className={"flex flex-wrap gap-1 text-sm"}>
416
431
  {pubLines?.map((name) => {
417
432
  return (
@@ -430,7 +445,7 @@ function NotificationDetails({
430
445
  )}
431
446
  <div>
432
447
  <br />
433
- <div className={"font-bold"}>Betroffene Haltestellen:</div>
448
+ <div className={"font-bold"}>{t("affected_stops")}:</div>
434
449
  <div className={"flex flex-wrap gap-1 text-sm"}>
435
450
  {stations?.length ? (
436
451
  stations.map((name) => {
@@ -451,7 +466,7 @@ function NotificationDetails({
451
466
  "rounded-md bg-black px-2 py-1 font-bold text-white"
452
467
  }
453
468
  >
454
- Alle Bahnhöfe auf dieser Strecke
469
+ {t("all_line_stops")}
455
470
  </div>
456
471
  )}
457
472
  </div>
@@ -6,6 +6,7 @@ import OverlayDetails from "../OverlayDetails";
6
6
  import OverlayHeader from "../OverlayHeader";
7
7
  import Search from "../Search";
8
8
  import ShareMenu from "../ShareMenu";
9
+ import useI18n from "../utils/hooks/useI18n";
9
10
  import useMapContext from "../utils/hooks/useMapContext";
10
11
 
11
12
  const contentClassName = `relative h-full overflow-x-hidden overflow-y-auto text-base bg-white`;
@@ -34,6 +35,7 @@ function OverlayContent({
34
35
  setIsLayerTreeOpen,
35
36
  setIsShareMenuOpen,
36
37
  } = useMapContext();
38
+ const { t } = useI18n();
37
39
 
38
40
  return (
39
41
  <>
@@ -44,7 +46,7 @@ function OverlayContent({
44
46
  onClose={() => {
45
47
  setIsExportMenuOpen(false);
46
48
  }}
47
- title={"Print"}
49
+ title={t("print_menu_title")}
48
50
  ></OverlayHeader>
49
51
  <ExportMenu
50
52
  className={twMerge(contentClassName, "flex flex-col gap-4 p-4")}
@@ -57,7 +59,7 @@ function OverlayContent({
57
59
  onClose={() => {
58
60
  setIsLayerTreeOpen(false);
59
61
  }}
60
- title={"Layers"}
62
+ title={t("layertree_menu_title")}
61
63
  ></OverlayHeader>
62
64
  <LayerTreeMenu
63
65
  className="relative flex h-full flex-col overflow-x-hidden overflow-y-auto p-2 text-base *:not-last:border-b"
@@ -71,7 +73,7 @@ function OverlayContent({
71
73
  onClose={() => {
72
74
  setIsShareMenuOpen(false);
73
75
  }}
74
- title="Share"
76
+ title={t("share_menu_title")}
75
77
  ></OverlayHeader>
76
78
  <ShareMenu className="h-full overflow-x-hidden overflow-y-auto p-4 text-base" />
77
79
  </>
@@ -11,8 +11,17 @@ import useMapContext from "../utils/hooks/useMapContext";
11
11
  * in the Overlay component.
12
12
  */
13
13
  function OverlayDetails() {
14
- const { featuresInfos, selectedFeature, setSelectedFeature } =
15
- useMapContext();
14
+ const {
15
+ featuresInfos,
16
+ realtimeLayer,
17
+ selectedFeature,
18
+ setSelectedFeature,
19
+ setStationId,
20
+ setTrainId,
21
+ stationId,
22
+ stationsLayer,
23
+ trainId,
24
+ } = useMapContext();
16
25
 
17
26
  const featuresInfo = useMemo(() => {
18
27
  return featuresInfos?.find((featureInfo) => {
@@ -20,27 +29,36 @@ function OverlayDetails() {
20
29
  });
21
30
  }, [featuresInfos, selectedFeature]);
22
31
 
23
- if (!selectedFeature) {
24
- return null;
25
- }
32
+ const layer = useMemo(() => {
33
+ if (featuresInfo?.layer) {
34
+ return featuresInfo.layer;
35
+ }
36
+ if (trainId) {
37
+ return realtimeLayer;
38
+ }
39
+ if (stationId) {
40
+ return stationsLayer;
41
+ }
42
+ return undefined;
43
+ }, [featuresInfo?.layer, realtimeLayer, stationId, stationsLayer, trainId]);
44
+
26
45
  return (
27
46
  <>
28
47
  <OverlayDetailsHeader
29
48
  feature={selectedFeature}
30
- layer={featuresInfo?.layer}
49
+ layer={layer}
31
50
  onClose={() => {
32
51
  setSelectedFeature(null);
52
+ setTrainId(null);
53
+ setStationId(null);
33
54
  }}
34
55
  />
35
56
  <FeatureDetails
36
57
  feature={selectedFeature}
37
58
  featuresInfo={featuresInfo}
38
- layer={featuresInfo?.layer}
39
- />
40
- <OverlayDetailsFooter
41
- feature={selectedFeature}
42
- layer={featuresInfo?.layer}
59
+ layer={layer}
43
60
  />
61
+ <OverlayDetailsFooter feature={selectedFeature} layer={layer} />
44
62
  </>
45
63
  );
46
64
  }
@@ -1,7 +1,8 @@
1
- import { memo, useMemo } from "preact/compat";
1
+ import { memo } from "preact/compat";
2
2
 
3
3
  import OverlayHeader from "../OverlayHeader";
4
- import { LAYERS_NAMES, LAYERS_TITLES } from "../utils/constants";
4
+ import useI18n from "../utils/hooks/useI18n";
5
+ import useLayerConfig from "../utils/hooks/useLayerConfig";
5
6
 
6
7
  import type { Feature } from "ol";
7
8
  import type BaseLayer from "ol/layer/Base";
@@ -19,17 +20,13 @@ function OverlayDetailsHeader({
19
20
  layer,
20
21
  ...props
21
22
  }: OverlayDetailsHeaderProps) {
22
- const title = useMemo(() => {
23
- let ttle = layer?.get("title");
24
- if (!ttle) {
25
- const key = Object.keys(LAYERS_TITLES).find((titleKey) => {
26
- return LAYERS_NAMES[titleKey] === layer?.get("name");
27
- });
28
- ttle = LAYERS_TITLES[key];
29
- }
30
- return ttle || layer?.get("name") || "Details";
31
- }, [layer]);
32
-
33
- return <OverlayHeader title={title} {...props}></OverlayHeader>;
23
+ const { t } = useI18n();
24
+ const layerConfig = useLayerConfig(layer?.get("name"));
25
+ return (
26
+ <OverlayHeader
27
+ title={t(layerConfig?.title || "")}
28
+ {...props}
29
+ ></OverlayHeader>
30
+ );
34
31
  }
35
32
  export default memo(OverlayDetailsHeader);
@@ -4,8 +4,8 @@ import { unByKey } from "ol/Observable";
4
4
  import { memo } from "preact/compat";
5
5
  import { useCallback, useEffect } from "preact/hooks";
6
6
 
7
+ import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
7
8
  import getPermalinkParameters from "../utils/getPermalinkParameters";
8
- // import { LAYER_PROP_IS_EXPORTING } from "../constants";
9
9
  // import getLayersAsFlatArray from "../getLayersAsFlatArray";
10
10
  import useMapContext from "../utils/hooks/useMapContext";
11
11
 
@@ -23,9 +23,9 @@ const Permalink = ({ replaceState = false }: { replaceState?: boolean }) => {
23
23
  const updatePermalink = useCallback(
24
24
  (currentMap: Map) => {
25
25
  // No update when exporting
26
- // if (map.get(LAYER_PROP_IS_EXPORTING)) {
27
- // return;
28
- // }
26
+ if (map.get(LAYER_PROP_IS_EXPORTING)) {
27
+ return;
28
+ }
29
29
  const currentUrlParams = new URLSearchParams(window.location.search);
30
30
  const urlParams = getPermalinkParameters(currentMap, currentUrlParams);
31
31
  urlParams.set("permalink", "true");
@@ -34,7 +34,7 @@ const Permalink = ({ replaceState = false }: { replaceState?: boolean }) => {
34
34
  window.history.replaceState(null, null, `?${urlParams.toString()}`);
35
35
  }
36
36
  },
37
- [replaceState, setPermalinkUrlSearchParams],
37
+ [map, replaceState, setPermalinkUrlSearchParams],
38
38
  );
39
39
 
40
40
  useEffect(() => {
@@ -1,4 +1,7 @@
1
+ import { memo } from "preact/compat";
2
+
1
3
  import InputCopy from "../ui/InputCopy";
4
+ import useI18n from "../utils/hooks/useI18n";
2
5
 
3
6
  import type { HTMLAttributes, PreactDOMAttributes } from "preact";
4
7
 
@@ -15,14 +18,13 @@ function PermalinkInput({
15
18
  inputProps = emptyProps,
16
19
  ...props
17
20
  }: PermalinkInputProps) {
21
+ const { t } = useI18n();
18
22
  return (
19
23
  <div {...props}>
20
24
  <InputCopy value={window?.location.href} {...inputProps} />
21
- <p className="py-2">
22
- Sie können auch den Link aus der Adresszeile des Browsers kopieren.
23
- </p>
25
+ <p className="py-2">{t("permalink_input_hint")}</p>
24
26
  </div>
25
27
  );
26
28
  }
27
29
 
28
- export default PermalinkInput;
30
+ export default memo(PermalinkInput);
@@ -184,15 +184,16 @@ function RealtimeLayer(props: Partial<RealtimeLayerOptions>) {
184
184
  };
185
185
  }, [isFollowing, map, layer, stopSequence, setIsTracking]);
186
186
 
187
- useEffect(() => {
188
- if (trainId) {
189
- // No animation, it's nicer for the user.
190
- const center = layer?.trajectories?.[trainId]?.properties?.coordinate;
191
- if (center) {
192
- map.getView().setCenter(center);
193
- }
194
- }
195
- }, [map, trainId, layer]);
187
+ // DO NOT CENTER ON THE TRAIN
188
+ // useEffect(() => {
189
+ // if (trainId) {
190
+ // // No animation, it's nicer for the user.
191
+ // const center = layer?.trajectories?.[trainId]?.properties?.coordinate;
192
+ // if (center) {
193
+ // map.getView().setCenter(center);
194
+ // }
195
+ // }
196
+ // }, [map, trainId, layer]);
196
197
 
197
198
  // Ask the station using the stationId to the Realtime API.
198
199
  useEffect(() => {
@@ -25,7 +25,7 @@ function RouteScheduleFooter() {
25
25
 
26
26
  return (
27
27
  <>
28
- <div className="m-4 mb-0 flex flex-wrap text-sm text-gray-500">
28
+ <div className="m-4 mb-0 flex flex-wrap text-xs text-gray-500">
29
29
  {stopSequence.operator &&
30
30
  defaultRenderLink(stopSequence.operator, stopSequence.operatorUrl)}
31
31
  {stopSequence.operator && stopSequence.publisher && <span> - </span>}