|
15 | 15 | GeocoderBounds, |
16 | 16 | MapKeyboardCommand, |
17 | 17 | MapLocation, |
| 18 | + MapLocationSyncCommand, |
18 | 19 | MapToolbarCommand |
19 | 20 | } from '$lib/types'; |
20 | 21 |
|
|
69 | 70 | opacity = $bindable(viewState.opacity), |
70 | 71 | rotateToMapOrientation = $bindable(false), |
71 | 72 | focusActiveMap = $bindable(false), |
| 73 | + viewsLinked = $bindable(false), |
| 74 | + locationSyncCommand, |
72 | 75 | inViewOnly = $bindable(false), |
73 | 76 | currentLocation = $bindable({ |
74 | 77 | center: [...config.map.initialView.center] as [number, number], |
|
88 | 91 | enableLocationMarker = false, |
89 | 92 | navPosition = 'left', |
90 | 93 | controlsPosition = 'top-right', |
| 94 | + showZoomControls = true, |
| 95 | + showLinkControl = false, |
91 | 96 | showInViewControl = false, |
| 97 | + onLocationChange, |
92 | 98 | autoplayActive = false, |
93 | 99 | autoplayNextAnnotation, |
94 | 100 | loaded = $bindable(false) |
|
98 | 104 | opacity?: number; |
99 | 105 | rotateToMapOrientation?: boolean; |
100 | 106 | focusActiveMap?: boolean; |
| 107 | + viewsLinked?: boolean; |
| 108 | + locationSyncCommand?: MapLocationSyncCommand; |
101 | 109 | inViewOnly?: boolean; |
102 | 110 | currentLocation?: MapLocation; |
103 | 111 | annotationsInView?: string[]; |
|
110 | 118 | enableLocationMarker?: boolean; |
111 | 119 | navPosition?: 'left' | 'right'; |
112 | 120 | controlsPosition?: 'top-left' | 'top-right'; |
| 121 | + showZoomControls?: boolean; |
| 122 | + showLinkControl?: boolean; |
113 | 123 | showInViewControl?: boolean; |
| 124 | + onLocationChange?: (location: MapLocation) => void; |
114 | 125 | autoplayActive?: boolean; |
115 | 126 | autoplayNextAnnotation?: string; |
116 | 127 | loaded?: boolean; |
|
145 | 156 | let zoomLimitRetryTimer: ReturnType<typeof setTimeout> | undefined; |
146 | 157 | let visibilityCheckFrame: number | undefined; |
147 | 158 | let isSyncing = false; |
| 159 | + let previousLocationSyncCommandId = 0; |
| 160 | + let activeLocationSyncCommandId = 0; |
148 | 161 | let warpedMapList = getWarpedMapList(); |
149 | 162 | let warpedMapLayer = new WarpedMapLayer({ |
150 | 163 | visible: false, |
|
217 | 230 |
|
218 | 231 | // Synchronize the map position with the bound location. |
219 | 232 | $effect(() => { |
220 | | - if (mapReady && map && currentLocation) { |
221 | | - const center = currentLocation.center; |
222 | | - const zoom = currentLocation.zoom; |
223 | | - const bearing = currentLocation.bearing ?? 0; |
224 | | - if (!mapMatchesLocation(center, zoom, bearing)) { |
225 | | - clearPreferredSelectionZoom(); |
| 233 | + if (!mapReady || !map || !currentLocation) return; |
| 234 | +
|
| 235 | + const pendingAnimatedSync = |
| 236 | + locationSyncCommand && locationSyncCommand.id !== previousLocationSyncCommandId |
| 237 | + ? locationSyncCommand |
| 238 | + : undefined; |
| 239 | +
|
| 240 | + if ( |
| 241 | + !pendingAnimatedSync && |
| 242 | + activeLocationSyncCommandId && |
| 243 | + locationSyncCommand?.id === activeLocationSyncCommandId |
| 244 | + ) { |
| 245 | + return; |
| 246 | + } |
| 247 | +
|
| 248 | + const targetLocation = pendingAnimatedSync?.location ?? currentLocation; |
| 249 | + const center = targetLocation.center; |
| 250 | + const zoom = targetLocation.zoom; |
| 251 | + const bearing = targetLocation.bearing ?? 0; |
| 252 | + if (!mapMatchesLocation(center, zoom, bearing)) { |
| 253 | + clearPreferredSelectionZoom(); |
| 254 | +
|
| 255 | + if (pendingAnimatedSync) { |
| 256 | + previousLocationSyncCommandId = pendingAnimatedSync.id; |
| 257 | + activeLocationSyncCommandId = pendingAnimatedSync.id; |
| 258 | + isSyncing = true; |
| 259 | + map.stop(); |
| 260 | + map.once('moveend', () => { |
| 261 | + if (activeLocationSyncCommandId === pendingAnimatedSync.id) { |
| 262 | + activeLocationSyncCommandId = 0; |
| 263 | + } |
| 264 | + isSyncing = false; |
| 265 | + }); |
| 266 | + map.flyTo({ center, zoom, bearing, pitch: 0, essential: true }); |
| 267 | + } else { |
226 | 268 | isSyncing = true; |
227 | 269 | map.jumpTo({ center, zoom, bearing }); |
228 | 270 | isSyncing = false; |
229 | 271 | } |
| 272 | + } else if (pendingAnimatedSync) { |
| 273 | + previousLocationSyncCommandId = pendingAnimatedSync.id; |
| 274 | + activeLocationSyncCommandId = 0; |
230 | 275 | } |
231 | 276 | }); |
232 | 277 |
|
|
335 | 380 | if (!mapReady || !map || !command || command.id === previousKeyboardCommandId) return; |
336 | 381 |
|
337 | 382 | previousKeyboardCommandId = command.id; |
| 383 | +
|
338 | 384 | let zoom = command.zoomDelta === undefined ? map.getZoom() : map.getZoom() + command.zoomDelta; |
339 | 385 |
|
340 | 386 | clearPreferredSelectionZoom(); |
|
1072 | 1118 |
|
1073 | 1119 | mapInstance.on('move', () => { |
1074 | 1120 | if (!isSyncing) { |
1075 | | - currentLocation = { |
| 1121 | + const nextLocation = { |
1076 | 1122 | center: mapInstance.getCenter().toArray() as [number, number], |
1077 | 1123 | zoom: mapInstance.getZoom(), |
1078 | 1124 | bearing: mapInstance.getBearing() |
1079 | 1125 | }; |
| 1126 | + currentLocation = nextLocation; |
| 1127 | + onLocationChange?.(nextLocation); |
1080 | 1128 | } |
1081 | 1129 |
|
1082 | 1130 | if (visibilityWarningOpen) { |
|
1160 | 1208 | bind:opacity |
1161 | 1209 | bind:rotateToMapOrientation |
1162 | 1210 | bind:focusActiveMap |
| 1211 | + bind:viewsLinked |
1163 | 1212 | bind:inViewOnly |
1164 | 1213 | position={controlsPosition} |
1165 | 1214 | canZoomToMap={canZoomToActiveMap} |
1166 | 1215 | canFilterInView={annotationsInView.length > 0} |
| 1216 | + {showZoomControls} |
| 1217 | + {showLinkControl} |
1167 | 1218 | {showInViewControl} |
1168 | 1219 | onUserCameraAction={clearPreferredSelectionZoom} |
1169 | 1220 | /> |
|
0 commit comments