@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.
- package/CHANGELOG.md +36 -0
- package/docutils.js +125 -51
- package/index.html +48 -21
- package/index.js +291 -234
- package/package.json +12 -12
- package/search.html +25 -22
- package/src/BaseLayer/BaseLayer.tsx +18 -2
- package/src/Copyright/Copyright.tsx +2 -2
- package/src/Copyright/index.tsx +1 -1
- package/src/LayerTree/TreeItem/TreeItem.tsx +1 -1
- package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +1 -0
- package/src/Map/Map.tsx +27 -1
- package/src/MapLayout/MapLayout.tsx +1 -1
- package/src/MapLayout/index.tsx +1 -1
- package/src/MobilityMap/MobilityMap.tsx +5 -11
- package/src/MobilityMap/MobilityMapAttributes.ts +8 -5
- package/src/MobilitySearch/MobilitySearchAttributes.ts +12 -0
- package/src/NotificationDetails/NotificationDetails.tsx +75 -57
- package/src/OverlayDetailsHeader/OverlayDetailsHeader.tsx +1 -1
- package/src/Permalink/Permalink.tsx +17 -6
- package/src/PermalinkInput/PermalinkInput.tsx +4 -1
- package/src/RealtimeLayer/index.tsx +1 -1
- package/src/RvfCopyright/RvfCopyright.tsx +32 -0
- package/src/RvfCopyright/index.tsx +1 -0
- package/src/RvfInputCopy/RvfInputCopy.tsx +18 -8
- package/src/RvfMapLayout/RvfMapLayout.tsx +198 -0
- package/src/RvfMapLayout/index.tsx +1 -0
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +10 -580
- package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +64 -0
- package/src/RvfRealtimeLayer/index.tsx +1 -0
- package/src/RvfStationsLayer/RvfStationsLayer.tsx +19 -0
- package/src/RvfStationsLayer/index.tsx +1 -0
- package/src/ShareMenu/ShareMenu.tsx +3 -1
- package/src/StationsLayer/index.tsx +1 -1
- package/src/ui/InputCopy/InputCopy.tsx +21 -10
- package/src/utils/constants.ts +1 -1
- package/src/utils/getUrlFromTemplate.test.ts +23 -0
- package/src/utils/getUrlFromTemplate.ts +47 -0
- package/src/utils/hooks/useI18n.tsx +2 -4
- package/src/utils/hooks/useInitialLayersVisiblity.tsx +27 -4
- package/src/utils/hooks/useInitialPermalink.tsx +31 -21
- package/src/utils/hooks/usePermalink.tsx +25 -0
- package/src/utils/translations.ts +4 -0
|
@@ -6,8 +6,8 @@ import { useCallback, useEffect } from "preact/hooks";
|
|
|
6
6
|
|
|
7
7
|
import { LAYER_PROP_IS_EXPORTING } from "../utils/constants";
|
|
8
8
|
import getPermalinkParameters from "../utils/getPermalinkParameters";
|
|
9
|
-
// import getLayersAsFlatArray from "../getLayersAsFlatArray";
|
|
10
9
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
10
|
+
import usePermalink from "../utils/hooks/usePermalink";
|
|
11
11
|
|
|
12
12
|
import type { Map } from "ol";
|
|
13
13
|
import type { EventsKey } from "ol/events";
|
|
@@ -20,6 +20,9 @@ import type { EventsKey } from "ol/events";
|
|
|
20
20
|
const Permalink = ({ replaceState = false }: { replaceState?: boolean }) => {
|
|
21
21
|
const { map, setPermalinkUrlSearchParams } = useMapContext();
|
|
22
22
|
|
|
23
|
+
const permalink = usePermalink();
|
|
24
|
+
|
|
25
|
+
// Update the search parameters when the map moves
|
|
23
26
|
const updatePermalink = useCallback(
|
|
24
27
|
(currentMap: Map) => {
|
|
25
28
|
// No update when exporting
|
|
@@ -28,15 +31,23 @@ const Permalink = ({ replaceState = false }: { replaceState?: boolean }) => {
|
|
|
28
31
|
}
|
|
29
32
|
const currentUrlParams = new URLSearchParams(window.location.search);
|
|
30
33
|
const urlParams = getPermalinkParameters(currentMap, currentUrlParams);
|
|
31
|
-
urlParams.set("permalink", "true");
|
|
32
34
|
setPermalinkUrlSearchParams(urlParams);
|
|
33
|
-
if (replaceState) {
|
|
34
|
-
window.history.replaceState(null, null, `?${urlParams.toString()}`);
|
|
35
|
-
}
|
|
36
35
|
},
|
|
37
|
-
[map,
|
|
36
|
+
[map, setPermalinkUrlSearchParams],
|
|
38
37
|
);
|
|
39
38
|
|
|
39
|
+
// Replace the window url when permalink changes
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (
|
|
42
|
+
replaceState &&
|
|
43
|
+
permalink &&
|
|
44
|
+
permalink !== window.location.href &&
|
|
45
|
+
!permalink.includes(encodeURIComponent("{{"))
|
|
46
|
+
) {
|
|
47
|
+
window.history.replaceState(null, null, permalink);
|
|
48
|
+
}
|
|
49
|
+
}, [map, permalink, replaceState]);
|
|
50
|
+
|
|
40
51
|
useEffect(() => {
|
|
41
52
|
let moveEndKey: EventsKey;
|
|
42
53
|
let loadEndKey: EventsKey;
|
|
@@ -2,6 +2,7 @@ import { memo } from "preact/compat";
|
|
|
2
2
|
|
|
3
3
|
import InputCopy from "../ui/InputCopy";
|
|
4
4
|
import useI18n from "../utils/hooks/useI18n";
|
|
5
|
+
import usePermalink from "../utils/hooks/usePermalink";
|
|
5
6
|
|
|
6
7
|
import type { HTMLAttributes, PreactDOMAttributes } from "preact";
|
|
7
8
|
|
|
@@ -19,9 +20,11 @@ function PermalinkInput({
|
|
|
19
20
|
...props
|
|
20
21
|
}: PermalinkInputProps) {
|
|
21
22
|
const { t } = useI18n();
|
|
23
|
+
const permalink = usePermalink();
|
|
24
|
+
|
|
22
25
|
return (
|
|
23
26
|
<div {...props}>
|
|
24
|
-
<InputCopy value={
|
|
27
|
+
<InputCopy value={permalink} {...inputProps} readonly={true} />
|
|
25
28
|
<p className="py-2">{t("permalink_input_hint")}</p>
|
|
26
29
|
</div>
|
|
27
30
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { default } from "
|
|
1
|
+
export { default } from "../RvfRealtimeLayer";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Copyright from "../Copyright/Copyright";
|
|
2
|
+
|
|
3
|
+
import type { CopyrightProps } from "../Copyright/Copyright";
|
|
4
|
+
|
|
5
|
+
const format = (copyrights) => {
|
|
6
|
+
const newCopyrights = [];
|
|
7
|
+
let alreadyAGeopsLink = false;
|
|
8
|
+
copyrights.forEach((copyright) => {
|
|
9
|
+
if (/geops/i.test(copyright)) {
|
|
10
|
+
// Hack until all geOps tiles are the same
|
|
11
|
+
// Currently there is some geops.com or geops.ch links
|
|
12
|
+
if (!alreadyAGeopsLink) {
|
|
13
|
+
alreadyAGeopsLink = true;
|
|
14
|
+
newCopyrights.push(copyright);
|
|
15
|
+
}
|
|
16
|
+
} else if (!/(sbb|rvf)/i.test(copyright)) {
|
|
17
|
+
newCopyrights.push(copyright);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return newCopyrights.join(" | ");
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const options = {
|
|
25
|
+
format,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function RvfCopyright(props: CopyrightProps) {
|
|
29
|
+
return <Copyright options={options} {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default RvfCopyright;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfCopyright";
|
|
@@ -1,37 +1,47 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useId, useState } from "preact/hooks";
|
|
2
2
|
|
|
3
3
|
import Copy from "../icons/Copy";
|
|
4
4
|
import IconButton from "../ui/IconButton";
|
|
5
|
+
import useI18n from "../utils/hooks/useI18n";
|
|
5
6
|
|
|
6
7
|
import type { JSX, PreactDOMAttributes } from "preact";
|
|
7
8
|
|
|
8
9
|
function RvfInputCopy(props: JSX.InputHTMLAttributes & PreactDOMAttributes) {
|
|
10
|
+
const { t } = useI18n();
|
|
9
11
|
const [positionTooltip, setPositionTooltip] = useState<DOMRect>();
|
|
10
12
|
const [isTooptipShowed, setIsTooltipShowed] = useState(false);
|
|
11
|
-
const
|
|
13
|
+
const inputId = useId();
|
|
14
|
+
const [node, setNode] = useState<HTMLDivElement | null>(null);
|
|
12
15
|
|
|
13
16
|
const handleCopyClick = (event) => {
|
|
14
17
|
setPositionTooltip(event.currentTarget.getBoundingClientRect());
|
|
15
|
-
|
|
18
|
+
const input: HTMLInputElement | null = node.querySelector(`#${inputId}`);
|
|
19
|
+
void navigator.clipboard.writeText(input?.value).then(() => {
|
|
16
20
|
setIsTooltipShowed(true);
|
|
17
21
|
setTimeout(() => {
|
|
18
22
|
setIsTooltipShowed(false);
|
|
19
23
|
}, 1000);
|
|
20
24
|
});
|
|
21
|
-
|
|
25
|
+
input?.select();
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
const handleInputFocus = () => {
|
|
25
|
-
|
|
29
|
+
const input: HTMLInputElement | null = node.querySelector(`#${inputId}`);
|
|
30
|
+
input?.select();
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
return (
|
|
29
|
-
<div
|
|
34
|
+
<div
|
|
35
|
+
className="text-grey flex items-center"
|
|
36
|
+
ref={(elt) => {
|
|
37
|
+
setNode(elt);
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
30
40
|
<input
|
|
31
41
|
className="h-7 w-full rounded-sm rounded-r-none border border-r-0 border-current p-1 leading-4 outline-none"
|
|
42
|
+
id={inputId}
|
|
32
43
|
onFocus={handleInputFocus}
|
|
33
44
|
readOnly
|
|
34
|
-
ref={inputRef}
|
|
35
45
|
type="text"
|
|
36
46
|
{...props}
|
|
37
47
|
/>
|
|
@@ -48,7 +58,7 @@ function RvfInputCopy(props: JSX.InputHTMLAttributes & PreactDOMAttributes) {
|
|
|
48
58
|
top: positionTooltip?.top - 40,
|
|
49
59
|
}}
|
|
50
60
|
>
|
|
51
|
-
|
|
61
|
+
{t("input_copy_success")}
|
|
52
62
|
</div>
|
|
53
63
|
</div>
|
|
54
64
|
);
|
|
@@ -0,0 +1,198 @@
|
|
|
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 RvfMainLinkButton from "../RvfMainLinkButton";
|
|
13
|
+
import RvfPoisLayer from "../RvfPoisLayer";
|
|
14
|
+
import RvfSelectedFeatureHighlightLayer from "../RvfSelectedFeatureHighlightLayer";
|
|
15
|
+
import RvfSellingPointsLayer from "../RvfSellingPointsLayer";
|
|
16
|
+
import RvfSharedMobilityLayerGroup from "../RvfSharedMobilityLayerGroup";
|
|
17
|
+
import RvfTarifZonenLayer from "../RvfTarifZonenLayer";
|
|
18
|
+
import ScaleLine from "../ScaleLine";
|
|
19
|
+
import Search from "../Search";
|
|
20
|
+
import SearchButton from "../SearchButton";
|
|
21
|
+
import ShareMenuButton from "../ShareMenuButton";
|
|
22
|
+
import StationsLayer from "../StationsLayer";
|
|
23
|
+
import useMapContext from "../utils/hooks/useMapContext";
|
|
24
|
+
import ZoomButtons from "../ZoomButtons";
|
|
25
|
+
|
|
26
|
+
import type { HTMLAttributes } from "preact";
|
|
27
|
+
|
|
28
|
+
const scrollableHandlerProps = {
|
|
29
|
+
style: { width: "calc(100% - 60px)" },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const stationsLayerProps = () => {
|
|
33
|
+
return {
|
|
34
|
+
layersFilter: ({ metadata }) => {
|
|
35
|
+
return metadata?.["general.filter"] === "stations";
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function RvfMapLayout({
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: { className?: string } & HTMLAttributes<HTMLDivElement>) {
|
|
44
|
+
const {
|
|
45
|
+
hasDetails,
|
|
46
|
+
hasGeolocation,
|
|
47
|
+
hasLayerTree,
|
|
48
|
+
hasPrint,
|
|
49
|
+
hasRealtime,
|
|
50
|
+
hasSearch,
|
|
51
|
+
hasShare,
|
|
52
|
+
hasToolbar,
|
|
53
|
+
isEmbed,
|
|
54
|
+
isOverlayOpen,
|
|
55
|
+
isSearchOpen,
|
|
56
|
+
mainlink,
|
|
57
|
+
} = useMapContext();
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className={twMerge(
|
|
62
|
+
"relative flex size-full flex-col text-base @lg/main:flex-row-reverse",
|
|
63
|
+
className,
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
>
|
|
67
|
+
<RvfSelectedFeatureHighlightLayer />
|
|
68
|
+
<StationsLayer minZoom={10} {...stationsLayerProps} />
|
|
69
|
+
<RvfTarifZonenLayer />
|
|
70
|
+
<RvfSellingPointsLayer />
|
|
71
|
+
<RvfPoisLayer />
|
|
72
|
+
<RvfSharedMobilityLayerGroup />
|
|
73
|
+
|
|
74
|
+
<Map className="relative flex-1 overflow-visible">
|
|
75
|
+
{isEmbed && <EmbedNavigation />}
|
|
76
|
+
{mainlink && (
|
|
77
|
+
<RvfMainLinkButton className="absolute inset-x-2 bottom-8 z-10" />
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
<div className="pointer-events-none absolute inset-x-2 bottom-2 z-10 flex items-end justify-between gap-2 text-[10px]">
|
|
81
|
+
<ScaleLine className="bg-slate-50/70" />
|
|
82
|
+
<Copyright className="pointer-events-auto bg-slate-50/70" />
|
|
83
|
+
</div>
|
|
84
|
+
<div className="absolute top-2 right-2 z-10 flex">
|
|
85
|
+
{hasGeolocation && <GeolocationButton title={"Geolokalisierung"} />}
|
|
86
|
+
</div>
|
|
87
|
+
<div className="absolute right-2 bottom-10 z-10 flex flex-col justify-between gap-2">
|
|
88
|
+
<ZoomButtons />
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{!hasToolbar && hasSearch && (
|
|
92
|
+
<div
|
|
93
|
+
className={twMerge(
|
|
94
|
+
"absolute top-2 right-2 left-2 z-10 max-w-96",
|
|
95
|
+
isOverlayOpen && "@lg:left-68",
|
|
96
|
+
)}
|
|
97
|
+
>
|
|
98
|
+
<Search />
|
|
99
|
+
</div>
|
|
100
|
+
)}
|
|
101
|
+
</Map>
|
|
102
|
+
|
|
103
|
+
<div className="pointer-events-none absolute top-2 bottom-2 left-2 z-10 flex flex-col gap-2">
|
|
104
|
+
{hasToolbar && (
|
|
105
|
+
<div
|
|
106
|
+
className={
|
|
107
|
+
"pointer-events-none relative z-10 w-fit rounded-2xl bg-black/10 p-0 backdrop-blur-sm *:pointer-events-auto"
|
|
108
|
+
}
|
|
109
|
+
// className="w-fit rounded-2xl bg-black/10 p-1 backdrop-blur-sm">
|
|
110
|
+
>
|
|
111
|
+
{hasSearch && (
|
|
112
|
+
<div
|
|
113
|
+
className={twMerge(
|
|
114
|
+
"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)]",
|
|
115
|
+
isSearchOpen ? "w-64 opacity-100" : "",
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<Search
|
|
119
|
+
className={
|
|
120
|
+
"border-grey @container m-0 h-[40px] rounded-2xl border p-2 px-4 text-base @sm/main:h-[44px] @sm/main:rounded-l-none @sm/main:rounded-r-2xl @md/main:h-[48px]"
|
|
121
|
+
}
|
|
122
|
+
inputClassName="h-6 text-base"
|
|
123
|
+
inputContainerClassName="border-none"
|
|
124
|
+
resultClassName="text-base **:hover:cursor-pointer hover:text-red-500 p-2"
|
|
125
|
+
resultsContainerClassName="@container rounded-b-2xl max-h-[200px] overflow-y-auto border border-grey border-t-0 "
|
|
126
|
+
withResultsClassName="text-base !rounded-b-none"
|
|
127
|
+
/>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
<div
|
|
132
|
+
className={twMerge(
|
|
133
|
+
"border-grey relative flex gap-[1px] overflow-hidden rounded-2xl border @sm/main:h-[44px] @md/main:h-[48px]",
|
|
134
|
+
"*:size-[38px] *:rounded-none *:border-none *:@sm/main:size-[42px] *:@md/main:!size-[46px]",
|
|
135
|
+
"*:first:!rounded-l-2xl",
|
|
136
|
+
"*:last:!rounded-r-2xl",
|
|
137
|
+
isSearchOpen
|
|
138
|
+
? "@sm:rounded-r-none @sm:border-r-0 @sm:*:last:!rounded-r-none @sm:*:last:border-r-0"
|
|
139
|
+
: "",
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
{hasPrint && <ExportMenuButton />}
|
|
143
|
+
{hasShare && <ShareMenuButton />}
|
|
144
|
+
{hasLayerTree && <LayerTreeButton />}
|
|
145
|
+
{hasSearch && <SearchButton />}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
|
|
150
|
+
{/* Desktop (>= lg) */}
|
|
151
|
+
<div
|
|
152
|
+
className={twMerge(
|
|
153
|
+
"pointer-events-none flex w-0 flex-1 flex-col overflow-hidden rounded-2xl",
|
|
154
|
+
isOverlayOpen ? "@lg:min-w-[320px]" : "p-0",
|
|
155
|
+
)}
|
|
156
|
+
style={{ containerType: "normal" }}
|
|
157
|
+
>
|
|
158
|
+
<Overlay
|
|
159
|
+
className={
|
|
160
|
+
"border-grey @container/overlay pointer-events-auto relative hidden flex-col overflow-hidden rounded-2xl border bg-white text-base shadow-lg @lg:flex"
|
|
161
|
+
}
|
|
162
|
+
ScrollableHandlerProps={scrollableHandlerProps}
|
|
163
|
+
>
|
|
164
|
+
<OverlayContent
|
|
165
|
+
hasDetails={hasDetails}
|
|
166
|
+
hasLayerTree={hasLayerTree}
|
|
167
|
+
hasPrint={hasPrint}
|
|
168
|
+
hasRealtime={hasRealtime}
|
|
169
|
+
hasSearch={false}
|
|
170
|
+
hasShare={hasShare}
|
|
171
|
+
/>
|
|
172
|
+
</Overlay>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{/* Mobile */}
|
|
177
|
+
<Overlay
|
|
178
|
+
className={
|
|
179
|
+
isOverlayOpen
|
|
180
|
+
? "absolute bottom-0 z-20 flex max-h-[70%] min-h-[75px] w-full flex-col border-t bg-white @lg:hidden"
|
|
181
|
+
: "@lg:hidden"
|
|
182
|
+
}
|
|
183
|
+
ScrollableHandlerProps={scrollableHandlerProps}
|
|
184
|
+
>
|
|
185
|
+
<OverlayContent
|
|
186
|
+
hasDetails={hasDetails}
|
|
187
|
+
hasLayerTree={hasLayerTree}
|
|
188
|
+
hasPrint={hasPrint}
|
|
189
|
+
hasRealtime={hasRealtime}
|
|
190
|
+
hasSearch={false} // The search could be in the overlay but we decide to put it in the toolbar for better UX
|
|
191
|
+
hasShare={hasShare}
|
|
192
|
+
/>
|
|
193
|
+
</Overlay>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default memo(RvfMapLayout);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./RvfMapLayout";
|