|
2 | 2 | import Icon from "../ui/icon.vue"; |
3 | 3 | import { find, getCurrentLanguage, getElevationForPoint, isSearchId, parseUrlQuery, loadDirectUrlQuery, type AnalyzedChangeset } from "facilmap-utils"; |
4 | 4 | import { useToasts } from "../ui/toasts/toasts.vue"; |
5 | | - import type { FindOnMapResult, SearchResult } from "facilmap-types"; |
| 5 | + import type { Bbox, FindOnMapResult, SearchResult } from "facilmap-types"; |
6 | 6 | import SearchResults from "../search-results/search-results.vue"; |
7 | | - import { flyTo, getZoomDestinationForChangeset, getZoomDestinationForMapResult, getZoomDestinationForResults, getZoomDestinationForSearchResult, normalizeZoomDestination, openSpecialQuery } from "../../utils/zoom"; |
| 7 | + import { flyTo, getZoomDestinationForBbox, getZoomDestinationForMapResult, getZoomDestinationForResults, getZoomDestinationForSearchResult, normalizeZoomDestination, openSpecialQuery, type ZoomDestination } from "../../utils/zoom"; |
8 | 8 | import { Util } from "leaflet"; |
9 | 9 | import { isMapResult } from "../../utils/search"; |
10 | 10 | import storage from "../../utils/storage"; |
|
43 | 43 | const mapResults = ref<FindOnMapResult[]>(); |
44 | 44 | const fileResult = ref<FileResultObject>(); |
45 | 45 | const changesetResult = ref<AnalyzedChangeset>(); |
| 46 | + const changesetZoomDestination = ref<ZoomDestination>(); |
46 | 47 |
|
47 | 48 | const zoomDestination = computed(() => { |
48 | | - if (changesetResult.value) { |
49 | | - return getZoomDestinationForChangeset(changesetResult.value); |
| 49 | + if (changesetZoomDestination.value) { |
| 50 | + return changesetZoomDestination.value; |
50 | 51 | } else { |
51 | 52 | return getZoomDestinationForResults([ |
52 | 53 | ...(searchResults.value || []), |
|
83 | 84 | void search(storage.autoZoom, storage.zoomToAll); |
84 | 85 | } |
85 | 86 |
|
86 | | - async function search(zoom: boolean, zoomToAll?: boolean, smooth = true): Promise<void> { |
87 | | - if (searchString.value != loadedSearchString.value) { |
88 | | - reset(); |
89 | | -
|
90 | | - if(searchString.value.trim() != "") { |
91 | | - try { |
92 | | - if (await openSpecialQuery(searchString.value, context, zoom)) { |
93 | | - searchString.value = ""; |
94 | | - return; |
95 | | - } |
96 | | -
|
97 | | - const query = searchString.value; |
98 | | - loadingSearchString.value = searchString.value; |
99 | | - loadingSearchAbort = new AbortController(); |
100 | | - const signal = loadingSearchAbort.signal; |
101 | | -
|
102 | | - const onProgress = throttle((p) => { |
103 | | - if (!signal.aborted) { |
104 | | - loadingSearchProgress.value = p * 100; |
| 87 | + function search(zoom: boolean, zoomToAll?: boolean, smooth = true): { zoomed: Promise<void>; loaded: Promise<void> } { |
| 88 | + let onZoomed: () => void; |
| 89 | + return { |
| 90 | + zoomed: new Promise((resolve) => { |
| 91 | + onZoomed = resolve; |
| 92 | + }), |
| 93 | + loaded: (async () => { |
| 94 | + if (searchString.value != loadedSearchString.value) { |
| 95 | + reset(); |
| 96 | +
|
| 97 | + if(searchString.value.trim() != "") { |
| 98 | + try { |
| 99 | + if (await openSpecialQuery(searchString.value, context, zoom)) { |
| 100 | + searchString.value = ""; |
| 101 | + return; |
| 102 | + } |
| 103 | +
|
| 104 | + const query = searchString.value; |
| 105 | + loadingSearchString.value = searchString.value; |
| 106 | + loadingSearchAbort = new AbortController(); |
| 107 | + const signal = loadingSearchAbort.signal; |
| 108 | +
|
| 109 | + const onProgress = throttle((p) => { |
| 110 | + if (!signal.aborted) { |
| 111 | + loadingSearchProgress.value = p * 100; |
| 112 | + } |
| 113 | + }, 200); |
| 114 | + const loadedUrl = await mapContext.value.runOperation(async () => await loadDirectUrlQuery(query, { |
| 115 | + signal: loadingSearchAbort!.signal, |
| 116 | + onProgress, |
| 117 | + onBbox: (bbox: Bbox) => { |
| 118 | + if (!signal.aborted) { |
| 119 | + changesetZoomDestination.value = getZoomDestinationForBbox(bbox); |
| 120 | + if (zoom) { |
| 121 | + zoomToAllResults(smooth); |
| 122 | + } |
| 123 | + } |
| 124 | + onZoomed(); |
| 125 | + } |
| 126 | + })); |
| 127 | + onProgress.flush(); |
| 128 | + const url = parseUrlQuery(query); |
| 129 | +
|
| 130 | + const [newSearchResults, newMapResults] = await Promise.all([ |
| 131 | + loadedUrl ? loadedUrl : |
| 132 | + url ? client.value.find({ query, loadUrls: true }) : ( |
| 133 | + mapContext.value.runOperation(async () => await find(query, { |
| 134 | + lang: isLanguageExplicit() ? getCurrentLanguage() : undefined |
| 135 | + })) |
| 136 | + ), |
| 137 | + client.value.mapData ? client.value.findOnMap({ query }) : undefined |
| 138 | + ]); |
| 139 | +
|
| 140 | + if (signal.aborted) |
| 141 | + return; |
| 142 | +
|
| 143 | + loadingSearchString.value = undefined; |
| 144 | + loadingSearchProgress.value = undefined; |
| 145 | + loadingSearchAbort = undefined; |
| 146 | + loadedSearchString.value = query; |
| 147 | +
|
| 148 | + if(isSearchId(query) && Array.isArray(newSearchResults) && newSearchResults.length > 0 && newSearchResults[0].display_name) { |
| 149 | + searchString.value = newSearchResults[0].display_name; |
| 150 | + loadedSearchString.value = query; |
| 151 | + } |
| 152 | +
|
| 153 | + if (typeof newSearchResults == "string") { |
| 154 | + const parsed = await mapContext.value.runOperation(async () => await parseFiles([ new TextEncoder().encode(newSearchResults) ])); |
| 155 | + if (signal.aborted) |
| 156 | + return; // Another search has been started in the meantime |
| 157 | + fileResult.value = parsed; |
| 158 | + mapContext.value.components.searchResultsLayer.setResults(fileResult.value.features); |
| 159 | + } else if ("changeset" in newSearchResults) { |
| 160 | + changesetResult.value = newSearchResults; |
| 161 | + mapContext.value.components.changesetLayer.setChangeset(newSearchResults); |
| 162 | + } else { |
| 163 | + const reactiveResults = reactive(newSearchResults); |
| 164 | + searchResults.value = reactiveResults; |
| 165 | + mapContext.value.components.searchResultsLayer.setResults(newSearchResults); |
| 166 | + mapResults.value = newMapResults ?? undefined; |
| 167 | +
|
| 168 | + const points = newSearchResults.filter((res) => (res.lon && res.lat)); |
| 169 | + if(points.length > 0) { |
| 170 | + (async () => { |
| 171 | + const elevations = await Promise.all(points.map(async (point) => { |
| 172 | + return await getElevationForPoint({ lat: Number(point.lat), lon: Number(point.lon) }); |
| 173 | + })); |
| 174 | + elevations.forEach((elevation, i) => { |
| 175 | + reactiveResults[i].elevation = elevation; |
| 176 | + }); |
| 177 | + })().catch((err) => { |
| 178 | + console.warn("Error fetching search result elevations", err); |
| 179 | + }); |
| 180 | + } |
| 181 | + } |
| 182 | + } catch(err: any) { |
| 183 | + if (err.name !== "AbortError") { |
| 184 | + toasts.showErrorToast(`fm${context.id}-search-form-error`, () => i18n.t("search-form.search-error"), err); |
| 185 | + } |
| 186 | + return; |
105 | 187 | } |
106 | | - }, 200); |
107 | | - const loadedUrl = await mapContext.value.runOperation(async () => await loadDirectUrlQuery(query, { |
108 | | - signal: loadingSearchAbort!.signal, |
109 | | - onProgress |
110 | | - })); |
111 | | - onProgress.flush(); |
112 | | - const url = parseUrlQuery(query); |
113 | | -
|
114 | | - const [newSearchResults, newMapResults] = await Promise.all([ |
115 | | - loadedUrl ? loadedUrl : |
116 | | - url ? client.value.find({ query, loadUrls: true }) : ( |
117 | | - mapContext.value.runOperation(async () => await find(query, { |
118 | | - lang: isLanguageExplicit() ? getCurrentLanguage() : undefined |
119 | | - })) |
120 | | - ), |
121 | | - client.value.mapData ? client.value.findOnMap({ query }) : undefined |
122 | | - ]); |
123 | | -
|
124 | | - if (signal.aborted) |
125 | | - return; |
126 | | -
|
127 | | - loadingSearchString.value = undefined; |
128 | | - loadingSearchProgress.value = undefined; |
129 | | - loadingSearchAbort = undefined; |
130 | | - loadedSearchString.value = query; |
131 | | -
|
132 | | - if(isSearchId(query) && Array.isArray(newSearchResults) && newSearchResults.length > 0 && newSearchResults[0].display_name) { |
133 | | - searchString.value = newSearchResults[0].display_name; |
134 | | - loadedSearchString.value = query; |
135 | 188 | } |
136 | | -
|
137 | | - if (typeof newSearchResults == "string") { |
138 | | - const parsed = await mapContext.value.runOperation(async () => await parseFiles([ new TextEncoder().encode(newSearchResults) ])); |
139 | | - if (signal.aborted) |
140 | | - return; // Another search has been started in the meantime |
141 | | - fileResult.value = parsed; |
142 | | - mapContext.value.components.searchResultsLayer.setResults(fileResult.value.features); |
143 | | - } else if ("changeset" in newSearchResults) { |
144 | | - changesetResult.value = newSearchResults; |
145 | | - mapContext.value.components.changesetLayer.setChangeset(newSearchResults); |
146 | | - } else { |
147 | | - const reactiveResults = reactive(newSearchResults); |
148 | | - searchResults.value = reactiveResults; |
149 | | - mapContext.value.components.searchResultsLayer.setResults(newSearchResults); |
150 | | - mapResults.value = newMapResults ?? undefined; |
151 | | -
|
152 | | - const points = newSearchResults.filter((res) => (res.lon && res.lat)); |
153 | | - if(points.length > 0) { |
154 | | - (async () => { |
155 | | - const elevations = await Promise.all(points.map(async (point) => { |
156 | | - return await getElevationForPoint({ lat: Number(point.lat), lon: Number(point.lon) }); |
157 | | - })); |
158 | | - elevations.forEach((elevation, i) => { |
159 | | - reactiveResults[i].elevation = elevation; |
160 | | - }); |
161 | | - })().catch((err) => { |
162 | | - console.warn("Error fetching search result elevations", err); |
163 | | - }); |
164 | | - } |
165 | | - } |
166 | | - } catch(err: any) { |
167 | | - if (err.name !== "AbortError") { |
168 | | - toasts.showErrorToast(`fm${context.id}-search-form-error`, () => i18n.t("search-form.search-error"), err); |
169 | | - } |
170 | | - return; |
171 | 189 | } |
172 | | - } |
173 | | - } |
174 | 190 |
|
175 | | - if (zoomToAll || (zoomToAll == null && (searchResults.value?.length ?? 0) + (mapResults.value?.length ?? 0) > 1)) { |
176 | | - if (zoom) |
177 | | - zoomToAllResults(smooth); |
178 | | - } else if (mapResults.value && mapResults.value.length > 0 && (mapResults.value[0].similarity == 1 || (!searchResults.value || searchResults.value.length == 0))) { |
179 | | - mapContext.value.components.selectionHandler.setSelectedItems([{ type: mapResults.value[0].kind, id: mapResults.value[0].id }]) |
180 | | - if (zoom) |
181 | | - zoomToResult(mapResults.value[0], smooth); |
182 | | - } else if (searchResults.value && searchResults.value.length > 0) { |
183 | | - mapContext.value.components.selectionHandler.setSelectedItems([{ type: "searchResult", result: searchResults.value[0], layerId }]); |
184 | | - if (zoom) |
185 | | - zoomToResult(searchResults.value[0], smooth); |
186 | | - } else if (fileResult.value || changesetResult.value) { |
187 | | - if (zoom) |
188 | | - zoomToAllResults(smooth); |
189 | | - } |
| 191 | + if (zoomToAll || (zoomToAll == null && (searchResults.value?.length ?? 0) + (mapResults.value?.length ?? 0) > 1)) { |
| 192 | + if (zoom) |
| 193 | + zoomToAllResults(smooth); |
| 194 | + } else if (mapResults.value && mapResults.value.length > 0 && (mapResults.value[0].similarity == 1 || (!searchResults.value || searchResults.value.length == 0))) { |
| 195 | + mapContext.value.components.selectionHandler.setSelectedItems([{ type: mapResults.value[0].kind, id: mapResults.value[0].id }]) |
| 196 | + if (zoom) |
| 197 | + zoomToResult(mapResults.value[0], smooth); |
| 198 | + } else if (searchResults.value && searchResults.value.length > 0) { |
| 199 | + mapContext.value.components.selectionHandler.setSelectedItems([{ type: "searchResult", result: searchResults.value[0], layerId }]); |
| 200 | + if (zoom) |
| 201 | + zoomToResult(searchResults.value[0], smooth); |
| 202 | + } else if (fileResult.value) { |
| 203 | + if (zoom) |
| 204 | + zoomToAllResults(smooth); |
| 205 | + } |
| 206 | + // For changesets, we already zoomed above in onBbox() |
| 207 | + })() |
| 208 | + }; |
190 | 209 | } |
191 | 210 |
|
192 | 211 | function reset(): void { |
|
202 | 221 | mapResults.value = undefined; |
203 | 222 | fileResult.value = undefined; |
204 | 223 | changesetResult.value = undefined; |
| 224 | + changesetZoomDestination.value = undefined; |
205 | 225 | mapContext.value.components.searchResultsLayer.setResults([]); |
206 | 226 | mapContext.value.components.changesetLayer.setChangeset(undefined); |
207 | 227 | }; |
|
0 commit comments