@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.
- package/CHANGELOG.md +46 -0
- package/README.md +16 -123
- package/docutils.js +1 -1
- package/index.html +4 -0
- package/index.js +154 -151
- package/package.json +1 -1
- package/src/ExportMenu/ExportMenu.tsx +7 -5
- package/src/ExportMenuButton/ExportMenuButton.tsx +8 -1
- package/src/GeolocationButton/GeolocationButton.tsx +9 -3
- package/src/LayerTree/TreeItem/TreeItem.tsx +3 -1
- package/src/LayerTreeButton/LayerTreeButton.tsx +10 -4
- package/src/LayerTreeMenu/LayerTreeMenu.tsx +15 -5
- package/src/LinesNetworkPlanLayer/LinesNetworkPlanLayer.tsx +5 -2
- package/src/MapDispatchEvents/MapDispatchEvents.tsx +7 -0
- package/src/MapLayout/MapLayout.tsx +176 -0
- package/src/MapLayout/index.tsx +1 -0
- package/src/MobilityMap/MobilityMap.tsx +25 -139
- package/src/MobilityMap/MobilityMapAttributes.ts +15 -3
- package/src/NotificationDetails/NotificationDetails.tsx +23 -8
- package/src/OverlayContent/OverlayContent.tsx +5 -3
- package/src/OverlayDetails/OverlayDetails.tsx +29 -11
- package/src/OverlayDetailsHeader/OverlayDetailsHeader.tsx +11 -14
- package/src/Permalink/Permalink.tsx +5 -5
- package/src/PermalinkInput/PermalinkInput.tsx +6 -4
- package/src/RealtimeLayer/RealtimeLayer.tsx +10 -9
- package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +1 -1
- package/src/RvfFeatureDetails/RvfNotificationDetails/RvfNotificationDetails.tsx +23 -8
- package/src/RvfMobilityMap/RvfMobilityMap.tsx +44 -41
- package/src/RvfSellingPointsLayer/RvfSellingPointsLayer.tsx +6 -6
- package/src/RvfSharedMobilityLayerGroup/RvfSharedMobilityLayerGroup.tsx +76 -58
- package/src/Search/Search.tsx +1 -1
- package/src/SearchButton/SearchButton.tsx +10 -4
- package/src/ShareMenu/ShareMenu.tsx +4 -2
- package/src/ShareMenuButton/ShareMenuButton.tsx +10 -3
- package/src/StationsLayer/StationsLayer.tsx +4 -1
- package/src/ZoomButtons/ZoomButtons.tsx +12 -2
- package/src/index.tsx +13 -5
- package/src/utils/constants.ts +20 -40
- package/src/utils/getPermalinkParameters.ts +12 -4
- package/src/utils/hooks/useInitialPermalink.tsx +83 -0
- package/src/utils/hooks/useLayerConfig.tsx +1 -3
- package/src/utils/hooks/useLayersConfig.tsx +2 -2
- package/src/utils/i18n.ts +0 -10
- package/src/utils/translations.ts +165 -0
- package/src/RvfFeatureDetailsFooter/RvfFeatureDetailsFooter.tsx +0 -43
- package/src/RvfFeatureDetailsFooter/index.tsx +0 -1
- package/src/RvfFeatureDetailsTitle/RvfFeatureDetailsTitle.tsx +0 -81
- package/src/RvfFeatureDetailsTitle/index.tsx +0 -1
- package/src/RvfLayerTree/RvfLayerTree.tsx +0 -40
- package/src/RvfLayerTree/TreeItem/TreeItem.tsx +0 -145
- package/src/RvfLayerTree/TreeItem/index.tsx +0 -1
- package/src/RvfLayerTree/index.tsx +0 -1
- package/src/RvfLayerTree/layersTreeContext.ts +0 -4
- package/src/RvfLayerTree/layersTreeReducer.ts +0 -158
- package/src/RvfOverlayContent/RvfOverlayContent.tsx +0 -128
- package/src/RvfOverlayContent/index.ts +0 -0
- 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
|
-
<
|
|
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
|
-
|
|
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
|
-
{
|
|
394
|
-
|
|
395
|
-
|
|
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>{` (
|
|
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 || "
|
|
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"}>
|
|
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"}>
|
|
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
|
-
|
|
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={"
|
|
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={"
|
|
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="
|
|
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 {
|
|
15
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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={
|
|
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={
|
|
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
|
|
1
|
+
import { memo } from "preact/compat";
|
|
2
2
|
|
|
3
3
|
import OverlayHeader from "../OverlayHeader";
|
|
4
|
-
import
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
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-
|
|
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>}
|