forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			116 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
			
		
		
	
	
			116 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
| <script lang="ts">
 | |
|   import { UIEventSource } from "../../Logic/UIEventSource"
 | |
|   import type { Feature } from "geojson"
 | |
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | |
|   import ToSvelte from "../Base/ToSvelte.svelte"
 | |
|   import Svg from "../../Svg.js"
 | |
|   import Translations from "../i18n/Translations"
 | |
|   import Loading from "../Base/Loading.svelte"
 | |
|   import Hotkeys from "../Base/Hotkeys"
 | |
|   import { Geocoding } from "../../Logic/Osm/Geocoding"
 | |
|   import { BBox } from "../../Logic/BBox"
 | |
|   import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore"
 | |
|   import { createEventDispatcher, onDestroy } from "svelte"
 | |
| 
 | |
|   export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
 | |
|   export let bounds: UIEventSource<BBox>
 | |
|   export let selectedElement: UIEventSource<Feature> | undefined = undefined
 | |
|   export let selectedLayer: UIEventSource<LayerConfig> | undefined = undefined
 | |
| 
 | |
|   export let clearAfterView: boolean = true
 | |
| 
 | |
|   let searchContents: string = ""
 | |
|   export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined)
 | |
|   onDestroy(
 | |
|     triggerSearch.addCallback((_) => {
 | |
|       performSearch()
 | |
|     })
 | |
|   )
 | |
| 
 | |
|   let isRunning: boolean = false
 | |
| 
 | |
|   let inputElement: HTMLInputElement
 | |
| 
 | |
|   let feedback: string = undefined
 | |
| 
 | |
|   Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
 | |
|     inputElement?.focus()
 | |
|     inputElement?.select()
 | |
|   })
 | |
| 
 | |
|   const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>()
 | |
|   $: {
 | |
|     if (!searchContents?.trim()) {
 | |
|       dispatch("searchIsValid", false)
 | |
|     } else {
 | |
|       dispatch("searchIsValid", true)
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async function performSearch() {
 | |
|     try {
 | |
|       isRunning = true
 | |
|       searchContents = searchContents?.trim() ?? ""
 | |
| 
 | |
|       if (searchContents === "") {
 | |
|         return
 | |
|       }
 | |
|       const result = await Geocoding.Search(searchContents, bounds.data)
 | |
|       if (result.length == 0) {
 | |
|         feedback = Translations.t.general.search.nothing.txt
 | |
|         return
 | |
|       }
 | |
|       const poi = result[0]
 | |
|       const [lat0, lat1, lon0, lon1] = poi.boundingbox
 | |
|       bounds.set(
 | |
|         new BBox([
 | |
|           [lon0, lat0],
 | |
|           [lon1, lat1],
 | |
|         ]).pad(0.01)
 | |
|       )
 | |
|       if (perLayer !== undefined) {
 | |
|         const id = poi.osm_type + "/" + poi.osm_id
 | |
|         const layers = Array.from(perLayer?.values() ?? [])
 | |
|         for (const layer of layers) {
 | |
|           const found = layer.features.data.find((f) => f.properties.id === id)
 | |
|           selectedElement?.setData(found)
 | |
|           selectedLayer?.setData(layer.layer.layerDef)
 | |
|         }
 | |
|       }
 | |
|       if (clearAfterView) {
 | |
|         searchContents = ""
 | |
|       }
 | |
|       dispatch("searchIsValid", false)
 | |
|       dispatch("searchCompleted")
 | |
|     } catch (e) {
 | |
|       console.error(e)
 | |
|       feedback = Translations.t.general.search.error.txt
 | |
|     } finally {
 | |
|       isRunning = false
 | |
|     }
 | |
|   }
 | |
| </script>
 | |
| 
 | |
| <div class="normal-background flex justify-between rounded-full pl-2">
 | |
|   <form class="w-full">
 | |
|     {#if isRunning}
 | |
|       <Loading>{Translations.t.general.search.searching}</Loading>
 | |
|     {:else if feedback !== undefined}
 | |
|       <div class="alert" on:click={() => (feedback = undefined)}>
 | |
|         {feedback}
 | |
|       </div>
 | |
|     {:else}
 | |
|       <input
 | |
|         type="search"
 | |
|         class="w-full"
 | |
|         bind:this={inputElement}
 | |
|         on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)}
 | |
|         bind:value={searchContents}
 | |
|         placeholder={Translations.t.general.search.search}
 | |
|       />
 | |
|     {/if}
 | |
|   </form>
 | |
|   <div class="h-6 w-6 self-end" on:click={performSearch}>
 | |
|     <ToSvelte construct={Svg.search_svg} />
 | |
|   </div>
 | |
| </div>
 |