@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,40 +0,0 @@
1
- import { memo } from "preact/compat";
2
- import { useEffect, useReducer } from "preact/hooks";
3
-
4
- import {
5
- LayersTreeContext,
6
- LayersTreeDispatchContext,
7
- } from "./layersTreeContext";
8
- import layersTreeReducer from "./layersTreeReducer";
9
- import TreeItem from "./TreeItem/TreeItem";
10
-
11
- import type { JSX, PreactDOMAttributes } from "preact";
12
-
13
- import type { TreeItemProps } from "./TreeItem/TreeItem";
14
-
15
- export type RvfLayerTreeProps = {
16
- layers: TreeItemProps[];
17
- } & JSX.HTMLAttributes<HTMLDivElement> &
18
- PreactDOMAttributes;
19
-
20
- function RvfLayerTree({ layers, ...props }: RvfLayerTreeProps) {
21
- const [tree, dispatch] = useReducer(layersTreeReducer, layers);
22
-
23
- useEffect(() => {
24
- dispatch({ payload: layers, type: "INIT" });
25
- }, [layers]);
26
-
27
- return (
28
- <LayersTreeContext.Provider value={tree}>
29
- <LayersTreeDispatchContext.Provider value={dispatch}>
30
- <div {...props}>
31
- {layers.map((item) => {
32
- return <TreeItem className="w-full" key={item.id} {...item} />;
33
- })}
34
- </div>
35
- </LayersTreeDispatchContext.Provider>
36
- </LayersTreeContext.Provider>
37
- );
38
- }
39
-
40
- export default memo(RvfLayerTree);
@@ -1,145 +0,0 @@
1
- import { useContext, useEffect, useId, useState } from "preact/compat";
2
-
3
- import ArrowDown from "../../icons/ArrowDown";
4
- import ArrowUp from "../../icons/ArrowUp";
5
- import minusGrey from "../../icons/Minus/minus-grey.svg";
6
- import RvfCheckbox from "../../RvfCheckbox";
7
- import RvfRadioButton from "../../RvfRadioButton";
8
- import { LayersTreeDispatchContext } from "../layersTreeContext";
9
-
10
- import type BaseLayer from "ol/layer/Base";
11
- import type { JSX, PreactDOMAttributes } from "preact";
12
- import type { SVGProps } from "preact/compat";
13
-
14
- export enum SelectionType {
15
- CHECKBOX = "checkbox",
16
- RADIO = "radio",
17
- }
18
-
19
- export type TreeItemProps = {
20
- childItems: TreeItemProps[];
21
- Icon?: (props: SVGProps<SVGSVGElement>) => preact.JSX.Element;
22
- id: string;
23
- isCollapsedOnControlClick?: boolean;
24
- isControlChecked?: boolean;
25
- layer?: BaseLayer;
26
- onIconClick?: () => void;
27
- selectionType: SelectionType;
28
- title: string;
29
- } & JSX.HTMLAttributes<HTMLButtonElement> &
30
- PreactDOMAttributes;
31
-
32
- function TreeItem({
33
- childItems,
34
- isCollapsedOnControlClick,
35
- isControlChecked,
36
- layer,
37
- selectionType,
38
- title,
39
- }: TreeItemProps) {
40
- const [isContainerVisible, setIsContainerVisible] = useState(true);
41
- const dispatch = useContext(LayersTreeDispatchContext);
42
- const inputId = useId();
43
- const buttonId = useId();
44
-
45
- useEffect(() => {
46
- if (isCollapsedOnControlClick) {
47
- setIsContainerVisible(isControlChecked);
48
- }
49
- }, [isControlChecked, isCollapsedOnControlClick, layer]);
50
-
51
- const handleItemClick = () => {
52
- setIsContainerVisible(!isContainerVisible);
53
-
54
- if (isCollapsedOnControlClick && !isContainerVisible) {
55
- dispatch({
56
- payload: {
57
- ...this.props,
58
- isControlChecked: true,
59
- },
60
- type: "SELECT_ITEM",
61
- });
62
- }
63
- };
64
-
65
- const handleSelectionChange = (event) => {
66
- dispatch({
67
- payload: {
68
- ...this.props,
69
- isControlChecked: event.target.checked,
70
- },
71
- type: "SELECT_ITEM",
72
- });
73
- };
74
-
75
- const renderedLayers = childItems
76
- .filter(({ title: titlee }) => {
77
- return !!titlee;
78
- })
79
- .map((item, idx) => {
80
- return <TreeItem key={idx} {...item} />;
81
- });
82
-
83
- if (!title) {
84
- return null;
85
- }
86
-
87
- const isMiddleState = () => {
88
- if (childItems.length > 0) {
89
- const checkedItems = childItems.filter((item) => {
90
- return item.isControlChecked;
91
- });
92
- if (checkedItems.length === childItems.length) {
93
- return false;
94
- }
95
-
96
- return childItems.some((item) => {
97
- return item.isControlChecked;
98
- });
99
- }
100
-
101
- return false;
102
- };
103
-
104
- return (
105
- <div>
106
- <div className="flex items-center gap-2 border-b py-2 pr-1">
107
- {selectionType === SelectionType.RADIO ? (
108
- <RvfRadioButton
109
- checked={isControlChecked}
110
- id={inputId}
111
- onChange={handleSelectionChange}
112
- />
113
- ) : (
114
- <RvfCheckbox
115
- checked={isControlChecked}
116
- checkedIconUrl={isMiddleState() ? minusGrey : null}
117
- className={isMiddleState() ? "bg-[length:18px]" : ""}
118
- id={inputId}
119
- onChange={handleSelectionChange}
120
- />
121
- )}
122
- <label
123
- className={`flex-1 cursor-pointer`}
124
- htmlFor={renderedLayers.length > 0 ? buttonId : inputId}
125
- >
126
- {title}
127
- </label>
128
- {renderedLayers.length > 0 && (
129
- <button
130
- className={`flex cursor-pointer items-center gap-2`}
131
- id={buttonId}
132
- onClick={handleItemClick}
133
- >
134
- {isContainerVisible ? <ArrowUp /> : <ArrowDown />}
135
- </button>
136
- )}
137
- </div>
138
- {isContainerVisible && renderedLayers.length > 0 && (
139
- <div className="pl-6">{renderedLayers}</div>
140
- )}
141
- </div>
142
- );
143
- }
144
-
145
- export default TreeItem;
@@ -1 +0,0 @@
1
- export { default } from "./TreeItem";
@@ -1 +0,0 @@
1
- export { default } from "./RvfLayerTree";
@@ -1,4 +0,0 @@
1
- import { createContext } from "preact";
2
-
3
- export const LayersTreeContext = createContext(null);
4
- export const LayersTreeDispatchContext = createContext(null);
@@ -1,158 +0,0 @@
1
- import { SelectionType } from "./TreeItem/TreeItem";
2
-
3
- const ROOT = {
4
- childItems: [],
5
- parent: null,
6
- };
7
-
8
- const mapNode = (childItems, parent) => {
9
- for (const child of childItems) {
10
- child.parent = parent;
11
-
12
- if (child.childItems.length) {
13
- mapNode(child.childItems, child);
14
- }
15
- }
16
- };
17
-
18
- const initTree = (tree) => {
19
- const initializedTree = { ...ROOT };
20
- initializedTree.childItems = tree;
21
- mapNode(initializedTree.childItems, initializedTree);
22
-
23
- return initializedTree;
24
- };
25
-
26
- const findNodeInTree = (node, nodeToFind) => {
27
- if (node.id === nodeToFind.id) {
28
- return node;
29
- }
30
-
31
- if (node.childItems.length) {
32
- for (const child of node.childItems) {
33
- const res = findNodeInTree(child, nodeToFind);
34
- if (res) {
35
- return res;
36
- }
37
- }
38
- }
39
- };
40
-
41
- const updateCheckedControlStatus = (currentItem, newItem, isParentUpdate) => {
42
- if (newItem.isControlChecked) {
43
- if (newItem.selectionType === SelectionType.CHECKBOX) {
44
- currentItem.isControlChecked = newItem.isControlChecked;
45
- currentItem.layer.setVisible(currentItem.isControlChecked);
46
- } else {
47
- for (const child of currentItem.parent.childItems) {
48
- child.isControlChecked = child.id === currentItem.id;
49
-
50
- if (!child.isControlChecked) {
51
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
52
- updateRadioChildNodes(child);
53
- }
54
- }
55
- }
56
- } else {
57
- if (newItem.selectionType === SelectionType.CHECKBOX) {
58
- currentItem.isControlChecked = newItem.isControlChecked;
59
- currentItem.layer.setVisible(currentItem.isControlChecked);
60
- }
61
- }
62
-
63
- // check all children
64
- if (currentItem.childItems.length && !isParentUpdate) {
65
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
66
- updateChildNodes(currentItem);
67
- }
68
-
69
- // check all parents
70
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
71
- updateParentNodes(currentItem);
72
- };
73
-
74
- const setNewControlCheckedStatus = (tree, newItem) => {
75
- const currentItem = findNodeInTree(tree, newItem);
76
-
77
- if (currentItem) {
78
- updateCheckedControlStatus(currentItem, newItem, false);
79
- }
80
-
81
- return { ...tree };
82
- };
83
-
84
- const updateChildNodes = (node) => {
85
- if (node.childItems[0].selectionType === SelectionType.RADIO) {
86
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
87
- updateRadioChildNodes(node);
88
- } else {
89
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
90
- updateCheckboxChildNodes(node);
91
- }
92
- };
93
-
94
- const updateRadioChildNodes = (parent) => {
95
- if (parent.isControlChecked) {
96
- for (let i = 0; i < parent.childItems.length; i++) {
97
- parent.childItems[i].isControlChecked = i === 0;
98
- }
99
- } else {
100
- for (const child of parent.childItems) {
101
- child.isControlChecked = false;
102
- }
103
- }
104
-
105
- if (parent.childItems.length) {
106
- if (parent.childItems[0].childItems.length) {
107
- updateChildNodes(parent.childItems[0]);
108
- }
109
- }
110
- };
111
-
112
- const updateCheckboxChildNodes = (parent) => {
113
- for (const child of parent.childItems) {
114
- child.isControlChecked = parent.isControlChecked;
115
- child.layer.setVisible(child.isControlChecked);
116
-
117
- if (child.childItems.length) {
118
- updateChildNodes(child);
119
- }
120
- }
121
- };
122
-
123
- const updateParentNodes = (node) => {
124
- if (node.parent) {
125
- if (node.parent.selectionType === SelectionType.CHECKBOX) {
126
- const newItem = {
127
- ...node.parent,
128
- isControlChecked: node.parent.childItems.some((child) => {
129
- return child.isControlChecked;
130
- }),
131
- };
132
- updateCheckedControlStatus(node.parent, newItem, true);
133
- } else {
134
- if (node?.parent?.parent) {
135
- const newItem = {
136
- ...node.parent,
137
- isControlChecked: node.parent.childItems.some((child) => {
138
- return child.isControlChecked;
139
- }),
140
- };
141
- updateCheckedControlStatus(node.parent, newItem, true);
142
- }
143
- }
144
- }
145
- };
146
-
147
- function layersTreeReducer(state = ROOT, action) {
148
- switch (action.type) {
149
- case "INIT":
150
- return initTree(action.payload);
151
- case "SELECT_ITEM":
152
- return setNewControlCheckedStatus(state, action.payload);
153
- default:
154
- return state;
155
- }
156
- }
157
-
158
- export default layersTreeReducer;
@@ -1,128 +0,0 @@
1
- import { twMerge } from "tailwind-merge";
2
-
3
- import RouteSchedule from "../RouteSchedule";
4
- import RvfExportMenu from "../RvfExportMenu";
5
- import RvfFeatureDetails from "../RvfFeatureDetails";
6
- import RvfFeatureDetailsFooter from "../RvfFeatureDetailsFooter";
7
- import RvfFeatureDetailsTitle from "../RvfFeatureDetailsTitle/RvfFeatureDetailsTitle";
8
- import RvfOverlayHeader from "../RvfOverlayHeader";
9
- import RvfShare from "../RvfShare";
10
- import RvfTopics from "../RvfTopics";
11
- import Search from "../Search";
12
- import Station from "../Station";
13
- import { LAYERS_TITLES } from "../utils/constants";
14
- import useMapContext from "../utils/hooks/useMapContext";
15
- import useRvfContext from "../utils/hooks/useRvfContext";
16
-
17
- const contentClassName = `relative h-full overflow-x-hidden overflow-y-auto text-base`;
18
-
19
- function RvfOverlayContent({
20
- hasDetails,
21
- hasLayerTree,
22
- hasPrint,
23
- hasRealtime,
24
- hasSearch,
25
- hasShare,
26
- }: {
27
- hasDetails: boolean;
28
- hasLayerTree: boolean;
29
- hasPrint: boolean;
30
- hasRealtime: boolean;
31
- hasSearch: boolean;
32
- hasShare: boolean;
33
- }) {
34
- const {
35
- isExportMenuOpen,
36
- isLayerTreeOpen,
37
- isSearchOpen,
38
- isShareMenuOpen,
39
- selectedFeature,
40
- setIsExportMenuOpen,
41
- setIsLayerTreeOpen,
42
- setIsShareMenuOpen,
43
- setSelectedFeature,
44
- } = useRvfContext();
45
- const { setStationId, setTrainId, stationId, tenant, trainId } =
46
- useMapContext();
47
- return (
48
- <>
49
- {hasRealtime && trainId && (
50
- <>
51
- <RvfOverlayHeader
52
- onClose={() => {
53
- setTrainId(null);
54
- }}
55
- title={LAYERS_TITLES.echtzeit}
56
- ></RvfOverlayHeader>
57
- <RouteSchedule className={contentClassName} />
58
- </>
59
- )}
60
- {tenant && stationId && (
61
- <>
62
- <RvfOverlayHeader
63
- onClose={() => {
64
- setStationId(null);
65
- }}
66
- title="Station"
67
- ></RvfOverlayHeader>
68
- <Station className={twMerge(contentClassName, "flex flex-col p-2")} />
69
- </>
70
- )}
71
- {hasDetails && selectedFeature && (
72
- <>
73
- <RvfOverlayHeader
74
- onClose={() => {
75
- setSelectedFeature(null);
76
- }}
77
- title={<RvfFeatureDetailsTitle feature={selectedFeature} />}
78
- ></RvfOverlayHeader>
79
- <RvfFeatureDetails
80
- className={twMerge(contentClassName, "relative")}
81
- />
82
- <RvfFeatureDetailsFooter className={"flex flex-row p-4 pt-2"} />
83
- </>
84
- )}
85
- {hasPrint && isExportMenuOpen && (
86
- <>
87
- <RvfOverlayHeader
88
- onClose={() => {
89
- setIsExportMenuOpen(false);
90
- }}
91
- title="Drucken"
92
- ></RvfOverlayHeader>
93
- <RvfExportMenu
94
- className={twMerge(contentClassName, "flex flex-col gap-4 p-4")}
95
- />
96
- </>
97
- )}
98
- {hasLayerTree && isLayerTreeOpen && (
99
- <>
100
- <RvfOverlayHeader
101
- onClose={() => {
102
- setIsLayerTreeOpen(false);
103
- }}
104
- title="Layers"
105
- ></RvfOverlayHeader>
106
- <RvfTopics className="relative flex h-full flex-col overflow-x-hidden overflow-y-auto p-2 text-base" />
107
- </>
108
- )}
109
- {hasShare && isShareMenuOpen && (
110
- <>
111
- <RvfOverlayHeader
112
- onClose={() => {
113
- setIsShareMenuOpen(false);
114
- }}
115
- title="Share"
116
- ></RvfOverlayHeader>
117
- <RvfShare className="relative flex h-full flex-col overflow-x-hidden overflow-y-auto p-4 text-base" />
118
- </>
119
- )}
120
- {hasSearch && isSearchOpen && (
121
- <>
122
- <Search className="relative flex h-full flex-col overflow-x-hidden overflow-y-auto p-2 text-base" />
123
- </>
124
- )}
125
- </>
126
- );
127
- }
128
- export default RvfOverlayContent;
File without changes
@@ -1,76 +0,0 @@
1
- import debounce from "lodash.debounce";
2
- import { getLayersAsFlatArray } from "mobility-toolbox-js/ol";
3
- import { unByKey } from "ol/Observable";
4
- import { useEffect } from "preact/hooks";
5
-
6
- import { LAYER_PROP_IS_EXPORTING } from "../constants";
7
- import getPermalinkParameters from "../getPermalinkParameters";
8
- import MobilityEvent from "../MobilityEvent";
9
-
10
- import type { Map } from "ol";
11
- import type { EventsKey } from "ol/events";
12
- import type { MutableRef } from "preact/hooks";
13
-
14
- const updatePermalink = (
15
- map: Map,
16
- eventNodeRef: MutableRef<HTMLDivElement>,
17
- ) => {
18
- // No update when exporting
19
- if (map.get(LAYER_PROP_IS_EXPORTING)) {
20
- return;
21
- }
22
- const currentUrlParams = new URLSearchParams(window.location.search);
23
- const urlParams = getPermalinkParameters(map, currentUrlParams);
24
- urlParams.set("permalink", "true");
25
- window.history.replaceState(null, null, `?${urlParams.toString()}`);
26
- eventNodeRef?.current?.dispatchEvent(
27
- new MobilityEvent<string>("mwc:permalink", window.location.href),
28
- );
29
- };
30
-
31
- const updatePermalinkDebounced = debounce(updatePermalink, 1000);
32
-
33
- /**
34
- * This hook only update parameters in the url, it does not apply the url parameters.
35
- */
36
- const useUpdatePermalink = (
37
- map: Map,
38
- permalink: boolean,
39
- eventNodeRef: MutableRef<HTMLDivElement>,
40
- ) => {
41
- useEffect(() => {
42
- let moveEndKey: EventsKey;
43
- let loadEndKey: EventsKey;
44
- let changeVisibleKeys: EventsKey[];
45
-
46
- if (map && permalink) {
47
- updatePermalinkDebounced(map);
48
-
49
- // Update x,y,z in URL on moveend
50
- moveEndKey = map?.on("moveend", (evt) => {
51
- updatePermalinkDebounced(evt.map, eventNodeRef);
52
- });
53
-
54
- // Update layers in URL on change:visible event
55
- loadEndKey = map.once("loadend", (evt) => {
56
- updatePermalinkDebounced(evt.map, eventNodeRef);
57
- changeVisibleKeys = getLayersAsFlatArray(
58
- evt.map.getLayers().getArray(),
59
- ).map((layer) => {
60
- return layer.on("change:visible", () => {
61
- updatePermalinkDebounced(evt.map, eventNodeRef);
62
- });
63
- });
64
- });
65
- }
66
-
67
- return () => {
68
- unByKey(moveEndKey);
69
- unByKey(loadEndKey);
70
- unByKey(changeVisibleKeys);
71
- };
72
- }, [map, permalink, eventNodeRef]);
73
- return null;
74
- };
75
-
76
- export default useUpdatePermalink;