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

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 (56) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +16 -123
  3. package/docutils.js +1 -1
  4. package/index.js +152 -149
  5. package/package.json +1 -1
  6. package/src/ExportMenu/ExportMenu.tsx +7 -5
  7. package/src/ExportMenuButton/ExportMenuButton.tsx +8 -1
  8. package/src/GeolocationButton/GeolocationButton.tsx +9 -3
  9. package/src/LayerTree/TreeItem/TreeItem.tsx +3 -1
  10. package/src/LayerTreeButton/LayerTreeButton.tsx +10 -4
  11. package/src/LayerTreeMenu/LayerTreeMenu.tsx +15 -5
  12. package/src/LinesNetworkPlanLayer/LinesNetworkPlanLayer.tsx +5 -2
  13. package/src/MapDispatchEvents/MapDispatchEvents.tsx +7 -0
  14. package/src/MapLayout/MapLayout.tsx +176 -0
  15. package/src/MapLayout/index.tsx +1 -0
  16. package/src/MobilityMap/MobilityMap.tsx +25 -139
  17. package/src/MobilityMap/MobilityMapAttributes.ts +15 -3
  18. package/src/NotificationDetails/NotificationDetails.tsx +23 -8
  19. package/src/OverlayContent/OverlayContent.tsx +5 -3
  20. package/src/OverlayDetails/OverlayDetails.tsx +29 -11
  21. package/src/OverlayDetailsHeader/OverlayDetailsHeader.tsx +11 -14
  22. package/src/Permalink/Permalink.tsx +5 -5
  23. package/src/PermalinkInput/PermalinkInput.tsx +6 -4
  24. package/src/RealtimeLayer/RealtimeLayer.tsx +10 -9
  25. package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +1 -1
  26. package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +23 -8
  27. package/src/RvfMobilityMap/RvfMobilityMap.tsx +45 -41
  28. package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +6 -6
  29. package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +76 -58
  30. package/src/Search/Search.tsx +1 -1
  31. package/src/SearchButton/SearchButton.tsx +10 -4
  32. package/src/ShareMenu/ShareMenu.tsx +4 -2
  33. package/src/ShareMenuButton/ShareMenuButton.tsx +10 -3
  34. package/src/StationsLayer/StationsLayer.tsx +4 -1
  35. package/src/ZoomButtons/ZoomButtons.tsx +12 -2
  36. package/src/index.tsx +13 -5
  37. package/src/utils/constants.ts +20 -40
  38. package/src/utils/getPermalinkParameters.ts +12 -4
  39. package/src/utils/hooks/useInitialPermalink.tsx +76 -0
  40. package/src/utils/hooks/useLayerConfig.tsx +1 -3
  41. package/src/utils/hooks/useLayersConfig.tsx +2 -2
  42. package/src/utils/i18n.ts +0 -10
  43. package/src/utils/translations.ts +165 -0
  44. package/src/RvfFeatureDetailsFooter/RvfFeatureDetailsFooter.tsx +0 -43
  45. package/src/RvfFeatureDetailsFooter/index.tsx +0 -1
  46. package/src/RvfFeatureDetailsTitle/RvfFeatureDetailsTitle.tsx +0 -81
  47. package/src/RvfFeatureDetailsTitle/index.tsx +0 -1
  48. package/src/RvfLayerTree/RvfLayerTree.tsx +0 -40
  49. package/src/RvfLayerTree/TreeItem/TreeItem.tsx +0 -145
  50. package/src/RvfLayerTree/TreeItem/index.tsx +0 -1
  51. package/src/RvfLayerTree/index.tsx +0 -1
  52. package/src/RvfLayerTree/layersTreeContext.ts +0 -4
  53. package/src/RvfLayerTree/layersTreeReducer.ts +0 -158
  54. package/src/RvfOverlayContent/RvfOverlayContent.tsx +0 -128
  55. package/src/RvfOverlayContent/index.ts +0 -0
  56. package/src/utils/hooks/useUpdatePermalink.tsx +0 -76
@@ -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
  "bg-red rounded-md 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>
@@ -1,5 +1,5 @@
1
1
  import { memo } from "preact/compat";
2
- import { useMemo, useRef, useState } from "preact/hooks";
2
+ import { useEffect, useMemo, useRef, useState } from "preact/hooks";
3
3
  import { twMerge } from "tailwind-merge";
4
4
 
5
5
  import BaseLayer from "../BaseLayer";
@@ -31,13 +31,13 @@ import SearchButton from "../SearchButton";
31
31
  import ShareMenuButton from "../ShareMenuButton";
32
32
  import SingleClickListener from "../SingleClickListener";
33
33
  import StationsLayer from "../StationsLayer";
34
- import { LAYERS_TITLES } from "../utils/constants";
35
34
  import fullTrajectoryStyle from "../utils/fullTrajectoryStyle";
36
35
  import getBgColor from "../utils/getBgColor";
37
36
  import { getRadius } from "../utils/getRadius";
38
37
  import getTextColor from "../utils/getTextColor";
39
38
  import { I18nContext } from "../utils/hooks/useI18n";
40
39
  import useInitialLayersVisiblity from "../utils/hooks/useInitialLayersVisiblity";
40
+ import useInitialPermalink from "../utils/hooks/useInitialPermalink";
41
41
  import { MapContext } from "../utils/hooks/useMapContext";
42
42
  import { RvfContext } from "../utils/hooks/useRvfContext";
43
43
  import i18n from "../utils/i18n";
@@ -199,7 +199,7 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
199
199
  const [previewNotifications, setPreviewNotifications] =
200
200
  useState<SituationType[]>();
201
201
 
202
- const { layers, mainlink } = props;
202
+ const { lang, layers, mainlink } = props;
203
203
 
204
204
  // Apply initial visibility of layers
205
205
  useInitialLayersVisiblity(map, layers);
@@ -371,6 +371,10 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
371
371
  };
372
372
  }, []);
373
373
 
374
+ useEffect(() => {
375
+ i18n.locale(lang);
376
+ }, [lang]);
377
+
374
378
  return (
375
379
  <I18nContext.Provider value={i18n}>
376
380
  {/* There is a bug in tailwindcss@4 , variables are not imported in the shadow dom
@@ -445,35 +449,15 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
445
449
  <Map className="relative flex-1 overflow-visible">
446
450
  <BaseLayer {...baseLayerProps} />
447
451
  {isEmbed && <EmbedNavigation />}
448
- {hasNotification && (
449
- <NotificationsLayer
450
- isQueryable={true}
451
- title={LAYERS_TITLES.notifications}
452
- />
453
- )}
452
+ {hasNotification && <NotificationsLayer />}
454
453
  <RvfSelectedFeatureHighlightLayer />
455
- {hasRealtime && (
456
- <RealtimeLayer
457
- title={LAYERS_TITLES.realtime}
458
- {...realtimeLayerProps}
459
- />
460
- )}
461
- <StationsLayer
462
- minZoom={10}
463
- {...stationsLayerProps}
464
- title={LAYERS_TITLES.stations}
465
- />
466
- <RvfTarifZonenLayer title={LAYERS_TITLES.tarifzonen} />
467
- <RvfSellingPointsLayer title={LAYERS_TITLES.verkaufsstellen} />
468
- {hasLnp && (
469
- <LinesNetworkPlanLayer
470
- title={LAYERS_TITLES.linesnetworkplan}
471
- />
472
- )}
473
- <RvfPoisLayer title={LAYERS_TITLES.pois} />
474
- <RvfSharedMobilityLayerGroup
475
- title={LAYERS_TITLES.sharedMobility}
476
- />
454
+ {hasRealtime && <RealtimeLayer {...realtimeLayerProps} />}
455
+ <StationsLayer minZoom={10} {...stationsLayerProps} />
456
+ <RvfTarifZonenLayer />
457
+ <RvfSellingPointsLayer />
458
+ {hasLnp && <LinesNetworkPlanLayer />}
459
+ <RvfPoisLayer />
460
+ <RvfSharedMobilityLayerGroup />
477
461
  {mainlink && (
478
462
  <RvfMainLinkButton className="absolute inset-x-2 bottom-8 z-10" />
479
463
  )}
@@ -493,6 +477,17 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
493
477
  <div className="absolute right-2 bottom-10 z-10 flex flex-col justify-between gap-2">
494
478
  <ZoomButtons />
495
479
  </div>
480
+
481
+ {!hasToolbar && hasSearch && (
482
+ <div
483
+ className={twMerge(
484
+ "absolute top-2 right-2 left-2 z-10 max-w-96",
485
+ isOverlayOpen && "@lg:left-68",
486
+ )}
487
+ >
488
+ <Search />
489
+ </div>
490
+ )}
496
491
  </Map>
497
492
 
498
493
  <div className="pointer-events-none absolute top-2 bottom-2 left-2 z-10 flex flex-col gap-2 *:pointer-events-auto">
@@ -506,7 +501,7 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
506
501
  {hasSearch && (
507
502
  <div
508
503
  className={twMerge(
509
- "absolute top-12 left-0 h-[40px] w-0 p-0 opacity-0 transition-all @sm:top-0 @sm:left-[calc(100%-43px)] @md:left-[calc(100%-47px)]",
504
+ "absolute top-12 left-0 w-0 p-0 opacity-0 transition-all @sm:top-0 @sm:left-[calc(100%-43px)] @md:left-[calc(100%-47px)]",
510
505
  isSearchOpen ? "w-64 opacity-100" : "",
511
506
  )}
512
507
  >
@@ -525,7 +520,7 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
525
520
 
526
521
  <div
527
522
  className={twMerge(
528
- "border-grey relative flex gap-[1px] overflow-hidden rounded-2xl border",
523
+ "border-grey relative flex gap-[1px] overflow-hidden rounded-2xl border @sm/main:h-[44px] @md/main:h-[48px]",
529
524
  "*:size-[38px] *:rounded-none *:border-none *:@sm/main:size-[42px] *:@md/main:!size-[46px]",
530
525
  "*:first:!rounded-l-2xl",
531
526
  "*:last:!rounded-r-2xl",
@@ -534,18 +529,19 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
534
529
  : "",
535
530
  )}
536
531
  >
537
- {hasPrint && <ExportMenuButton title={"Drucken"} />}
538
- {hasShare && <ShareMenuButton title={"Share"} />}
539
- {hasLayerTree && <LayerTreeButton title={"Layers"} />}
540
- {hasSearch && <SearchButton title={"Suche"} />}
532
+ {hasPrint && <ExportMenuButton />}
533
+ {hasShare && <ShareMenuButton />}
534
+ {hasLayerTree && <LayerTreeButton />}
535
+ {hasSearch && <SearchButton />}
541
536
  </div>
542
537
  </div>
543
538
  )}
544
539
 
540
+ {/* Desktop (>= lg) */}
545
541
  <div
546
542
  className={twMerge(
547
543
  "flex w-0 flex-1 flex-col overflow-hidden rounded-2xl",
548
- isOverlayOpen ? "@lg:min-w-60" : "p-0",
544
+ isOverlayOpen ? "@lg:min-w-64" : "p-0",
549
545
  )}
550
546
  style={{ containerType: "normal" }}
551
547
  >
@@ -594,14 +590,22 @@ function RvfMobilityMap(props: RvfMobilityMapProps) {
594
590
  }
595
591
 
596
592
  // We creates a wrapper to inject the default props values from MobilityMapAttributes.
597
- const defaultProps = {};
598
-
593
+ const defaultProps: Partial<MobilityMapProps> = {};
599
594
  Object.entries(MobilityMapAttributes).forEach(([key]) => {
600
595
  defaultProps[key] = MobilityMapAttributes[key].defaultValue || null;
601
596
  });
602
597
 
603
598
  function MobilityMapWithDefaultProps(props: MobilityMapProps) {
604
- return <RvfMobilityMap {...defaultProps} {...props} />;
599
+ // Apply initial value from permalink, only x,y,z
600
+ const { permalinktemplate } = props;
601
+ const { permalinktemplate: defaultPermalinkTemplate } = defaultProps;
602
+ const propsFromPermalink = useInitialPermalink(
603
+ permalinktemplate || defaultPermalinkTemplate,
604
+ );
605
+
606
+ return (
607
+ <RvfMobilityMap {...defaultProps} {...propsFromPermalink} {...props} />
608
+ );
605
609
  }
606
610
 
607
611
  export default memo(MobilityMapWithDefaultProps);
@@ -6,32 +6,31 @@ import Automat from "../icons/Automat";
6
6
  import InPerson from "../icons/InPerson";
7
7
  import Video from "../icons/Video";
8
8
  import { LAYERS_NAMES } from "../utils/constants";
9
+ import useLayerConfig from "../utils/hooks/useLayerConfig";
9
10
  import useMapContext from "../utils/hooks/useMapContext";
10
11
  import useRvfContext from "../utils/hooks/useRvfContext";
11
12
 
12
13
  import type { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/MaplibreStyleLayer";
13
14
 
14
15
  function RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
15
- const { title } = props;
16
16
  const { baseLayer, map } = useMapContext();
17
17
  const { setSellingPointsLayer } = useRvfContext();
18
+ const layerConfig = useLayerConfig(LAYERS_NAMES.verkaufsstellen);
18
19
 
19
20
  const layer = useMemo(() => {
20
21
  if (!baseLayer) {
21
22
  return null;
22
23
  }
23
24
  return new MaplibreStyleLayer({
24
- isQueryable: true,
25
25
  layersFilter: ({ metadata }) => {
26
26
  return metadata?.["general.filter"] === "selling_points";
27
27
  },
28
28
  maplibreLayer: baseLayer,
29
29
  name: LAYERS_NAMES.verkaufsstellen,
30
30
  ...(props || {}),
31
-
32
- title: (
31
+ layerTreeTitle: (
33
32
  <div className="flex items-center justify-between gap-2">
34
- {title}
33
+ {layerConfig?.title}
35
34
  <span className="flex items-center">
36
35
  <Automat />
37
36
  <InPerson />
@@ -39,8 +38,9 @@ function RvfSellingPointsLayer(props: MaplibreStyleLayerOptions) {
39
38
  </span>
40
39
  </div>
41
40
  ),
41
+ title: layerConfig?.title,
42
42
  });
43
- }, [baseLayer, props, title]);
43
+ }, [baseLayer, props, layerConfig]);
44
44
 
45
45
  useEffect(() => {
46
46
  setSellingPointsLayer(layer);
@@ -26,7 +26,7 @@ import {
26
26
  WFS_FREE_FLOAT_TYPE,
27
27
  WFS_STATIONS_TYPE,
28
28
  } from "../utils/constants";
29
- import useLayersConfig from "../utils/hooks/useLayersConfig";
29
+ import useI18n from "../utils/hooks/useI18n";
30
30
  import useMapContext from "../utils/hooks/useMapContext";
31
31
  import useRvfContext from "../utils/hooks/useRvfContext";
32
32
  import { fetchSharingWFS } from "../utils/sharingWFSUtils";
@@ -65,7 +65,7 @@ const fetchStations = async (
65
65
  function RvfSharedMobilityLayerGroup(props: GroupOptions) {
66
66
  const { baseLayer, map } = useMapContext();
67
67
  const { setSharedMobilityLayerGroup } = useRvfContext();
68
- const layersConfig = useLayersConfig();
68
+ const { t } = useI18n();
69
69
 
70
70
  // Layers
71
71
  // const [bikeFreloLayer, setBikeFreloLayer] = useState<MaplibreStyleLayer>();
@@ -234,33 +234,36 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
234
234
  }
235
235
 
236
236
  const bikeFrelo = new MaplibreStyleLayer({
237
+ layerTreeTitle: (title) => {
238
+ return (
239
+ <div className="flex items-center justify-between gap-2">
240
+ {t(title)}
241
+ <Bicycle />
242
+ </div>
243
+ );
244
+ },
237
245
  maplibreLayer: baseLayer,
238
246
  name: LAYERS_NAMES.bikeFrelo,
239
247
  styleLayersFilter: ({ metadata }) => {
240
248
  return metadata?.["rvf.filter"] === "bike.frelo";
241
249
  },
242
-
243
- title: (
244
- <div className="flex items-center justify-between gap-2">
245
- {"Frelo"}
246
- <Bicycle />
247
- </div>
248
- ),
249
250
  });
250
251
  // setBikeFreloLayer(bikeFrelo);
251
252
 
252
253
  const bikeOthers = new MaplibreStyleLayer({
254
+ layerTreeTitle: (title) => {
255
+ return (
256
+ <div className="flex items-center justify-between gap-2">
257
+ {t(title)}
258
+ <Bicycle />
259
+ </div>
260
+ );
261
+ },
253
262
  maplibreLayer: baseLayer,
254
263
  name: LAYERS_NAMES.bikeOthers,
255
264
  styleLayersFilter: ({ metadata }) => {
256
265
  return metadata?.["rvf.filter"] === "bike.other";
257
266
  },
258
- title: (
259
- <div className="flex items-center justify-between gap-2">
260
- {layersConfig[LAYERS_NAMES.bikeOthers]?.title}
261
- <Bicycle />
262
- </div>
263
- ),
264
267
  });
265
268
  // setBikeOthersLayer(bikeOthers);
266
269
 
@@ -268,14 +271,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
268
271
  layersFilter: ({ metadata }) => {
269
272
  return metadata?.["rvf.filter"] === "cargo_bike.frelo";
270
273
  },
274
+ layerTreeTitle: (title) => {
275
+ return (
276
+ <div className="flex items-center justify-between gap-2">
277
+ {t(title)}
278
+ <CargoBike />
279
+ </div>
280
+ );
281
+ },
271
282
  maplibreLayer: baseLayer,
272
283
  name: LAYERS_NAMES.cargobikeFrelo,
273
- title: (
274
- <div className="flex items-center justify-between gap-2">
275
- {layersConfig[LAYERS_NAMES.cargobikeFrelo]?.title}
276
- <CargoBike />
277
- </div>
278
- ),
279
284
  });
280
285
  // setCargobikeFreloLayer(cargobikeFrelo);
281
286
 
@@ -283,14 +288,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
283
288
  layersFilter: ({ metadata }) => {
284
289
  return metadata?.["rvf.filter"] === "cargo_bike.other";
285
290
  },
291
+ layerTreeTitle: (title) => {
292
+ return (
293
+ <div className="flex items-center justify-between gap-2">
294
+ {t(title)}
295
+ <CargoBike />
296
+ </div>
297
+ );
298
+ },
286
299
  maplibreLayer: baseLayer,
287
300
  name: LAYERS_NAMES.cargobikeOthers,
288
- title: (
289
- <div className="flex items-center justify-between gap-2">
290
- {layersConfig[LAYERS_NAMES.cargobikeOthers]?.title}
291
- <CargoBike />
292
- </div>
293
- ),
294
301
  });
295
302
  // setCargobikeOthersLayer(cargobikeOthers);
296
303
 
@@ -298,14 +305,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
298
305
  layersFilter: ({ metadata }) => {
299
306
  return metadata?.["rvf.filter"] === "car.grueneflotte";
300
307
  },
308
+ layerTreeTitle: (title) => {
309
+ return (
310
+ <div className="flex items-center justify-between gap-2">
311
+ {t(title)}
312
+ <Car />
313
+ </div>
314
+ );
315
+ },
301
316
  maplibreLayer: baseLayer,
302
317
  name: LAYERS_NAMES.carGrf,
303
- title: (
304
- <div className="flex items-center justify-between gap-2">
305
- {layersConfig[LAYERS_NAMES.carGrf]?.title}
306
- <Car />
307
- </div>
308
- ),
309
318
  });
310
319
  // setCarGrfLayer(carGrf);
311
320
 
@@ -313,14 +322,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
313
322
  layersFilter: ({ metadata }) => {
314
323
  return metadata?.["rvf.filter"] === "car.naturenergy";
315
324
  },
325
+ layerTreeTitle: (title) => {
326
+ return (
327
+ <div className="flex items-center justify-between gap-2">
328
+ {t(title)}
329
+ <Car />
330
+ </div>
331
+ );
332
+ },
316
333
  maplibreLayer: baseLayer,
317
334
  name: LAYERS_NAMES.carNatur,
318
- title: (
319
- <div className="flex items-center justify-between gap-2">
320
- {layersConfig[LAYERS_NAMES.carNatur]?.title}
321
- <Car />
322
- </div>
323
- ),
324
335
  });
325
336
  // setCarNaturLayer(carNatur);
326
337
 
@@ -328,14 +339,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
328
339
  layersFilter: ({ metadata }) => {
329
340
  return metadata?.["rvf.filter"] === "car.other";
330
341
  },
342
+ layerTreeTitle: (title) => {
343
+ return (
344
+ <div className="flex items-center justify-between gap-2">
345
+ {t(title)}
346
+ <Car />
347
+ </div>
348
+ );
349
+ },
331
350
  maplibreLayer: baseLayer,
332
351
  name: LAYERS_NAMES.carOthers,
333
- title: (
334
- <div className="flex items-center justify-between gap-2">
335
- {layersConfig[LAYERS_NAMES.carOthers]?.title}
336
- <Car />
337
- </div>
338
- ),
339
352
  });
340
353
  // setCarOthersLayer(carOthers);
341
354
 
@@ -343,14 +356,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
343
356
  layersFilter: ({ metadata }) => {
344
357
  return metadata?.["rvf.filter"] === "scooter";
345
358
  },
359
+ layerTreeTitle: (title) => {
360
+ return (
361
+ <div className="flex items-center justify-between gap-2">
362
+ {t(title)}
363
+ <Scooter />
364
+ </div>
365
+ );
366
+ },
346
367
  maplibreLayer: baseLayer,
347
368
  name: LAYERS_NAMES.eroller,
348
- title: (
349
- <div className="flex items-center justify-between gap-2">
350
- {layersConfig[LAYERS_NAMES.eroller]?.title}
351
- <Scooter />
352
- </div>
353
- ),
354
369
  });
355
370
  // setScooterLayer(scooter);
356
371
 
@@ -358,14 +373,16 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
358
373
  layersFilter: ({ metadata }) => {
359
374
  return metadata?.["rvf.filter"] === "hitchhiking";
360
375
  },
376
+ layerTreeTitle: (title) => {
377
+ return (
378
+ <div className="flex items-center justify-between gap-2">
379
+ {t(title)}
380
+ <Ride />
381
+ </div>
382
+ );
383
+ },
361
384
  maplibreLayer: baseLayer,
362
385
  name: LAYERS_NAMES.mitfahrpunkte,
363
- title: (
364
- <div className="flex items-center justify-between gap-2">
365
- {layersConfig[LAYERS_NAMES.mitfahrpunkte]?.title}
366
- <Ride />
367
- </div>
368
- ),
369
386
  });
370
387
 
371
388
  return new Group({
@@ -380,9 +397,10 @@ function RvfSharedMobilityLayerGroup(props: GroupOptions) {
380
397
  scooter,
381
398
  hitchHiking,
382
399
  ],
400
+ name: LAYERS_NAMES.sharedMobility,
383
401
  ...props,
384
402
  } as GroupOptions);
385
- }, [baseLayer, layersConfig, props]);
403
+ }, [baseLayer, props, t]);
386
404
 
387
405
  // Reload features every minute
388
406
  useEffect(() => {
@@ -7,7 +7,7 @@ import useMapContext from "../utils/hooks/useMapContext";
7
7
 
8
8
  import type { StopsSearchProps } from "../StopsSearch/StopsSearch";
9
9
 
10
- function Search(props: StopsSearchProps) {
10
+ function Search(props: Partial<StopsSearchProps>) {
11
11
  const { apikey, map, stopsurl } = useMapContext();
12
12
 
13
13
  const onSelect = useCallback(
@@ -3,22 +3,28 @@ import { useCallback } from "preact/hooks";
3
3
 
4
4
  import Search from "../icons/Search";
5
5
  import IconButton from "../ui/IconButton";
6
+ import useI18n from "../utils/hooks/useI18n";
6
7
  import useMapContext from "../utils/hooks/useMapContext";
7
8
 
8
- import type { HTMLAttributes, PreactDOMAttributes } from "preact";
9
+ import type { IconButtonProps } from "../ui/IconButton/IconButton";
9
10
 
10
- export type SearchButtonProps = HTMLAttributes<HTMLButtonElement> &
11
- PreactDOMAttributes;
11
+ export type SearchButtonProps = IconButtonProps;
12
12
 
13
13
  function SearchButton({ ...props }: SearchButtonProps) {
14
14
  const { isSearchOpen, setIsSearchOpen } = useMapContext();
15
+ const { t } = useI18n();
15
16
 
16
17
  const onClick = useCallback(() => {
17
18
  setIsSearchOpen(!isSearchOpen);
18
19
  }, [isSearchOpen, setIsSearchOpen]);
19
20
 
20
21
  return (
21
- <IconButton {...props} onClick={onClick} selected={isSearchOpen}>
22
+ <IconButton
23
+ title={t("search_menu_title")}
24
+ {...props}
25
+ onClick={onClick}
26
+ selected={isSearchOpen}
27
+ >
22
28
  <Search />
23
29
  </IconButton>
24
30
  );
@@ -5,6 +5,7 @@ import Email from "../icons/Email";
5
5
  import Image from "../icons/Image";
6
6
  import PermalinkInput from "../PermalinkInput";
7
7
  import Button from "../ui/Button";
8
+ import useI18n from "../utils/hooks/useI18n";
8
9
  import useMapContext from "../utils/hooks/useMapContext";
9
10
 
10
11
  import type { HTMLAttributes, PreactDOMAttributes } from "preact";
@@ -14,6 +15,7 @@ function ShareMenu({
14
15
  ...props
15
16
  }: HTMLAttributes<HTMLDivElement> & PreactDOMAttributes) {
16
17
  const { map } = useMapContext();
18
+ const { t } = useI18n();
17
19
 
18
20
  return (
19
21
  // eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions
@@ -23,12 +25,12 @@ function ShareMenu({
23
25
  href={`mailto:?subject=Karte&body=${window?.location.href}`}
24
26
  >
25
27
  <Email />
26
- <span>Email senden</span>
28
+ <span>{t("share_email_send")}</span>
27
29
  </Button>
28
30
  <CanvasSaveButton map={map}>
29
31
  <Button className={"w-fit"}>
30
32
  <Image />
31
- <span>Bild speichern</span>
33
+ <span>{t("share_image_save")}</span>
32
34
  </Button>
33
35
  </CanvasSaveButton>
34
36
  <PermalinkInput />
@@ -3,22 +3,29 @@ import { useCallback } from "preact/hooks";
3
3
 
4
4
  import Share from "../icons/Share";
5
5
  import IconButton from "../ui/IconButton";
6
+ import useI18n from "../utils/hooks/useI18n";
6
7
  import useMapContext from "../utils/hooks/useMapContext";
7
8
 
8
9
  import type { HTMLAttributes, PreactDOMAttributes } from "preact";
9
10
 
10
- export type RvfShareMenuButtonProps = HTMLAttributes<HTMLButtonElement> &
11
+ export type ShareMenuButtonProps = HTMLAttributes<HTMLButtonElement> &
11
12
  PreactDOMAttributes;
12
13
 
13
- function ShareMenuButton({ ...props }: RvfShareMenuButtonProps) {
14
+ function ShareMenuButton({ ...props }: ShareMenuButtonProps) {
14
15
  const { isShareMenuOpen, setIsShareMenuOpen } = useMapContext();
16
+ const { t } = useI18n();
15
17
 
16
18
  const onClick = useCallback(() => {
17
19
  setIsShareMenuOpen(!isShareMenuOpen);
18
20
  }, [isShareMenuOpen, setIsShareMenuOpen]);
19
21
 
20
22
  return (
21
- <IconButton {...props} onClick={onClick} selected={isShareMenuOpen}>
23
+ <IconButton
24
+ title={t("share_menu_title")}
25
+ {...props}
26
+ onClick={onClick}
27
+ selected={isShareMenuOpen}
28
+ >
22
29
  <Share />
23
30
  </IconButton>
24
31
  );
@@ -16,7 +16,10 @@ function StationsLayer(props: Partial<MaplibreStyleLayerOptions>) {
16
16
  }
17
17
  return new MaplibreStyleLayer({
18
18
  layersFilter: ({ metadata }) => {
19
- return metadata?.["general.filter"] === "stations";
19
+ return (
20
+ metadata?.["tralis.variable"] === "station" ||
21
+ metadata?.["general.filter"] === "stations"
22
+ );
20
23
  },
21
24
  maplibreLayer: baseLayer,
22
25
  name: LAYER_NAME_STATIONS,