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>
 |