@geops/rvf-mobility-web-component 0.1.32 → 0.1.34

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.
@@ -1,9 +1,35 @@
1
+ import { RealtimeLine } from "mobility-toolbox-js/types";
1
2
  import { Feature } from "ol";
2
3
  import { useEffect, useMemo, useState } from "preact/hooks";
3
4
 
5
+ import ArrowDown from "../../icons/ArrowDown";
6
+ import ArrowUp from "../../icons/ArrowUp";
7
+ import RouteIcon from "../../RouteIcon";
8
+ import RouteStopProgress from "../../RouteStopProgress";
4
9
  import useMapContext from "../../utils/hooks/useMapContext";
10
+ import { RouteStopContext } from "../../utils/hooks/useRouteStop";
5
11
 
6
12
  let cacheLineInfosById = null;
13
+ let cacheStopInfosById = null;
14
+
15
+ interface LineInfo {
16
+ color: string;
17
+ external_id: string;
18
+ id: string;
19
+ long_name: string;
20
+ mot: string;
21
+ operator_name: string;
22
+ short_name: string;
23
+ text_color: string;
24
+ }
25
+
26
+ interface StopInfo {
27
+ external_id: string;
28
+ importance: number;
29
+ long_name: string;
30
+ short_name: string;
31
+ visibility_level: number;
32
+ }
7
33
 
8
34
  function RvfLineNetworkDetails({
9
35
  feature,
@@ -13,7 +39,11 @@ function RvfLineNetworkDetails({
13
39
  features: Feature[];
14
40
  }) {
15
41
  const { apikey, mapsurl } = useMapContext();
16
- const [lineInfos, setLineInfos] = useState(null);
42
+ const [lineInfos, setLineInfos] = useState<LineInfo[]>(null);
43
+ const [stopInfos, setStopInfos] = useState<StopInfo[]>(null);
44
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
+ const [stopInfosOpenId, setStopInfosOpenId] = useState<string>(null);
46
+
17
47
  useEffect(() => {
18
48
  const fetchInfos = async () => {
19
49
  if (!cacheLineInfosById) {
@@ -22,15 +52,16 @@ function RvfLineNetworkDetails({
22
52
  );
23
53
  const data = await response.json();
24
54
  cacheLineInfosById = data["geops.lnp.lines"];
55
+ cacheStopInfosById = data["geops.lnp.stops"];
25
56
  }
26
57
  setLineInfos(cacheLineInfosById);
58
+ setStopInfos(cacheStopInfosById);
27
59
  };
28
60
  fetchInfos();
29
61
  }, [apikey, mapsurl]);
30
62
 
31
- const lineNetworkIdsByOperator = useMemo(() => {
63
+ const lineInfosByOperator: Record<string, LineInfo[]> = useMemo(() => {
32
64
  const byOperators = {};
33
-
34
65
  [
35
66
  ...new Set(
36
67
  features.map((f) => {
@@ -46,51 +77,155 @@ function RvfLineNetworkDetails({
46
77
  if (!byOperators[operatorName]) {
47
78
  byOperators[operatorName] = [];
48
79
  }
80
+ lineInfos[id].id = id;
49
81
  byOperators[operatorName].push(lineInfos[id]);
50
82
  });
51
83
 
52
84
  return byOperators;
53
85
  }, [features, lineInfos]);
54
86
 
87
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
88
+ const stopInfoIdsByLineId: Record<string, string[]> = useMemo(() => {
89
+ const byLineId = {};
90
+ features.forEach((f) => {
91
+ const lineId = f.get("original_line_id");
92
+ if (lineId && !byLineId[lineId]) {
93
+ try {
94
+ byLineId[lineId] = JSON.parse(f.get("stop_ids"));
95
+ } catch (e) {
96
+ console.log(e);
97
+ }
98
+ }
99
+ });
100
+
101
+ return byLineId;
102
+ }, [features]);
103
+
55
104
  if (!feature || !lineInfos) {
56
105
  return null;
57
106
  }
58
107
 
59
108
  return (
60
109
  <div className="flex flex-col gap-4">
61
- {Object.entries(lineNetworkIdsByOperator).map(
62
- ([operatorName, linesInfos]) => {
63
- return (
64
- <div className={"flex flex-col gap-2"} key={operatorName}>
65
- <div>{operatorName}</div>
66
- {linesInfos.map((lineInfo) => {
110
+ {Object.entries(lineInfosByOperator).map(([operatorName, linesInfos]) => {
111
+ return (
112
+ <div className={"flex flex-col gap-2"} key={operatorName}>
113
+ <div>{operatorName}</div>
114
+ {linesInfos
115
+ .sort((a, b) => {
116
+ return a.short_name?.localeCompare(b.short_name);
117
+ })
118
+ .map((lineInfo) => {
67
119
  const {
120
+ color: backgroundColor,
121
+ id,
68
122
  // color,
69
123
  // external_id,
70
- long_name: longName,
124
+ long_name,
125
+ mot,
71
126
  short_name: shortName,
72
- // text_color,
127
+ text_color: textColor,
73
128
  } = lineInfo;
129
+ let longName = long_name;
130
+
131
+ let stops = null;
132
+ //stopInfoIdsByLineId?.[id] || null;
133
+ if (!stops?.length) {
134
+ stops = null;
135
+ }
136
+ console.log("stops", stops, longName);
137
+ if (!longName && stops) {
138
+ const names = stops.map((stopId) => {
139
+ return stopInfos[stopId].short_name;
140
+ });
141
+ console.log("stops", names);
142
+
143
+ longName = [
144
+ ...new Set([names[0], names[names.length - 1]]),
145
+ ].join(" - ");
146
+ }
147
+
148
+ // Build a line object
149
+ const line: { type: string } & RealtimeLine = {
150
+ color: null,
151
+ id: null,
152
+ name: shortName,
153
+ stroke: null,
154
+ text_color: null,
155
+ type: mot,
156
+ };
157
+
158
+ if (textColor) {
159
+ line.text_color = "#" + textColor;
160
+ }
161
+
162
+ if (backgroundColor) {
163
+ line.color = "#" + backgroundColor;
164
+ }
74
165
 
75
166
  return (
76
- <div className={"flex items-center gap-2"} key={shortName}>
77
- <div>
167
+ <div key={shortName}>
168
+ <div
169
+ className={
170
+ "flex w-full items-center justify-between gap-2"
171
+ }
172
+ // onClick={() => {
173
+ // setStopInfosOpenId(stopInfosOpenId === id ? null : id);
174
+ // }}
175
+ >
176
+ <div>
177
+ <RouteIcon line={line}></RouteIcon>
178
+ </div>
179
+ {!!longName && (
180
+ <div className={"flex-1 text-left"}>{longName}</div>
181
+ )}
182
+ {!!stops && (
183
+ <button className={"shrink-0"}>
184
+ {stopInfosOpenId === id ? <ArrowUp /> : <ArrowDown />}
185
+ </button>
186
+ )}
187
+ </div>
188
+ {!!stops && (
78
189
  <div
79
- className={
80
- "rounded-md bg-red px-[12px] py-[9px] font-bold leading-none text-white"
81
- }
190
+ className={`${stopInfosOpenId === id ? "" : "hidden"}`}
82
191
  >
83
- {shortName}
192
+ {stops?.map((stopId, index, arr) => {
193
+ const stop = stopInfos[stopId];
194
+ return (
195
+ <div
196
+ className={"flex items-center gap-2"}
197
+ key={stopId}
198
+ >
199
+ <RouteStopContext.Provider
200
+ value={{
201
+ index,
202
+ status: {
203
+ isFirst: !index,
204
+ isLast: index === arr.length - 1,
205
+ isLeft: false,
206
+ isPassed: false,
207
+ progress: !index ? 50 : 0,
208
+ },
209
+ stop,
210
+ }}
211
+ >
212
+ <RouteStopProgress
213
+ className="relative flex size-8 shrink-0 items-center justify-center"
214
+ lineColor={line.color}
215
+ />
216
+ <div>{stop.short_name}</div>
217
+ </RouteStopContext.Provider>
218
+ </div>
219
+ );
220
+ })}
84
221
  </div>
85
- </div>
86
- <div>{longName}</div>
222
+ )}
87
223
  </div>
88
224
  );
89
225
  })}
90
- </div>
91
- );
92
- },
93
- )}
226
+ </div>
227
+ );
228
+ })}
94
229
  </div>
95
230
  );
96
231
  }
@@ -246,7 +246,7 @@ function RvfMobilityMap({
246
246
 
247
247
  // TODO: this should be removed. The parent application should be responsible to do this
248
248
  // or we should find something that fit more usecases
249
- useUpdatePermalink(map, permalink === "true");
249
+ useUpdatePermalink(map, permalink === "true", eventNodeRef);
250
250
 
251
251
  const mapContextValue = useMemo(() => {
252
252
  return {
Binary file
Binary file
@@ -1,3 +1,11 @@
1
+ import warningbanner from "../icons/warning-banner.png";
2
+ import warning from "../icons/warning.png";
3
+
4
+ const icons = {
5
+ warning: warning,
6
+ warningbanner: warningbanner,
7
+ };
8
+
1
9
  // Add a source and styleLayer using the id in parameter.
2
10
  const addSourceAndLayers = (
3
11
  mapboxLayer,
@@ -20,13 +28,27 @@ const addSourceAndLayers = (
20
28
  }
21
29
  const { mbMap } = mapboxLayer;
22
30
 
31
+ Object.keys(icons).forEach((icon) => {
32
+ if (!mbMap.getImage(icon)) {
33
+ mbMap.loadImage(icons[icon]).then((image) => {
34
+ if (!mbMap.getImage(icon)) {
35
+ mbMap.addImage(icon, image.data);
36
+ }
37
+ });
38
+ }
39
+ });
40
+
23
41
  // Update source
24
42
  if (sourceId && sourceData) {
25
43
  const source = mbMap.getSource(sourceId);
26
44
  if (source) {
27
- source.setData(sourceData.data);
45
+ source.setData(sourceData);
28
46
  } else {
29
- mbMap.addSource(sourceId, sourceData);
47
+ mbMap.addSource(sourceId, {
48
+ data: sourceData,
49
+ generateId: true,
50
+ type: "geojson",
51
+ });
30
52
  }
31
53
  }
32
54
 
@@ -15,9 +15,10 @@ const getFeatureInformationTitle = (feature: Feature) => {
15
15
  category,
16
16
  disruption_type: disruptionType,
17
17
  feed_id: feedId,
18
- long_name: longName,
18
+ line_id: lineId,
19
19
  tickets,
20
20
  } = selectedFeature.getProperties();
21
+ console.log(selectedFeature.getProperties());
21
22
 
22
23
  if (disruptionType) {
23
24
  return "MOCO Meldung";
@@ -29,7 +30,7 @@ const getFeatureInformationTitle = (feature: Feature) => {
29
30
  return TITLE_BY_FEED_ID[feedId] || defaultTitle;
30
31
  }
31
32
 
32
- if (longName) {
33
+ if (lineId) {
33
34
  return RVF_LAYERS_TITLES.liniennetz;
34
35
  }
35
36
  if (tickets) {
@@ -2,17 +2,22 @@ import debounce from "lodash.debounce";
2
2
  import { Map } from "ol";
3
3
  import { EventsKey } from "ol/events";
4
4
  import { unByKey } from "ol/Observable";
5
- import { useEffect } from "preact/hooks";
5
+ import { MutableRef, useEffect } from "preact/hooks";
6
6
 
7
7
  import { LAYER_PROP_IS_EXPORTING } from "../constants";
8
8
  import getLayersAsFlatArray from "../getLayersAsFlatArray";
9
9
  import getPermalinkParameters from "../getPermalinkParameters";
10
+ import MobilityEvent from "../MobilityEvent";
10
11
 
11
12
  /**
12
13
  * This hook only update parameters in the url, it does not apply the url parameters.
13
14
  */
14
15
 
15
- const useUpdatePermalink = (map: Map, permalink: boolean) => {
16
+ const useUpdatePermalink = (
17
+ map: Map,
18
+ permalink: boolean,
19
+ eventNodeRef: MutableRef<HTMLDivElement>,
20
+ ) => {
16
21
  useEffect(() => {
17
22
  let moveEndKey: EventsKey;
18
23
  let loadEndKey: EventsKey;
@@ -23,17 +28,17 @@ const useUpdatePermalink = (map: Map, permalink: boolean) => {
23
28
 
24
29
  // Update x,y,z in URL on moveend
25
30
  moveEndKey = map?.on("moveend", (evt) => {
26
- updatePermalinkDebounced(evt.map);
31
+ updatePermalinkDebounced(evt.map, eventNodeRef);
27
32
  });
28
33
 
29
34
  // Update layers in URL on change:visible event
30
35
  loadEndKey = map.once("loadend", (evt) => {
31
- updatePermalinkDebounced(evt.map);
36
+ updatePermalinkDebounced(evt.map, eventNodeRef);
32
37
  changeVisibleKeys = getLayersAsFlatArray(
33
38
  evt.map.getLayers().getArray(),
34
39
  ).map((layer) => {
35
40
  return layer.on("change:visible", () => {
36
- updatePermalinkDebounced(evt.map);
41
+ updatePermalinkDebounced(evt.map, eventNodeRef);
37
42
  });
38
43
  });
39
44
  });
@@ -44,11 +49,14 @@ const useUpdatePermalink = (map: Map, permalink: boolean) => {
44
49
  unByKey(loadEndKey);
45
50
  unByKey(changeVisibleKeys);
46
51
  };
47
- }, [map, permalink]);
52
+ }, [map, permalink, eventNodeRef]);
48
53
  return null;
49
54
  };
50
55
 
51
- const updatePermalink = (map: Map) => {
56
+ const updatePermalink = (
57
+ map: Map,
58
+ eventNodeRef: MutableRef<HTMLDivElement>,
59
+ ) => {
52
60
  // No update when exporting
53
61
  if (map.get(LAYER_PROP_IS_EXPORTING)) {
54
62
  return;
@@ -57,6 +65,9 @@ const updatePermalink = (map: Map) => {
57
65
  const urlParams = getPermalinkParameters(map, currentUrlParams);
58
66
  urlParams.set("permalink", "true");
59
67
  window.history.replaceState(null, null, `?${urlParams.toString()}`);
68
+ eventNodeRef?.current?.dispatchEvent(
69
+ new MobilityEvent<string>("mwc:permalink", window.location.href),
70
+ );
60
71
  };
61
72
 
62
73
  const updatePermalinkDebounced = debounce(updatePermalink, 1000);
@@ -15,15 +15,27 @@ const useZoom = () => {
15
15
  if (view) {
16
16
  setZoom(view.getZoom());
17
17
  }
18
- const zoomListener = view.on("change:resolution", () => {
18
+ const onZoomChange = (evt) => {
19
19
  clearTimeout(timeout);
20
20
  timeout = setTimeout(() => {
21
- return setZoom(view.getZoom());
21
+ return setZoom(evt.target.getZoom());
22
22
  }, 150);
23
+ };
24
+ let zoomListener = view.on("change:resolution", onZoomChange);
25
+
26
+ const key = map.on("change:view", () => {
27
+ const view = map.getView();
28
+ unByKey(zoomListener);
29
+ if (view) {
30
+ setZoom(view.getZoom());
31
+ }
32
+ zoomListener = view.on("change:resolution", onZoomChange);
23
33
  });
34
+
24
35
  return () => {
25
36
  clearTimeout(timeout);
26
37
  unByKey(zoomListener);
38
+ unByKey(key);
27
39
  };
28
40
  }, [map]);
29
41
  return zoom;