|
6 | 6 | type MapCircleStyle, |
7 | 7 | } from '$lib/components/ui/map/MapCircleLayer.svelte'; |
8 | 8 | import { useRoutes, useVehicles } from '$lib/data/app'; |
9 | | - import { type Bus, type Location, type Route, type Stop } from '$lib/data/types'; |
| 9 | + import { isTimepoint, useTimepointsAPI } from '$lib/data/timepoints'; |
| 10 | + import { type Bus, type Direction, type Location, type Route, type Stop } from '$lib/data/types'; |
10 | 11 | import { mapManager } from '$lib/managers/map.manager.svelte'; |
11 | 12 | import { themeManager } from '$lib/managers/theme.manager.svelte'; |
12 | 13 | import { getRouteTint } from '$lib/utils/tints'; |
|
25 | 26 | const STOPS_ID = 'stops'; |
26 | 27 |
|
27 | 28 | const routes = useRoutes(); |
| 29 | + const timepoints = useTimepointsAPI(); |
28 | 30 | const busLocations = $derived(useVehicles(() => ({ route: mapManager.selectedRoute }))); |
29 | 31 |
|
30 | 32 | let userLocation: Location | null = $state(null); |
|
83 | 85 | })), |
84 | 86 | ); |
85 | 87 |
|
| 88 | + function isStopTimepoint(stop: Stop) { |
| 89 | + const routeCode = mapManager.selectedRoute?.routeCode; |
| 90 | + return !!routeCode && isTimepoint(timepoints.data ?? {}, routeCode, stop.id); |
| 91 | + } |
| 92 | +
|
| 93 | + // timepoints render as square DOM markers instead of circles, so they are excluded here |
86 | 94 | const stopCircles = $derived( |
87 | 95 | stopsByDirection.flatMap(({ direction, stops }) => |
88 | | - stops.map( |
89 | | - (stop): MapCircle => ({ |
90 | | - id: `${direction.id}-${stop.id}`, |
91 | | - longitude: stop.location.longitude, |
92 | | - latitude: stop.location.latitude, |
93 | | - }), |
94 | | - ), |
| 96 | + stops |
| 97 | + .filter((stop) => !isStopTimepoint(stop)) |
| 98 | + .map( |
| 99 | + (stop): MapCircle => ({ |
| 100 | + id: `${direction.id}-${stop.id}`, |
| 101 | + longitude: stop.location.longitude, |
| 102 | + latitude: stop.location.latitude, |
| 103 | + }), |
| 104 | + ), |
95 | 105 | ), |
96 | 106 | ); |
97 | 107 |
|
98 | | - const stopCircleStyles = $derived.by(() => { |
| 108 | + const timepointStops = $derived( |
| 109 | + stopsByDirection.flatMap(({ direction, stops }) => |
| 110 | + stops.filter(isStopTimepoint).map((stop) => ({ direction, stop })), |
| 111 | + ), |
| 112 | + ); |
| 113 | +
|
| 114 | + const stopColors = $derived.by(() => { |
99 | 115 | const route = mapManager.selectedRoute; |
100 | | - if (!route) return {}; |
| 116 | + if (!route) return null; |
101 | 117 |
|
102 | 118 | const color = getRouteTint(route, themeManager.theme); |
103 | | - const strokeColor = getLighterColor(color); |
| 119 | + return { color, strokeColor: getLighterColor(color) }; |
| 120 | + }); |
| 121 | +
|
| 122 | + const stopCircleStyles = $derived.by(() => { |
| 123 | + if (!stopColors) return {}; |
| 124 | + const { color, strokeColor } = stopColors; |
104 | 125 |
|
105 | 126 | return Object.fromEntries( |
106 | 127 | stopsByDirection.flatMap(({ direction, stops }) => { |
|
113 | 134 | ); |
114 | 135 | }); |
115 | 136 |
|
| 137 | + function selectStop(stopId: string) { |
| 138 | + markStopTapped(); |
| 139 | + mapManager.selected = stopId === selectedStop?.stop.id ? null : { type: 'stop', id: stopId }; |
| 140 | + } |
| 141 | +
|
116 | 142 | const stopsByCircleId = $derived( |
117 | 143 | new Map<string, Stop>( |
118 | 144 | stopsByDirection.flatMap(({ direction, stops }) => |
|
164 | 190 | {/each} |
165 | 191 | {/snippet} |
166 | 192 |
|
| 193 | +{#snippet timepointMarker(direction: Direction, stop: Stop)} |
| 194 | + <MapMarker |
| 195 | + longitude={stop.location.longitude} |
| 196 | + latitude={stop.location.latitude} |
| 197 | + zIndex={10} |
| 198 | + onclick={() => selectStop(stop.id)} |
| 199 | + > |
| 200 | + <MarkerContent class="flex size-7 items-center justify-center"> |
| 201 | + <div |
| 202 | + class="size-3 rounded-[2px] border-2" |
| 203 | + style="background-color: {stopColors?.color}; border-color: {stopColors?.strokeColor}; opacity: {isDirectionSelected( |
| 204 | + direction.id, |
| 205 | + ) |
| 206 | + ? 1 |
| 207 | + : 0.5}" |
| 208 | + ></div> |
| 209 | + </MarkerContent> |
| 210 | + </MapMarker> |
| 211 | +{/snippet} |
| 212 | + |
167 | 213 | {#snippet busMarker(bus: Bus)} |
168 | 214 | <MapMarker |
169 | 215 | longitude={bus.location.longitude} |
|
186 | 232 | styles={stopCircleStyles} |
187 | 233 | onclick={(id) => { |
188 | 234 | const stopId = (id && stopsByCircleId.get(id)?.id) || null; |
189 | | - if (stopId) markStopTapped(); |
190 | | - mapManager.selected = |
191 | | - !stopId || stopId === selectedStop?.stop.id ? null : { type: 'stop', id: stopId }; |
| 235 | + if (!stopId) { |
| 236 | + mapManager.selected = null; |
| 237 | + return; |
| 238 | + } |
| 239 | + selectStop(stopId); |
192 | 240 | }} |
193 | 241 | /> |
194 | 242 |
|
| 243 | +{#each timepointStops as { direction, stop } (`${direction.id}-${stop.id}`)} |
| 244 | + {@render timepointMarker(direction, stop)} |
| 245 | +{/each} |
| 246 | + |
195 | 247 | {#each busLocations?.data ?? [] as bus (bus.id)} |
196 | 248 | {@render busMarker(bus)} |
197 | 249 | {/each} |
|
0 commit comments