@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
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.59",
5
+ "version": "0.1.61",
6
6
  "homepage": "https://rvf-mobility-web-component-geops.vercel.app/",
7
7
  "type": "module",
8
8
  "main": "index.js",
@@ -6,6 +6,7 @@ import Select from "../ui/Select";
6
6
  import { LAYER_PROP_IS_EXPORTING, MAX_EXTENT } from "../utils/constants";
7
7
  import exportPdf from "../utils/exportPdf";
8
8
  import getAllLayers from "../utils/getAllLayers";
9
+ import useI18n from "../utils/hooks/useI18n";
9
10
  import useMapContext from "../utils/hooks/useMapContext";
10
11
 
11
12
  import type { HTMLAttributes, PreactDOMAttributes } from "preact";
@@ -25,6 +26,7 @@ function ExportMenu({ ...props }: ExportMenuButtonProps) {
25
26
  const selectId = useId();
26
27
  const [isExporting, setIsExporting] = useState(false);
27
28
  const [isExportingError, setIsExportingError] = useState(false);
29
+ const { t } = useI18n();
28
30
  return (
29
31
  <div {...props}>
30
32
  {/* <!-- Content --> */}
@@ -37,10 +39,10 @@ function ExportMenu({ ...props }: ExportMenuButtonProps) {
37
39
  return setUseMaxExtent(!useMaxExtent);
38
40
  }}
39
41
  />
40
- <label htmlFor={checkboxId}>Ganze Region exportieren</label>
42
+ <label htmlFor={checkboxId}>{t("export_all_region")}</label>
41
43
  </div>
42
44
  <div className={"flex flex-wrap items-center gap-2"}>
43
- <label htmlFor={selectId}>Format:</label>
45
+ <label htmlFor={selectId}>{t("export_format_title")}:</label>
44
46
  <Select
45
47
  className={"w-24"}
46
48
  id={selectId}
@@ -102,10 +104,10 @@ function ExportMenu({ ...props }: ExportMenuButtonProps) {
102
104
  >
103
105
  {/* eslint-disable-next-line no-nested-ternary */}
104
106
  {isExporting
105
- ? "Exporting..."
107
+ ? t("exporting")
106
108
  : isExportingError
107
- ? "Error"
108
- : "Download"}
109
+ ? t("error")
110
+ : t("download")}
109
111
  </Button>
110
112
  </div>
111
113
  </div>
@@ -3,6 +3,7 @@ import { useCallback } from "preact/hooks";
3
3
 
4
4
  import Download from "../icons/Download";
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 { IconButtonProps } from "../ui/IconButton/IconButton";
@@ -11,13 +12,19 @@ export type ExportMenuButtonProps = IconButtonProps;
11
12
 
12
13
  function ExportMenuButton({ ...props }: ExportMenuButtonProps) {
13
14
  const { isExportMenuOpen, setIsExportMenuOpen } = useMapContext();
15
+ const { t } = useI18n();
14
16
 
15
17
  const onClick = useCallback(() => {
16
18
  setIsExportMenuOpen(!isExportMenuOpen);
17
19
  }, [isExportMenuOpen, setIsExportMenuOpen]);
18
20
 
19
21
  return (
20
- <IconButton {...props} onClick={onClick} selected={isExportMenuOpen}>
22
+ <IconButton
23
+ title={t("print_menu_title")}
24
+ {...props}
25
+ onClick={onClick}
26
+ selected={isExportMenuOpen}
27
+ >
21
28
  <Download />
22
29
  </IconButton>
23
30
  );
@@ -6,18 +6,19 @@ import { useEffect, useMemo } from "preact/hooks";
6
6
 
7
7
  import GeolocationIcon from "../icons/Geolocation";
8
8
  import IconButton from "../ui/IconButton";
9
+ import useI18n from "../utils/hooks/useI18n";
9
10
  import useMapContext from "../utils/hooks/useMapContext";
10
11
 
11
- import type { JSX, PreactDOMAttributes } from "preact";
12
+ import type { IconButtonProps } from "../ui/IconButton/IconButton";
12
13
 
13
- export type GeolocationButtonProps = JSX.HTMLAttributes<HTMLButtonElement> &
14
- PreactDOMAttributes;
14
+ export type GeolocationButtonProps = IconButtonProps;
15
15
 
16
16
  const TRACKING_ZOOM = 16;
17
17
 
18
18
  function GeolocationButton({ ...props }: GeolocationButtonProps) {
19
19
  const mapContext = useMapContext();
20
20
  const { isTracking, map, setIsTracking } = mapContext;
21
+ const { t } = useI18n();
21
22
 
22
23
  const geolocation = useMemo(() => {
23
24
  return new Geolocation();
@@ -62,6 +63,11 @@ function GeolocationButton({ ...props }: GeolocationButtonProps) {
62
63
  onClick={() => {
63
64
  return setIsTracking(!isTracking);
64
65
  }}
66
+ title={
67
+ isTracking
68
+ ? t("geolocation_button_title_off")
69
+ : t("geolocation_button_title_on")
70
+ }
65
71
  {...props}
66
72
  selected={isTracking}
67
73
  >
@@ -5,6 +5,7 @@ import ArrowDown from "../../icons/ArrowDown";
5
5
  import ArrowUp from "../../icons/ArrowUp";
6
6
  import minusGrey from "../../icons/Minus/minus.svg";
7
7
  import Checkbox from "../../ui/Checkbox";
8
+ import useI18n from "../../utils/hooks/useI18n";
8
9
  // import RvfRadioButton from "../../RvfRadioButton";
9
10
  import { LayersTreeDispatchContext } from "../layersTreeContext";
10
11
 
@@ -46,6 +47,7 @@ function TreeItem({
46
47
  const dispatch = useContext(LayersTreeDispatchContext);
47
48
  const inputId = useId();
48
49
  const buttonId = useId();
50
+ const { t } = useI18n();
49
51
 
50
52
  useEffect(() => {
51
53
  if (isCollapsedOnControlClick) {
@@ -127,7 +129,7 @@ function TreeItem({
127
129
  className={`flex-1 cursor-pointer`}
128
130
  htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
129
131
  >
130
- {title}
132
+ {typeof title === "string" ? t(title) : title}
131
133
  </label>
132
134
  {renderedLayers.length > 0 && (
133
135
  <button
@@ -3,22 +3,28 @@ import { useCallback } from "preact/hooks";
3
3
 
4
4
  import Stack from "../icons/Stack";
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 LayerTreeButtonProps = HTMLAttributes<HTMLButtonElement> &
11
- PreactDOMAttributes;
11
+ export type LayerTreeButtonProps = IconButtonProps;
12
12
 
13
13
  function LayerTreeButton({ ...props }: LayerTreeButtonProps) {
14
14
  const { isLayerTreeOpen, setIsLayerTreeOpen } = useMapContext();
15
+ const { t } = useI18n();
15
16
 
16
17
  const onClick = useCallback(() => {
17
18
  setIsLayerTreeOpen(!isLayerTreeOpen);
18
19
  }, [isLayerTreeOpen, setIsLayerTreeOpen]);
19
20
 
20
21
  return (
21
- <IconButton {...props} onClick={onClick} selected={isLayerTreeOpen}>
22
+ <IconButton
23
+ title={t("layertree_menu_title")}
24
+ {...props}
25
+ onClick={onClick}
26
+ selected={isLayerTreeOpen}
27
+ >
22
28
  <Stack />
23
29
  </IconButton>
24
30
  );
@@ -5,7 +5,7 @@ import { memo, useEffect, useMemo, useState } from "preact/compat";
5
5
 
6
6
  import LayerTree from "../LayerTree";
7
7
  import { SelectionType } from "../LayerTree/TreeItem";
8
- import { LAYERS_TITLES } from "../utils/constants";
8
+ import useLayersConfig from "../utils/hooks/useLayersConfig";
9
9
  import useMapContext from "../utils/hooks/useMapContext";
10
10
 
11
11
  import type { Group } from "ol/layer";
@@ -13,6 +13,7 @@ import type BaseLayer from "ol/layer/Base";
13
13
  import type { HTMLAttributes, PreactDOMAttributes } from "preact";
14
14
 
15
15
  import type { LayerTreeProps } from "../LayerTree/LayerTree";
16
+ import type { LayerConfig } from "../utils/hooks/useLayersConfig";
16
17
 
17
18
  export interface LayerTreeConfig {
18
19
  childItems: LayerTreeConfig[];
@@ -31,27 +32,36 @@ export type LayerTreeMenuProps = HTMLAttributes<HTMLDivElement> &
31
32
  const getConfigForLayer = (
32
33
  layer: BaseLayer,
33
34
  revision: number,
35
+ layersConfig: Record<string, LayerConfig> = {},
34
36
  ): LayerTreeConfig => {
37
+ const defaultTitle = layersConfig[layer.get("name")]?.title || "";
38
+ const customTitleFunction = layer.get("layerTreeTitle");
39
+ let title = defaultTitle;
40
+ if (typeof customTitleFunction === "function") {
41
+ title = customTitleFunction(title);
42
+ }
43
+
35
44
  return {
36
45
  childItems:
37
46
  (layer as Group)
38
47
  ?.getLayers?.()
39
48
  .getArray()
40
49
  .map((subLayer) => {
41
- return getConfigForLayer(subLayer, revision);
50
+ return getConfigForLayer(subLayer, revision, layersConfig);
42
51
  }) || [],
43
52
  id: getUid(layer),
44
53
  isControlChecked: layer.getVisible(),
45
54
  layer,
46
55
  revision,
47
56
  selectionType: SelectionType.CHECKBOX,
48
- title: layer.get("title") || LAYERS_TITLES[layer.get("name")],
57
+ title,
49
58
  };
50
59
  };
51
60
 
52
61
  function LayerTreeMenu(props: LayerTreeMenuProps) {
53
62
  const { map } = useMapContext();
54
63
  const [revision, setRevision] = useState(0);
64
+ const layersConfig = useLayersConfig();
55
65
 
56
66
  const layers: LayerTreeConfig[] = useMemo(() => {
57
67
  const config =
@@ -59,10 +69,10 @@ function LayerTreeMenu(props: LayerTreeMenuProps) {
59
69
  ?.getLayers()
60
70
  .getArray()
61
71
  .map((layer) => {
62
- return getConfigForLayer(layer, revision);
72
+ return getConfigForLayer(layer, revision, layersConfig);
63
73
  }) || [];
64
74
  return config;
65
- }, [map, revision]);
75
+ }, [layersConfig, map, revision]);
66
76
 
67
77
  // Force update of config when a layers`s visibility changes progammatically
68
78
  useEffect(() => {
@@ -15,10 +15,13 @@ function LinesNetworkPlanLayer(props: MaplibreStyleLayerOptions) {
15
15
  return null;
16
16
  }
17
17
  return new MaplibreStyleLayer({
18
- layersFilter: ({ metadata }) => {
18
+ layersFilter: ({ metadata, source }) => {
19
19
  return (
20
+ metadata?.["geops.filter"] === "lnp" ||
21
+ metadata?.["general.filter"] === "lnp" ||
20
22
  metadata?.["rvf.filter"] === "netzplan_lines" ||
21
- metadata?.["rvf.filter"] === "netzplan_stations"
23
+ metadata?.["rvf.filter"] === "netzplan_stations" ||
24
+ source === "network_plans"
22
25
  );
23
26
  },
24
27
  maplibreLayer: baseLayer,
@@ -64,6 +64,13 @@ function MapDispatchEvents({
64
64
  const json = selectedFeature
65
65
  ? geojson.writeFeatureObject(selectedFeature)
66
66
  : null;
67
+
68
+ // When the ol feature is a cluster
69
+ if (json?.properties?.features?.length > 0) {
70
+ json.properties.features = json.properties.features.map((feature) => {
71
+ return geojson.writeFeatureObject(feature);
72
+ });
73
+ }
67
74
  dispatchEvent(node, "mwc:selectedfeature", json);
68
75
  }, [node, selectedFeature]);
69
76
 
@@ -0,0 +1,176 @@
1
+ import { memo } from "preact/compat";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ import Copyright from "../Copyright";
5
+ import EmbedNavigation from "../EmbedNavigation";
6
+ import ExportMenuButton from "../ExportMenuButton";
7
+ import GeolocationButton from "../GeolocationButton";
8
+ import LayerTreeButton from "../LayerTreeButton";
9
+ import Map from "../Map";
10
+ import Overlay from "../Overlay";
11
+ import OverlayContent from "../OverlayContent";
12
+ import ScaleLine from "../ScaleLine";
13
+ import Search from "../Search";
14
+ import SearchButton from "../SearchButton";
15
+ import ShareMenuButton from "../ShareMenuButton";
16
+ import useMapContext from "../utils/hooks/useMapContext";
17
+ import ZoomButtons from "../ZoomButtons";
18
+
19
+ import type { HTMLAttributes } from "preact";
20
+
21
+ const scrollableHandlerProps = {
22
+ style: { width: "calc(100% - 60px)" },
23
+ };
24
+
25
+ function MapLayout({
26
+ className,
27
+ ...props
28
+ }: { className?: string } & HTMLAttributes<HTMLDivElement>) {
29
+ const {
30
+ hasDetails,
31
+ hasGeolocation,
32
+ hasLayerTree,
33
+ hasPrint,
34
+ hasRealtime,
35
+ hasSearch,
36
+ hasShare,
37
+ hasToolbar,
38
+ isEmbed,
39
+ isOverlayOpen,
40
+ isSearchOpen,
41
+ } = useMapContext();
42
+
43
+ return (
44
+ <div
45
+ className={twMerge(
46
+ "relative flex size-full flex-col @lg/main:flex-row-reverse",
47
+ className,
48
+ )}
49
+ {...props}
50
+ >
51
+ <Map className="relative flex-1 overflow-visible">
52
+ {isEmbed && <EmbedNavigation />}
53
+
54
+ <div className="absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
55
+ <ScaleLine className="bg-slate-50/70" />
56
+ <Copyright className="bg-slate-50/70" />
57
+ </div>
58
+
59
+ <div className="absolute right-2 bottom-10 z-10 flex flex-col justify-between gap-2">
60
+ <ZoomButtons />
61
+ </div>
62
+
63
+ {hasGeolocation && (
64
+ <div className="absolute top-2 right-2 z-10 flex flex-col gap-2">
65
+ <GeolocationButton />
66
+ </div>
67
+ )}
68
+
69
+ {!hasToolbar && hasSearch && (
70
+ <div
71
+ className={twMerge(
72
+ "absolute top-2 right-2 left-2 z-10 max-w-96",
73
+ isOverlayOpen && "@lg:left-84",
74
+ )}
75
+ >
76
+ <Search />
77
+ </div>
78
+ )}
79
+ </Map>
80
+
81
+ <div className="pointer-events-none absolute top-2 bottom-2 left-2 z-10 flex flex-col gap-2 *:pointer-events-auto">
82
+ {hasToolbar && (
83
+ <div
84
+ className={
85
+ "relative z-10 w-fit rounded-2xl bg-black/10 p-0 backdrop-blur-sm"
86
+ }
87
+ >
88
+ <div
89
+ className={twMerge(
90
+ "border-grey relative z-10 flex h-[48px] gap-[1px] overflow-hidden rounded-2xl border",
91
+ "*:size-[46px] *:rounded-none *:border-none",
92
+ "*:first:!rounded-l-2xl",
93
+ "*:last:!rounded-r-2xl",
94
+ isSearchOpen
95
+ ? "@sm:rounded-r-none @sm:border-r-0 @sm:*:last:!rounded-r-none @sm:*:last:border-r-0"
96
+ : "",
97
+ )}
98
+ >
99
+ {hasPrint && <ExportMenuButton />}
100
+ {hasShare && <ShareMenuButton />}
101
+ {hasLayerTree && <LayerTreeButton />}
102
+ {hasSearch && <SearchButton />}
103
+ </div>
104
+
105
+ {hasSearch && (
106
+ <div
107
+ className={twMerge(
108
+ "absolute top-14 left-0 z-5 w-0 p-0 opacity-0 transition-all @sm:top-0 @sm:left-[calc(100%-47px)]",
109
+ isSearchOpen ? "w-64 opacity-100" : "",
110
+ )}
111
+ >
112
+ <Search
113
+ className={
114
+ "border-grey @container m-0 h-[48px] gap-4 rounded-2xl border p-2 px-4 text-base @sm/main:rounded-l-none @sm/main:rounded-r-2xl"
115
+ }
116
+ // inputClassName="h-6 text-base"
117
+ inputContainerClassName="border-none"
118
+ resultClassName="text-base **:hover:cursor-pointer p-2"
119
+ resultsContainerClassName="@container rounded-b-2xl max-h-[200px] overflow-y-auto border border-t-0 "
120
+ withResultsClassName="text-base !rounded-b-none"
121
+ />
122
+ </div>
123
+ )}
124
+ </div>
125
+ )}
126
+
127
+ {/* Desktop (>= lg) */}
128
+ {isOverlayOpen && (
129
+ <div
130
+ className={twMerge(
131
+ "flex w-0 flex-1 flex-col overflow-hidden rounded-2xl @lg:min-w-80",
132
+ )}
133
+ style={{ containerType: "normal" }}
134
+ >
135
+ <Overlay
136
+ className={
137
+ "border-grey @container/overlay pointer-events-auto relative hidden flex-col overflow-hidden rounded-2xl border bg-white text-base shadow-lg @lg:flex"
138
+ }
139
+ ScrollableHandlerProps={scrollableHandlerProps}
140
+ >
141
+ <OverlayContent
142
+ hasDetails={hasDetails}
143
+ hasLayerTree={hasLayerTree}
144
+ hasPrint={hasPrint}
145
+ hasRealtime={hasRealtime}
146
+ hasSearch={false}
147
+ hasShare={hasShare}
148
+ />
149
+ </Overlay>
150
+ </div>
151
+ )}
152
+ </div>
153
+
154
+ {/* Mobile (< lg) */}
155
+ {isOverlayOpen && (
156
+ <Overlay
157
+ className={
158
+ "absolute bottom-0 z-20 flex max-h-[70%] min-h-[75px] w-full flex-col border-t bg-white @lg:hidden"
159
+ }
160
+ ScrollableHandlerProps={scrollableHandlerProps}
161
+ >
162
+ <OverlayContent
163
+ hasDetails={hasDetails}
164
+ hasLayerTree={hasLayerTree}
165
+ hasPrint={hasPrint}
166
+ hasRealtime={hasRealtime}
167
+ hasSearch={false}
168
+ hasShare={hasShare}
169
+ />
170
+ </Overlay>
171
+ )}
172
+ </div>
173
+ );
174
+ }
175
+
176
+ export default memo(MapLayout);
@@ -0,0 +1 @@
1
+ export { default } from "./MapLayout";