forked from MapComplete/MapComplete
		
	Feature: improve inspectorGui: add search, placeholder, see #2353
This commit is contained in:
		
							parent
							
								
									0a67668bec
								
							
						
					
					
						commit
						589909ba44
					
				
					 5 changed files with 123 additions and 51 deletions
				
			
		|  | @ -19,7 +19,7 @@ | |||
|   let height = 0 | ||||
|   onMount(() => { | ||||
|     let topbar = document.getElementById("top-bar") | ||||
|     height = topbar.clientHeight | ||||
|     height = topbar?.clientHeight ?? 0 | ||||
|   }) | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,7 +8,6 @@ | |||
|   import { MapLibreAdaptor } from "./Map/MapLibreAdaptor" | ||||
|   import { Map as MlMap } from "maplibre-gl" | ||||
|   import ShowDataLayer from "./Map/ShowDataLayer" | ||||
| 
 | ||||
|   import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
|   import type { Feature } from "geojson" | ||||
|   import Loading from "./Base/Loading.svelte" | ||||
|  | @ -24,9 +23,16 @@ | |||
|   import Page from "./Base/Page.svelte" | ||||
|   import PreviouslySpiedUsers from "./History/PreviouslySpiedUsers.svelte" | ||||
|   import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||
|   import MagnifyingGlassCircle from "@babeard/svelte-heroicons/outline/MagnifyingGlassCircle" | ||||
|   import Translations from "./i18n/Translations" | ||||
|   import Tr from "./Base/Tr.svelte" | ||||
|   import Searchbar from "../UI/Base/Searchbar.svelte" | ||||
|   import CombinedSearcher from "../Logic/Search/CombinedSearcher" | ||||
|   import CoordinateSearch from "../Logic/Search/CoordinateSearch" | ||||
|   import OpenLocationCodeSearch from "../Logic/Search/OpenLocationCodeSearch" | ||||
|   import PhotonSearch from "../Logic/Search/PhotonSearch" | ||||
|   import GeocodeResults from "./Search/GeocodeResults.svelte" | ||||
|   import MagnifyingGlassCircle from "@babeard/svelte-heroicons/mini/MagnifyingGlassCircle" | ||||
|   import type { GeocodeResult } from "../Logic/Search/GeocodingProvider" | ||||
| 
 | ||||
|   console.log("Loading inspector GUI") | ||||
|   let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") | ||||
|  | @ -37,16 +43,45 @@ | |||
|   let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0")) | ||||
|   let loadingData = false | ||||
|   let selectedElement = new UIEventSource<Feature>(undefined) | ||||
|   let searchvalue = new UIEventSource<string>("") | ||||
| 
 | ||||
|   let geocoder = new CombinedSearcher( | ||||
|     new CoordinateSearch(), | ||||
|     new OpenLocationCodeSearch(), | ||||
|     new PhotonSearch(true, 2), | ||||
|     new PhotonSearch() | ||||
|   ) | ||||
|   let showSearchDrawer = new UIEventSource(true) | ||||
|   let searchIsFocussed = new UIEventSource(false) | ||||
|   let searchIsRunning = new UIEventSource(false) | ||||
|   let maplibremap: MapLibreAdaptor = new MapLibreAdaptor(map, { | ||||
|     zoom, | ||||
|     location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data }), | ||||
|     location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data }) | ||||
|   }) | ||||
|   maplibremap.location.stabilized(500).addCallbackAndRunD((l) => { | ||||
|     lat.set(l.lat) | ||||
|     lon.set(l.lon) | ||||
|   }) | ||||
| 
 | ||||
|   let searchSuggestions = searchvalue.bindD(search => { | ||||
|     searchIsRunning.set(true) | ||||
|     try { | ||||
|       return UIEventSource.FromPromise(geocoder.search(search)) | ||||
|     } finally { | ||||
|       searchIsRunning.set(false) | ||||
|     } | ||||
|   }) | ||||
|   let state = { | ||||
|     mapProperties: maplibremap, | ||||
|     searchState: { | ||||
|       searchTerm: searchvalue, | ||||
|       suggestions: searchSuggestions, | ||||
|       suggestionsSearchRunning: searchIsRunning, | ||||
|       showSearchDrawer, | ||||
|       applyGeocodeResult(geocodeResult: GeocodeResult) { | ||||
|         maplibremap.location.set({ lon: geocodeResult.lon, lat: geocodeResult.lat }) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   let allLayers = HistoryUtils.personalTheme.layers | ||||
|   let layersNoFixme = allLayers.filter((l) => l.id !== "fixme") | ||||
|   let fixme = allLayers.find((l) => l.id === "fixme") | ||||
|  | @ -59,7 +94,7 @@ | |||
|       Utils.waitFor(200).then(() => { | ||||
|         selectedElement.set(f) | ||||
|       }) | ||||
|     }, | ||||
|     } | ||||
|   }) | ||||
| 
 | ||||
|   let osmConnection = new OsmConnection() | ||||
|  | @ -91,7 +126,7 @@ | |||
|         inspectedData.push({ | ||||
|           label: undefined, | ||||
|           visitedTime: new Date().toISOString(), | ||||
|           name: user, | ||||
|           name: user | ||||
|         }) | ||||
|       } | ||||
|       inspectedContributors.ping() | ||||
|  | @ -101,7 +136,7 @@ | |||
|     featuresStore.set([]) | ||||
|     const overpass = new Overpass( | ||||
|       undefined, | ||||
|       user.split(";").map((user) => 'nw(user_touched:"' + user + '");'), | ||||
|       user.split(";").map((user) => "nw(user_touched:\"" + user + "\");"), | ||||
|       Constants.defaultOverpassUrls[0] | ||||
|     ) | ||||
|     if (!maplibremap.bounds.data) { | ||||
|  | @ -137,7 +172,7 @@ | |||
|     <h1 class="m-0 mx-2 flex-shrink-0"> | ||||
|       <Tr t={t.title} /> | ||||
|     </h1> | ||||
|     <ValidatedInput type="string" value={username} on:submit={() => load()} /> | ||||
|     <ValidatedInput placeholder={t.previouslySpied.username} type="string" value={username} on:submit={() => load()} /> | ||||
|     {#if loadingData} | ||||
|       <Loading /> | ||||
|     {:else} | ||||
|  | @ -207,8 +242,16 @@ | |||
|       </Drawer> | ||||
|     {/if} | ||||
| 
 | ||||
|     <div class="m-1 flex-grow overflow-hidden rounded-xl"> | ||||
| 
 | ||||
|     <div class="relative m-1 flex-grow overflow-hidden rounded-xl"> | ||||
|       <MaplibreMap {map} mapProperties={maplibremap} autorecovery={true} /> | ||||
|       <div class="absolute right-0 top-0 w-1/4 p-4"> | ||||
|         <Searchbar isFocused={searchIsFocussed} value={searchvalue} | ||||
|                    on:focus={() => state.searchState.showSearchDrawer.set(true)} /> | ||||
|         {#if $searchSuggestions?.length > 0 || $searchIsFocussed} | ||||
|           <GeocodeResults {state} /> | ||||
|         {/if} | ||||
|       </div> | ||||
|     </div> | ||||
|   {:else if mode === "table"} | ||||
|     <div class="m-2 h-full overflow-y-auto"> | ||||
|  |  | |||
|  | @ -1,25 +1,33 @@ | |||
| <script lang="ts"> | ||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { GeoOperations } from "../../Logic/GeoOperations" | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||
|   import Icon from "../Map/Icon.svelte" | ||||
|   import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte" | ||||
|   import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp" | ||||
|   import DefaultIcon from "../Map/DefaultIcon.svelte" | ||||
|   import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState" | ||||
|   import type { MapProperties } from "../../Models/MapProperties" | ||||
|   import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" | ||||
|   import FeaturePropertiesStore from "../../Logic/FeatureSource/Actors/FeaturePropertiesStore" | ||||
|   import SearchState from "../../Logic/State/SearchState" | ||||
|   import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp" | ||||
| 
 | ||||
|   export let entry: GeocodeResult | ||||
|   export let state: WithSearchState | ||||
|   export let state: { | ||||
|     mapProperties: MapProperties, | ||||
|     theme?: ThemeConfig, | ||||
|     featureProperties?: FeaturePropertiesStore, | ||||
|     searchState: Partial<SearchState> | ||||
|   } | ||||
| 
 | ||||
|   let layer: LayerConfig | ||||
|   let tags: UIEventSource<Record<string, string>> | ||||
|   let descriptionTr: TagRenderingConfig = undefined | ||||
|   if (entry.feature?.properties?.id) { | ||||
|     layer = state.theme.getMatchingLayer(entry.feature.properties) | ||||
|     tags = state.featureProperties.getStore(entry.feature.properties.id) | ||||
|     tags = state.featureProperties?.getStore(entry.feature.properties.id) | ||||
|     descriptionTr = layer?.tagRenderings?.find((tr) => tr.labels.indexOf("description") >= 0) | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,29 +3,34 @@ | |||
|    * Shows all the location-results | ||||
|    */ | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import DotMenu from "../Base/DotMenu.svelte" | ||||
|   import { CogIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState" | ||||
|   import type { MapProperties } from "../../Models/MapProperties" | ||||
| 
 | ||||
|   export let state: WithSearchState | ||||
|   export let state: { | ||||
|     searchState: { | ||||
|       searchTerm: UIEventSource<string> | ||||
|       suggestions: Store<GeocodeResult[]> | ||||
|       suggestionsSearchRunning: Store<boolean> | ||||
|     }, mapProperties: MapProperties | ||||
|   } | ||||
| 
 | ||||
|   let searchTerm = state.searchState.searchTerm | ||||
|   let results = state.searchState.suggestions | ||||
|   let isSearching = state.searchState.suggestionsSearchRunning | ||||
|   let recentlySeen: Store<GeocodeResult[]> = state.userRelatedState.recentlyVisitedSearch.value | ||||
|   let isSearching = state.searchState.suggestionsSearchRunning ?? new ImmutableStore(false) | ||||
| 
 | ||||
|   const t = Translations.t.general.search | ||||
| </script> | ||||
| 
 | ||||
| {#if $searchTerm.length > 0} | ||||
|   <SidebarUnit> | ||||
|     <h3><Tr t={t.locations} /></h3> | ||||
|     <h3> | ||||
|       <Tr t={t.locations} /> | ||||
|     </h3> | ||||
| 
 | ||||
|     {#if $results?.length > 0} | ||||
|       {#each $results as entry (entry)} | ||||
|  | @ -41,35 +46,12 @@ | |||
|       </div> | ||||
|     {/if} | ||||
| 
 | ||||
|     {#if !$isSearching && $results.length === 0} | ||||
|     {#if !$isSearching && $results?.length === 0} | ||||
|       <b class="flex justify-center p-4"> | ||||
|         <Tr t={t.nothingFor.Subs({ term: "<i>" + $searchTerm + "</i>" })} /> | ||||
|       </b> | ||||
|     {/if} | ||||
|   </SidebarUnit> | ||||
| {:else if $recentlySeen?.length > 0} | ||||
|   <SidebarUnit> | ||||
|     <div class="flex justify-between"> | ||||
|       <h3 class="m-2"> | ||||
|         <Tr t={t.recents} /> | ||||
|       </h3> | ||||
|       <DotMenu> | ||||
|         <button | ||||
|           on:click={() => { | ||||
|             state.userRelatedState.recentlyVisitedSearch.clear() | ||||
|           }} | ||||
|         > | ||||
|           <TrashIcon /> | ||||
|           <Tr t={t.deleteSearchHistory} /> | ||||
|         </button> | ||||
|         <button on:click={() => state.guistate.openUsersettings("sync-visited-locations")}> | ||||
|           <CogIcon /> | ||||
|           <Tr t={t.editSearchSyncSettings} /> | ||||
|         </button> | ||||
|       </DotMenu> | ||||
|     </div> | ||||
|     {#each $recentlySeen as entry (entry)} | ||||
|       <GeocodeResultSvelte {entry} {state} on:select /> | ||||
|     {/each} | ||||
|   </SidebarUnit> | ||||
| {:else} | ||||
|   <slot name="if-no-results" /> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -10,6 +10,12 @@ | |||
|   import Translations from "../i18n/Translations" | ||||
|   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" | ||||
|   import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState" | ||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import { CogIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import DotMenu from "../Base/DotMenu.svelte" | ||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte" | ||||
| 
 | ||||
|   export let state: WithSearchState | ||||
|   let activeFilters: Store<(ActiveFilter & FilterSearchResult)[]> = | ||||
|  | @ -26,7 +32,9 @@ | |||
|           return r | ||||
|         }) | ||||
|     ) | ||||
| 
 | ||||
|   let searchTerm = state.searchState.searchTerm | ||||
|   let recentlySeen: Store<GeocodeResult[]> = state.userRelatedState.recentlyVisitedSearch.value | ||||
| 
 | ||||
|   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview | ||||
|   let allowFilters = state.featureSwitches.featureSwitchFilter | ||||
|  | @ -47,7 +55,38 @@ | |||
|   {#if $allowFilters} | ||||
|     <FilterResults {state} /> | ||||
|   {/if} | ||||
|   <GeocodeResults {state} /> | ||||
|   <GeocodeResults {state}> | ||||
|     <svelte:fragment slot="if-no-results"> | ||||
| 
 | ||||
|       {#if $recentlySeen?.length > 0} | ||||
|         <SidebarUnit> | ||||
|           <div class="flex justify-between"> | ||||
|             <h3 class="m-2"> | ||||
|               <Tr t={Translations.t.general.search.recents} /> | ||||
|             </h3> | ||||
|             <DotMenu> | ||||
|               <button | ||||
|                 on:click={() => { | ||||
|             state.userRelatedState.recentlyVisitedSearch.clear() | ||||
|           }} | ||||
|               > | ||||
|                 <TrashIcon /> | ||||
|                 <Tr t={Translations.t.general.search.deleteSearchHistory} /> | ||||
|               </button> | ||||
|               <button on:click={() => state.guistate.openUsersettings("sync-visited-locations")}> | ||||
|                 <CogIcon /> | ||||
|                 <Tr t={Translations.t.general.search.editSearchSyncSettings} /> | ||||
|               </button> | ||||
|             </DotMenu> | ||||
|           </div> | ||||
|           {#each $recentlySeen as entry (entry)} | ||||
|             <GeocodeResultSvelte {entry} {state} on:select /> | ||||
|           {/each} | ||||
|         </SidebarUnit> | ||||
|       {/if} | ||||
|     </svelte:fragment> | ||||
| 
 | ||||
|   </GeocodeResults> | ||||
| 
 | ||||
|   {#if $allowOtherThemes} | ||||
|     <ThemeResults {state} /> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue