@geops/rvf-mobility-web-component 0.1.83 → 0.1.84
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 +33 -0
- package/index.js +206 -187
- package/package.json +2 -2
- package/src/Departure/Departure.tsx +6 -3
- package/src/FeatureDetails/FeatureDetails.tsx +4 -4
- package/src/FeaturesInfosListener/FeaturesInfosListener.tsx +22 -0
- package/src/LayerTreeMenu/LayerTreeMenu.tsx +12 -4
- package/src/LayoutState/LayoutState.tsx +144 -63
- package/src/LinesNetworkPlanDetails/LinesNetworkPlanDetails.tsx +2 -2
- package/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +2 -40
- package/src/MapLayout/MapLayout.tsx +2 -7
- package/src/MobilityMap/MobilityMap.tsx +1 -6
- package/src/MobilityMap/MobilityMapAttributes.ts +28 -7
- package/src/NotificationDetails/NotificationDetails.tsx +21 -22
- package/src/NotificationsLayer/NotificationsLayer.tsx +47 -3
- package/src/OverlayDetails/OverlayDetails.tsx +5 -0
- package/src/RealtimeLayer/RealtimeLayer.tsx +44 -50
- package/src/RouteIcon/RouteIcon.tsx +25 -14
- package/src/RouteSchedule/RouteSchedule.tsx +14 -11
- package/src/RouteScheduleFooter/RouteScheduleFooter.tsx +6 -3
- package/src/RouteScheduleHeader/RouteScheduleHeader.tsx +8 -2
- package/src/RouteStop/RouteStop.tsx +14 -29
- package/src/RouteStopPlatform/RouteStopPlatform.tsx +1 -3
- package/src/RouteStopProgress/RouteStopProgress.tsx +11 -18
- package/src/RouteStopStation/RouteStopStation.tsx +3 -3
- package/src/RvfRealtimeLayer/RvfRealtimeLayer.tsx +16 -3
- package/src/RvfSearch/RvfSearch.tsx +5 -4
- package/src/Search/Search.tsx +41 -20
- package/src/Search/SearchBase.tsx +169 -0
- package/src/Search/SearchHeadless.tsx +79 -0
- package/src/Search/index.tsx +2 -0
- package/src/SearchLinesResult/SearchLinesResult.tsx +43 -0
- package/src/SearchLinesResult/index.tsx +1 -0
- package/src/SearchLinesResults/SearchLinesResults.tsx +106 -0
- package/src/SearchLinesResults/index.tsx +1 -0
- package/src/SearchLnpStopsResult/SearchLnpStopsResult.tsx +42 -0
- package/src/SearchLnpStopsResult/index.tsx +1 -0
- package/src/SearchLnpStopsResults/SearchLnpStopsResults.tsx +106 -0
- package/src/SearchLnpStopsResults/index.tsx +1 -0
- package/src/SearchResult/SearchResult.tsx +25 -0
- package/src/SearchResult/index.tsx +1 -0
- package/src/SearchResults/SearchResults.tsx +81 -0
- package/src/SearchResults/index.tsx +1 -0
- package/src/SearchResultsHeader/SearchResultsHeader.tsx +36 -0
- package/src/SearchResultsHeader/index.tsx +1 -0
- package/src/SearchStopsResult/SearchStopsResult.tsx +43 -0
- package/src/SearchStopsResult/index.tsx +1 -0
- package/src/SearchStopsResults/SearchStopsResults.tsx +101 -0
- package/src/SearchStopsResults/index.tsx +1 -0
- package/src/SearchTrainsResult/SearchTrainsResult.tsx +42 -0
- package/src/SearchTrainsResult/index.tsx +1 -0
- package/src/SearchTrainsResults/SearchTrainsResults.tsx +109 -0
- package/src/SearchTrainsResults/index.tsx +1 -0
- package/src/SingleClickListener/SingleClickListener.tsx +38 -2
- package/src/Station/Station.tsx +23 -48
- package/src/StationHeader/StationHeader.tsx +3 -3
- package/src/StationsLayer/StationsLayer.tsx +9 -1
- package/src/StopsSearch/StopsSearch.tsx +2 -2
- package/src/ui/InputSearch/InputSearch.tsx +105 -0
- package/src/ui/InputSearch/index.tsx +1 -0
- package/src/utils/centerOnVehicle.ts +9 -2
- package/src/utils/constants.ts +8 -1
- package/src/utils/fullTrajectoryStyle.ts +4 -7
- package/src/utils/getBgColor.ts +4 -2
- package/src/utils/getDelayColorForVehicle.test.ts +21 -11
- package/src/utils/getDelayColorForVehicle.ts +7 -5
- package/src/utils/getDelayTextForVehicle.test.ts +12 -12
- package/src/utils/getDelayTextForVehicle.ts +4 -0
- package/src/utils/getMainColorForVehicle.ts +11 -4
- package/src/utils/getRadius.ts +9 -3
- package/src/utils/getTextColor.ts +1 -1
- package/src/utils/getTextColorForVehicle.ts +29 -0
- package/src/utils/getTextFontForVehicle.test.ts +1 -1
- package/src/utils/getTextFontForVehicle.tsx +11 -3
- package/src/utils/getTextForVehicle.ts +7 -1
- package/src/utils/hooks/useFit.tsx +69 -0
- package/src/utils/hooks/useFitOnFeatures.tsx +77 -0
- package/src/utils/hooks/useLayersConfig.tsx +3 -0
- package/src/utils/hooks/useLnp.tsx +39 -5
- package/src/utils/hooks/useMapContext.tsx +2 -5
- package/src/utils/hooks/useRealtimeDepartures.tsx +45 -0
- package/src/utils/hooks/useRealtimeRenderedTrajectory.tsx +42 -0
- package/src/utils/hooks/useRealtimeStation.tsx +39 -0
- package/src/utils/hooks/useRealtimeStopSequences.tsx +43 -0
- package/src/utils/hooks/useRealtimeTrainsByRouteIdentifier.tsx +71 -0
- package/src/utils/hooks/useRouteStop.tsx +7 -1
- package/src/utils/hooks/useSearchLines.tsx +34 -0
- package/src/utils/hooks/useSearchLnpStops.tsx +38 -0
- package/src/utils/hooks/useSearchStops.tsx +85 -0
- package/src/utils/hooks/useSearchTrains.tsx +83 -0
- package/src/utils/realtimeRVFStyle.ts +38 -30
- package/src/utils/translations.ts +17 -0
- package/tash +58 -0
- package/src/utils/centerOnStation.ts +0 -18
- package/src/utils/getDelayFontForVehicle.test.ts +0 -7
- package/src/utils/getDelayFontForVehicle.tsx +0 -8
- package/src/utils/hooks/useStation.tsx +0 -22
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { cloneElement, toChildArray } from "preact";
|
|
2
|
+
import { memo } from "preact/compat";
|
|
3
|
+
import { useCallback, useContext, useMemo } from "preact/hooks";
|
|
4
|
+
|
|
5
|
+
import { SearchContext } from "../Search/SearchBase";
|
|
6
|
+
import SearchResult from "../SearchResult";
|
|
7
|
+
import SearchResults from "../SearchResults";
|
|
8
|
+
import SearchResultsHeader from "../SearchResultsHeader";
|
|
9
|
+
import useI18n from "../utils/hooks/useI18n";
|
|
10
|
+
import useSearchTrains from "../utils/hooks/useSearchTrains";
|
|
11
|
+
|
|
12
|
+
import type { RealtimeRouteIdentifierMatch } from "mobility-toolbox-js/types";
|
|
13
|
+
import type { ReactElement } from "preact/compat";
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
SearchResultsChildProps,
|
|
17
|
+
SearchResultsProps,
|
|
18
|
+
} from "../SearchResults/SearchResults";
|
|
19
|
+
|
|
20
|
+
function SearchTrainsResults({
|
|
21
|
+
children,
|
|
22
|
+
filter,
|
|
23
|
+
resultClassName,
|
|
24
|
+
resultsClassName,
|
|
25
|
+
resultsContainerClassName,
|
|
26
|
+
sort,
|
|
27
|
+
}: SearchResultsProps<RealtimeRouteIdentifierMatch>) {
|
|
28
|
+
const { open, query, setOpen, setSelectedQuery } = useContext(SearchContext);
|
|
29
|
+
const searchResponse = useSearchTrains(query);
|
|
30
|
+
|
|
31
|
+
const { t } = useI18n();
|
|
32
|
+
|
|
33
|
+
const onSelectResult = useCallback(
|
|
34
|
+
(item: RealtimeRouteIdentifierMatch) => {
|
|
35
|
+
setSelectedQuery(item.route_identifier);
|
|
36
|
+
setOpen(false);
|
|
37
|
+
},
|
|
38
|
+
[setOpen, setSelectedQuery],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const results = useMemo(() => {
|
|
42
|
+
let rs = [...(searchResponse?.results || [])];
|
|
43
|
+
|
|
44
|
+
if (filter) {
|
|
45
|
+
rs = rs.filter(filter);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (sort) {
|
|
49
|
+
rs = rs.sort(sort);
|
|
50
|
+
}
|
|
51
|
+
return rs;
|
|
52
|
+
}, [searchResponse, filter, sort]);
|
|
53
|
+
|
|
54
|
+
const searchResponseFiltered = useMemo(() => {
|
|
55
|
+
return {
|
|
56
|
+
...searchResponse,
|
|
57
|
+
results,
|
|
58
|
+
};
|
|
59
|
+
}, [results, searchResponse]);
|
|
60
|
+
|
|
61
|
+
const showResults = useMemo(() => {
|
|
62
|
+
return open && !!results?.length;
|
|
63
|
+
}, [open, results]);
|
|
64
|
+
|
|
65
|
+
if (!showResults) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<SearchResultsHeader>{t("search_trains_results")}</SearchResultsHeader>
|
|
72
|
+
<SearchResults
|
|
73
|
+
className={resultsContainerClassName}
|
|
74
|
+
resultsClassName={resultsClassName}
|
|
75
|
+
searchResponse={searchResponseFiltered}
|
|
76
|
+
>
|
|
77
|
+
{results.map((item: RealtimeRouteIdentifierMatch) => {
|
|
78
|
+
return (
|
|
79
|
+
<SearchResult
|
|
80
|
+
className={resultClassName}
|
|
81
|
+
key={item.trains[0].train_id}
|
|
82
|
+
>
|
|
83
|
+
{toChildArray(children).map(
|
|
84
|
+
(
|
|
85
|
+
child: ReactElement<
|
|
86
|
+
SearchResultsChildProps<RealtimeRouteIdentifierMatch>
|
|
87
|
+
>,
|
|
88
|
+
) => {
|
|
89
|
+
const onSelectItem = (
|
|
90
|
+
itemm: RealtimeRouteIdentifierMatch,
|
|
91
|
+
evt: Event,
|
|
92
|
+
) => {
|
|
93
|
+
onSelectResult(itemm);
|
|
94
|
+
child.props?.onSelectItem?.(itemm, evt);
|
|
95
|
+
};
|
|
96
|
+
return cloneElement(child, {
|
|
97
|
+
item: item,
|
|
98
|
+
onSelectItem,
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
)}
|
|
102
|
+
</SearchResult>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
105
|
+
</SearchResults>
|
|
106
|
+
</>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
export default memo(SearchTrainsResults);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./SearchTrainsResults";
|
|
@@ -25,6 +25,10 @@ function SingleClickListener({
|
|
|
25
25
|
queryablelayers,
|
|
26
26
|
setFeaturesInfos,
|
|
27
27
|
setFeaturesInfosHovered,
|
|
28
|
+
setLinesIds,
|
|
29
|
+
setNotificationId,
|
|
30
|
+
setStationId,
|
|
31
|
+
setTrainId,
|
|
28
32
|
stationsLayer,
|
|
29
33
|
tenant,
|
|
30
34
|
} = useMapContext();
|
|
@@ -50,7 +54,19 @@ function SingleClickListener({
|
|
|
50
54
|
const stationsFeatures = featuresInfoStations?.features || [];
|
|
51
55
|
|
|
52
56
|
const [stationFeature] = stationsFeatures.filter((feat) => {
|
|
53
|
-
|
|
57
|
+
// TODO: think how to do better. LNP stations should have a tralis_network property?
|
|
58
|
+
// travic stations has a tralis_network property
|
|
59
|
+
if (feat.get("tralis_network")) {
|
|
60
|
+
return feat.get("tralis_network").includes(tenant);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// We move the external_id to uid to be consistent across all stations (lnp and others)
|
|
64
|
+
if (!feat.get("uid") && feat.get("external_id")) {
|
|
65
|
+
feat.set("uid", feat.get("external_id"));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// LNP stations have no tralis_network property
|
|
69
|
+
return true;
|
|
54
70
|
});
|
|
55
71
|
|
|
56
72
|
// Replace the features clicked in the stations layer by the filtered one
|
|
@@ -85,8 +101,28 @@ function SingleClickListener({
|
|
|
85
101
|
async (evt: MapBrowserEvent<PointerEvent>) => {
|
|
86
102
|
const featuresInfos = await getFeaturesInfosAtEvt(evt);
|
|
87
103
|
setFeaturesInfos(featuresInfos);
|
|
104
|
+
// When user click we close the overlay
|
|
105
|
+
if (
|
|
106
|
+
featuresInfos?.flatMap((fi) => {
|
|
107
|
+
return fi.features;
|
|
108
|
+
}).length === 0
|
|
109
|
+
) {
|
|
110
|
+
// It means no feature selectable were clicked so we set all ids to null
|
|
111
|
+
// to close the overlay
|
|
112
|
+
setTrainId(null);
|
|
113
|
+
setStationId(null);
|
|
114
|
+
setNotificationId(null);
|
|
115
|
+
setLinesIds(null);
|
|
116
|
+
}
|
|
88
117
|
},
|
|
89
|
-
[
|
|
118
|
+
[
|
|
119
|
+
getFeaturesInfosAtEvt,
|
|
120
|
+
setFeaturesInfos,
|
|
121
|
+
setLinesIds,
|
|
122
|
+
setNotificationId,
|
|
123
|
+
setStationId,
|
|
124
|
+
setTrainId,
|
|
125
|
+
],
|
|
90
126
|
);
|
|
91
127
|
|
|
92
128
|
useEffect(() => {
|
package/src/Station/Station.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { debounceDeparturesMessages } from "mobility-toolbox-js/ol";
|
|
2
1
|
import { memo } from "preact/compat";
|
|
3
|
-
import { useEffect, useRef, useState } from "preact/hooks";
|
|
4
2
|
import { twMerge } from "tailwind-merge";
|
|
5
3
|
|
|
6
4
|
import Departure from "../Departure";
|
|
5
|
+
import ShadowOverflow from "../ShadowOverflow";
|
|
7
6
|
import StationHeader from "../StationHeader";
|
|
8
7
|
import useMapContext from "../utils/hooks/useMapContext";
|
|
8
|
+
import useRealtimeDepartures from "../utils/hooks/useRealtimeDepartures";
|
|
9
|
+
import useRealtimeStation from "../utils/hooks/useRealtimeStation";
|
|
9
10
|
|
|
10
11
|
import type { RealtimeDeparture } from "mobility-toolbox-js/types";
|
|
11
12
|
import type { HTMLAttributes, PreactDOMAttributes } from "preact";
|
|
@@ -15,34 +16,10 @@ export type StationProps = {
|
|
|
15
16
|
} & HTMLAttributes<HTMLDivElement> &
|
|
16
17
|
PreactDOMAttributes;
|
|
17
18
|
|
|
18
|
-
function Station(props: StationProps) {
|
|
19
|
-
const {
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const { className } = props;
|
|
23
|
-
|
|
24
|
-
useEffect(() => {
|
|
25
|
-
if (!station || !realtimeLayer?.api) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const onMessage = debounceDeparturesMessages(
|
|
30
|
-
(newDepartures: RealtimeDeparture[]) => {
|
|
31
|
-
setDepartures(newDepartures);
|
|
32
|
-
return null;
|
|
33
|
-
},
|
|
34
|
-
false,
|
|
35
|
-
180,
|
|
36
|
-
);
|
|
37
|
-
// @ts-expect-error bad type definition
|
|
38
|
-
realtimeLayer.api.subscribeDepartures(station?.properties?.uid, onMessage);
|
|
39
|
-
|
|
40
|
-
return () => {
|
|
41
|
-
setDepartures(null);
|
|
42
|
-
// @ts-expect-error bad type definition
|
|
43
|
-
realtimeLayer?.api?.unsubscribeDepartures(station?.properties.uid);
|
|
44
|
-
};
|
|
45
|
-
}, [station, realtimeLayer?.api]);
|
|
19
|
+
function Station({ className, ...props }: StationProps) {
|
|
20
|
+
const { stationId } = useMapContext();
|
|
21
|
+
const station = useRealtimeStation(stationId);
|
|
22
|
+
const departures = useRealtimeDepartures(stationId);
|
|
46
23
|
|
|
47
24
|
if (!station) {
|
|
48
25
|
return null;
|
|
@@ -50,24 +27,22 @@ function Station(props: StationProps) {
|
|
|
50
27
|
|
|
51
28
|
return (
|
|
52
29
|
<>
|
|
53
|
-
<StationHeader />
|
|
54
|
-
<
|
|
55
|
-
className={twMerge("flex flex-col p-2", className)}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
})}
|
|
70
|
-
</div>
|
|
30
|
+
<StationHeader station={station} />
|
|
31
|
+
<ShadowOverflow>
|
|
32
|
+
<div className={twMerge("flex flex-col p-2", className)} {...props}>
|
|
33
|
+
{(departures || [])
|
|
34
|
+
// .filter(hideDepartures)
|
|
35
|
+
.map((departure: RealtimeDeparture, index: number) => {
|
|
36
|
+
return (
|
|
37
|
+
<Departure
|
|
38
|
+
departure={departure}
|
|
39
|
+
index={index}
|
|
40
|
+
key={departure.call_id}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
})}
|
|
44
|
+
</div>
|
|
45
|
+
</ShadowOverflow>
|
|
71
46
|
</>
|
|
72
47
|
);
|
|
73
48
|
}
|
|
@@ -2,10 +2,10 @@ import { memo } from "preact/compat";
|
|
|
2
2
|
|
|
3
3
|
import StationName from "../StationName";
|
|
4
4
|
import StationServices from "../StationServices";
|
|
5
|
-
import useMapContext from "../utils/hooks/useMapContext";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import type { RealtimeStation } from "mobility-toolbox-js/types";
|
|
7
|
+
|
|
8
|
+
function StationHeader({ station }: { station: RealtimeStation }) {
|
|
9
9
|
return (
|
|
10
10
|
<div className="flex items-center gap-x-4 bg-slate-100 p-4">
|
|
11
11
|
<div className="flex grow flex-col">
|
|
@@ -18,11 +18,19 @@ function StationsLayer(props: Partial<MaplibreStyleLayerOptions>) {
|
|
|
18
18
|
layersFilter: ({ metadata }) => {
|
|
19
19
|
return (
|
|
20
20
|
metadata?.["tralis.variable"] === "station" ||
|
|
21
|
-
metadata?.["general.filter"] === "stations"
|
|
21
|
+
metadata?.["general.filter"] === "stations" ||
|
|
22
|
+
metadata?.["geops.filter"] === "netzplan_stops"
|
|
22
23
|
);
|
|
23
24
|
},
|
|
24
25
|
maplibreLayer: baseLayer,
|
|
25
26
|
name: LAYER_NAME_STATIONS,
|
|
27
|
+
// queryRenderedLayersFilter: ({ metadata }) => {
|
|
28
|
+
// return (
|
|
29
|
+
// metadata?.["tralis.variable"] === "station" ||
|
|
30
|
+
// metadata?.["general.filter"] === "stations" ||
|
|
31
|
+
// metadata?.["geops.filter"] === "netzplan_stops" // we include lnp stations only on click
|
|
32
|
+
// );
|
|
33
|
+
// },
|
|
26
34
|
...(props || {}),
|
|
27
35
|
});
|
|
28
36
|
}, [baseLayer, props]);
|
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
TargetedKeyboardEvent,
|
|
25
25
|
} from "preact";
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
import type { StopsFeature } from "../utils/hooks/useSearchStops";
|
|
28
28
|
|
|
29
29
|
export type StopsSearchProps = {
|
|
30
30
|
apikey: string;
|
|
@@ -235,7 +235,7 @@ function StopsSearch({
|
|
|
235
235
|
searchIconContainerClassName,
|
|
236
236
|
)}
|
|
237
237
|
>
|
|
238
|
-
<Search
|
|
238
|
+
<Search />
|
|
239
239
|
</div>
|
|
240
240
|
<div
|
|
241
241
|
className={twMerge(
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { memo } from "preact/compat";
|
|
2
|
+
import { useRef } from "preact/hooks";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
|
|
5
|
+
import Cancel from "../../icons/Cancel";
|
|
6
|
+
import Search from "../../icons/Search";
|
|
7
|
+
import useI18n from "../../utils/hooks/useI18n";
|
|
8
|
+
import IconButton from "../IconButton";
|
|
9
|
+
import Input from "../Input";
|
|
10
|
+
|
|
11
|
+
import type { PreactDOMAttributes } from "preact";
|
|
12
|
+
import type { HTMLAttributes, ReactNode } from "preact/compat";
|
|
13
|
+
|
|
14
|
+
import type { IconButtonProps } from "../IconButton/IconButton";
|
|
15
|
+
import type { InputProps } from "../Input/Input";
|
|
16
|
+
|
|
17
|
+
export type InputSearchProps = {
|
|
18
|
+
cancelButtonClassName?: string;
|
|
19
|
+
cancelButtonProps?: IconButtonProps;
|
|
20
|
+
cancelIcon?: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
inputClassName?: string;
|
|
23
|
+
inputContainerClassName?: string;
|
|
24
|
+
inputProps?: InputProps;
|
|
25
|
+
resultClassName?: string;
|
|
26
|
+
resultsClassName?: string;
|
|
27
|
+
resultsContainerClassName?: string;
|
|
28
|
+
searchIcon?: ReactNode;
|
|
29
|
+
searchIconContainerClassName?: string;
|
|
30
|
+
withResultsClassName?: string;
|
|
31
|
+
} & HTMLAttributes<HTMLDivElement> &
|
|
32
|
+
PreactDOMAttributes;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Rich search input component.
|
|
36
|
+
*/
|
|
37
|
+
function InputSearch({
|
|
38
|
+
cancelButtonClassName,
|
|
39
|
+
cancelButtonProps,
|
|
40
|
+
cancelIcon,
|
|
41
|
+
children,
|
|
42
|
+
className,
|
|
43
|
+
inputClassName,
|
|
44
|
+
inputContainerClassName,
|
|
45
|
+
inputProps,
|
|
46
|
+
searchIcon,
|
|
47
|
+
searchIconContainerClassName,
|
|
48
|
+
withResultsClassName,
|
|
49
|
+
}: InputSearchProps) {
|
|
50
|
+
const { t } = useI18n();
|
|
51
|
+
const myRef = useRef<HTMLDivElement>();
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<div
|
|
56
|
+
className={twMerge(
|
|
57
|
+
"flex h-16 items-center gap-4 rounded-md bg-white p-4 pt-3.5 shadow",
|
|
58
|
+
className,
|
|
59
|
+
withResultsClassName,
|
|
60
|
+
)}
|
|
61
|
+
ref={myRef}
|
|
62
|
+
>
|
|
63
|
+
<div
|
|
64
|
+
className={twMerge(
|
|
65
|
+
"text-grey flex items-center",
|
|
66
|
+
searchIconContainerClassName,
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{searchIcon || <Search />}
|
|
70
|
+
</div>
|
|
71
|
+
<div
|
|
72
|
+
className={twMerge(
|
|
73
|
+
"@container/inputsearch flex grow items-center gap-2 border-b-2 border-solid",
|
|
74
|
+
inputContainerClassName,
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
<Input
|
|
78
|
+
autoComplete="off"
|
|
79
|
+
className={twMerge(
|
|
80
|
+
"h-8 w-1 grow overflow-hidden text-ellipsis placeholder:text-zinc-400",
|
|
81
|
+
inputClassName,
|
|
82
|
+
)}
|
|
83
|
+
type="text"
|
|
84
|
+
{...(inputProps || {})}
|
|
85
|
+
/>
|
|
86
|
+
{!!inputProps.value && (
|
|
87
|
+
<IconButton
|
|
88
|
+
className={twMerge(
|
|
89
|
+
"flex size-4 items-center rounded-none border-none bg-transparent p-0 shadow-none",
|
|
90
|
+
cancelButtonClassName,
|
|
91
|
+
)}
|
|
92
|
+
title={t("search_input_cancel")}
|
|
93
|
+
{...(cancelButtonProps || {})}
|
|
94
|
+
>
|
|
95
|
+
{cancelIcon || <Cancel />}
|
|
96
|
+
</IconButton>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
{children}
|
|
101
|
+
</>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export default memo(InputSearch);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./InputSearch";
|
|
@@ -8,6 +8,7 @@ const centerOnVehicle = async (
|
|
|
8
8
|
vehicle: RealtimeTrajectory,
|
|
9
9
|
map: Map,
|
|
10
10
|
targetZoom = 0,
|
|
11
|
+
isOverlayOpen = true,
|
|
11
12
|
) => {
|
|
12
13
|
if (!vehicle) {
|
|
13
14
|
return Promise.reject(new Error("No vehicle provided"));
|
|
@@ -21,15 +22,21 @@ const centerOnVehicle = async (
|
|
|
21
22
|
const zoom = targetZoom || view.getZoom();
|
|
22
23
|
const resolution = zoom > 0 ? view.getResolutionForZoom(zoom) : undefined;
|
|
23
24
|
|
|
24
|
-
let center = coordinate;
|
|
25
|
+
let center = coordinate ? [...coordinate] : null;
|
|
25
26
|
if (!center && geometry) {
|
|
26
27
|
const { coord } = getVehiclePosition(Date.now(), vehicle, true);
|
|
27
|
-
center = coord
|
|
28
|
+
center = coord ? [...coord] : null;
|
|
28
29
|
}
|
|
29
30
|
if (!center) {
|
|
30
31
|
return Promise.reject(new Error("No center found"));
|
|
31
32
|
}
|
|
33
|
+
if (isOverlayOpen) {
|
|
34
|
+
// Adjust center to take in account the opened overlay.
|
|
35
|
+
center[0] -= (320 / 2) * resolution; // shift right by 400px
|
|
36
|
+
// console.log(center);
|
|
37
|
+
}
|
|
32
38
|
|
|
39
|
+
// Shift of 150px to the left and 50px to the top.
|
|
33
40
|
view.cancelAnimations();
|
|
34
41
|
|
|
35
42
|
const promise = new Promise((resolve) => {
|
package/src/utils/constants.ts
CHANGED
|
@@ -224,7 +224,7 @@ export const LNP_LINE_ID_PROP = "original_line_id";
|
|
|
224
224
|
// LNP data source id in the style
|
|
225
225
|
export const LNP_SOURCE_ID = "network_plans";
|
|
226
226
|
|
|
227
|
-
//
|
|
227
|
+
// LNP metadata key in the lnp data source
|
|
228
228
|
export const LNP_MD_LINES = "geops.lnp.lines";
|
|
229
229
|
export const LNP_MD_STOPS = "geops.lnp.stops";
|
|
230
230
|
|
|
@@ -233,3 +233,10 @@ export const LNP_GEOPS_FILTER_HIGHLIGHT = "highlightnetzplan";
|
|
|
233
233
|
|
|
234
234
|
// LNP style layer id where the dynamic filtering will apply
|
|
235
235
|
export const LNP_LAYER_ID_HIGHLIGHT = "netzplan_highlight_trip";
|
|
236
|
+
|
|
237
|
+
// Layer props used by layer and/or layerConfig
|
|
238
|
+
export const LAYER_TREE_HIDE_PROP = "layerTreeHidden";
|
|
239
|
+
export const LAYER_TREE_TITLE_FUNC_PROP = "layerTreeTitleRenderFunc";
|
|
240
|
+
|
|
241
|
+
/** FIT ON FEATURES */
|
|
242
|
+
export const FIT_ON_FEATURES_MAX_ZOOM_POINT = 16;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Circle, Fill, Stroke, Style } from "ol/style";
|
|
2
2
|
|
|
3
|
+
import getBgColor from "./getBgColor";
|
|
4
|
+
|
|
3
5
|
import type { FeatureLike } from "ol/Feature";
|
|
4
6
|
|
|
5
7
|
const borderStyle = new Style({
|
|
@@ -16,11 +18,7 @@ const borderStyle = new Style({
|
|
|
16
18
|
zIndex: 2,
|
|
17
19
|
});
|
|
18
20
|
|
|
19
|
-
const fullTrajectoryStyle = (
|
|
20
|
-
feature: FeatureLike,
|
|
21
|
-
resolution: number,
|
|
22
|
-
options: { getBgColor: (type: string, line: { name: string }) => string },
|
|
23
|
-
): Style[] => {
|
|
21
|
+
const fullTrajectoryStyle = (feature: FeatureLike): Style[] => {
|
|
24
22
|
let lineColor = "#ffffff"; // white
|
|
25
23
|
|
|
26
24
|
const type = feature.get("type");
|
|
@@ -29,8 +27,7 @@ const fullTrajectoryStyle = (
|
|
|
29
27
|
if (stroke && stroke[0] !== "#") {
|
|
30
28
|
stroke = `#${stroke}`;
|
|
31
29
|
}
|
|
32
|
-
lineColor =
|
|
33
|
-
stroke || options?.getBgColor(type, { name: feature.get("line_name") });
|
|
30
|
+
lineColor = stroke || getBgColor(type, { name: feature.get("line_name") });
|
|
34
31
|
|
|
35
32
|
// Don't allow white lines, use red instead.
|
|
36
33
|
lineColor = /#ffffff/i.test(lineColor) ? "#ff0000" : lineColor;
|
package/src/utils/getBgColor.ts
CHANGED
|
@@ -2,7 +2,9 @@ import { realtimeConfig } from "mobility-toolbox-js/ol";
|
|
|
2
2
|
|
|
3
3
|
import { LINE_COLOR_BY_NAME } from "./constants";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import type { RealtimeLine, RealtimeMot } from "mobility-toolbox-js/types";
|
|
6
|
+
|
|
7
|
+
const getBgColor = (type: RealtimeMot, line: Partial<RealtimeLine>) => {
|
|
6
8
|
if (type === "bus") {
|
|
7
9
|
return "#646363";
|
|
8
10
|
}
|
|
@@ -12,7 +14,7 @@ const getBgColor = (type, line) => {
|
|
|
12
14
|
return lineColor;
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
|
-
return realtimeConfig.
|
|
17
|
+
return realtimeConfig.getColorForType(type);
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
export default getBgColor;
|
|
@@ -2,8 +2,8 @@ import getDelayColorForVehicle from "./getDelayColorForVehicle";
|
|
|
2
2
|
|
|
3
3
|
describe("getDelayColorForVehicle", () => {
|
|
4
4
|
it("returns cancelled color", () => {
|
|
5
|
-
expect(getDelayColorForVehicle(0, true, true)).toBe("#dc2626");
|
|
6
|
-
expect(getDelayColorForVehicle(0, true, false)).toBe("#a0a0a0");
|
|
5
|
+
expect(getDelayColorForVehicle(null, null, 0, true, true)).toBe("#dc2626");
|
|
6
|
+
expect(getDelayColorForVehicle(null, null, 0, true, false)).toBe("#a0a0a0");
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
it("returns null delay (no realtime train) color", () => {
|
|
@@ -11,9 +11,13 @@ describe("getDelayColorForVehicle", () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
it("returns transparent", () => {
|
|
14
|
-
expect(getDelayColorForVehicle(0)).toBe("transparent");
|
|
15
|
-
expect(getDelayColorForVehicle(1 * 60 * 1000)).toBe(
|
|
16
|
-
|
|
14
|
+
expect(getDelayColorForVehicle(null, null, 0)).toBe("transparent");
|
|
15
|
+
expect(getDelayColorForVehicle(null, null, 1 * 60 * 1000)).toBe(
|
|
16
|
+
"transparent",
|
|
17
|
+
);
|
|
18
|
+
expect(getDelayColorForVehicle(null, null, 0.4 * 60 * 1000)).toBe(
|
|
19
|
+
"transparent",
|
|
20
|
+
);
|
|
17
21
|
});
|
|
18
22
|
// it("returns green", () => {
|
|
19
23
|
// expect(getDelayColorForVehicle(0)).toBe("#16a34a");
|
|
@@ -24,14 +28,20 @@ describe("getDelayColorForVehicle", () => {
|
|
|
24
28
|
// expect(getDelayColorForVehicle(4.49 * 60 * 1000 - 1)).toBe("#ca8a04");
|
|
25
29
|
// });
|
|
26
30
|
it("returns orange", () => {
|
|
27
|
-
expect(getDelayColorForVehicle(3 * 60 * 1000)).toBe("#ea580c");
|
|
28
|
-
expect(getDelayColorForVehicle(4.49 * 60 * 1000 - 1)).toBe(
|
|
29
|
-
|
|
31
|
+
expect(getDelayColorForVehicle(null, null, 3 * 60 * 1000)).toBe("#ea580c");
|
|
32
|
+
expect(getDelayColorForVehicle(null, null, 4.49 * 60 * 1000 - 1)).toBe(
|
|
33
|
+
"#ea580c",
|
|
34
|
+
);
|
|
35
|
+
expect(getDelayColorForVehicle(null, null, 5 * 60 * 1000)).toBe("#ea580c");
|
|
30
36
|
});
|
|
31
37
|
|
|
32
38
|
it("returns red", () => {
|
|
33
|
-
expect(getDelayColorForVehicle(9.49 * 60 * 1000 - 1)).toBe(
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
expect(getDelayColorForVehicle(null, null, 9.49 * 60 * 1000 - 1)).toBe(
|
|
40
|
+
"#dc2626",
|
|
41
|
+
);
|
|
42
|
+
expect(getDelayColorForVehicle(null, null, 10 * 60 * 1000)).toBe("#dc2626");
|
|
43
|
+
expect(getDelayColorForVehicle(null, null, 180 * 60 * 1000)).toBe(
|
|
44
|
+
"#dc2626",
|
|
45
|
+
);
|
|
36
46
|
});
|
|
37
47
|
});
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import getDelayColor from "./getDelayColor";
|
|
2
2
|
|
|
3
|
+
import type { ViewState } from "mobility-toolbox-js/types";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @param {boolean} cancelled true if the journey is cancelled.
|
|
7
|
-
* @param {boolean} isDelayText true if the color is used for delay text of the symbol.
|
|
6
|
+
* Return the delay color depending on an object representing a vehicle or a line.
|
|
7
|
+
* This function is used to have the same color on the map and on other components.
|
|
8
8
|
*/
|
|
9
9
|
const getDelayColorForVehicle = (
|
|
10
|
-
|
|
10
|
+
object?: unknown,
|
|
11
|
+
viewState?: ViewState,
|
|
12
|
+
delayInMs?: number,
|
|
11
13
|
cancelled?: boolean,
|
|
12
14
|
isDelayText?: boolean,
|
|
13
15
|
): string => {
|
|
@@ -2,31 +2,31 @@ import getDelayTextForVehicle from "./getDelayTextForVehicle";
|
|
|
2
2
|
|
|
3
3
|
describe("getDelayTextForVehicle", () => {
|
|
4
4
|
it("returns cancelled character", () => {
|
|
5
|
-
expect(getDelayTextForVehicle(7200000, true)).toBe(
|
|
5
|
+
expect(getDelayTextForVehicle(null, null, 7200000, true)).toBe(
|
|
6
6
|
String.fromCodePoint(0x00d7),
|
|
7
7
|
);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
it("returns hours (floor)", () => {
|
|
11
|
-
expect(getDelayTextForVehicle(7200000)).toBe("+2h");
|
|
12
|
-
expect(getDelayTextForVehicle(7255555)).toBe("+2h1m");
|
|
11
|
+
expect(getDelayTextForVehicle(null, null, 7200000)).toBe("+2h");
|
|
12
|
+
expect(getDelayTextForVehicle(null, null, 7255555)).toBe("+2h1m");
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
it("returns minutes (round)", () => {
|
|
16
|
-
expect(getDelayTextForVehicle(120000)).toBe("+2m");
|
|
17
|
-
expect(getDelayTextForVehicle(151000)).toBe("+3m");
|
|
16
|
+
expect(getDelayTextForVehicle(null, null, 120000)).toBe("+2m");
|
|
17
|
+
expect(getDelayTextForVehicle(null, null, 151000)).toBe("+3m");
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
it("doesn't display seconds", () => {
|
|
21
|
-
expect(getDelayTextForVehicle(1000)).toBe("");
|
|
22
|
-
expect(getDelayTextForVehicle(30000)).toBe("+1m");
|
|
23
|
-
expect(getDelayTextForVehicle(7255555)).toBe("+2h1m");
|
|
21
|
+
expect(getDelayTextForVehicle(null, null, 1000)).toBe("");
|
|
22
|
+
expect(getDelayTextForVehicle(null, null, 30000)).toBe("+1m");
|
|
23
|
+
expect(getDelayTextForVehicle(null, null, 7255555)).toBe("+2h1m");
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
it("returns empty value", () => {
|
|
27
|
-
expect(getDelayTextForVehicle(1000)).toBe("");
|
|
28
|
-
expect(getDelayTextForVehicle(null)).toBe("");
|
|
29
|
-
expect(getDelayTextForVehicle(undefined)).toBe("");
|
|
30
|
-
expect(getDelayTextForVehicle(0)).toBe("");
|
|
27
|
+
expect(getDelayTextForVehicle(null, null, 1000)).toBe("");
|
|
28
|
+
expect(getDelayTextForVehicle(null, null, null)).toBe("");
|
|
29
|
+
expect(getDelayTextForVehicle(null, null, undefined)).toBe("");
|
|
30
|
+
expect(getDelayTextForVehicle(null, null, 0)).toBe("");
|
|
31
31
|
});
|
|
32
32
|
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import getDelayString from "./getDelayString";
|
|
2
2
|
|
|
3
|
+
import type { RealtimeTrajectory, ViewState } from "mobility-toolbox-js/types";
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* This function returns the text displays near the vehicle.
|
|
5
7
|
* We use getDelayString inside it to make sure that RouteSchedule and
|
|
6
8
|
* the map have the same values.
|
|
7
9
|
*/
|
|
8
10
|
const getDelayTextForVehicle = (
|
|
11
|
+
trajectory: RealtimeTrajectory,
|
|
12
|
+
viewState: ViewState,
|
|
9
13
|
delayInMs: number,
|
|
10
14
|
cancelled = false,
|
|
11
15
|
): string => {
|