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 |   let height = 0 | ||||||
|   onMount(() => { |   onMount(() => { | ||||||
|     let topbar = document.getElementById("top-bar") |     let topbar = document.getElementById("top-bar") | ||||||
|     height = topbar.clientHeight |     height = topbar?.clientHeight ?? 0 | ||||||
|   }) |   }) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ | ||||||
|   import { MapLibreAdaptor } from "./Map/MapLibreAdaptor" |   import { MapLibreAdaptor } from "./Map/MapLibreAdaptor" | ||||||
|   import { Map as MlMap } from "maplibre-gl" |   import { Map as MlMap } from "maplibre-gl" | ||||||
|   import ShowDataLayer from "./Map/ShowDataLayer" |   import ShowDataLayer from "./Map/ShowDataLayer" | ||||||
| 
 |  | ||||||
|   import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" |   import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||||
|   import type { Feature } from "geojson" |   import type { Feature } from "geojson" | ||||||
|   import Loading from "./Base/Loading.svelte" |   import Loading from "./Base/Loading.svelte" | ||||||
|  | @ -24,9 +23,16 @@ | ||||||
|   import Page from "./Base/Page.svelte" |   import Page from "./Base/Page.svelte" | ||||||
|   import PreviouslySpiedUsers from "./History/PreviouslySpiedUsers.svelte" |   import PreviouslySpiedUsers from "./History/PreviouslySpiedUsers.svelte" | ||||||
|   import { OsmConnection } from "../Logic/Osm/OsmConnection" |   import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||||
|   import MagnifyingGlassCircle from "@babeard/svelte-heroicons/outline/MagnifyingGlassCircle" |  | ||||||
|   import Translations from "./i18n/Translations" |   import Translations from "./i18n/Translations" | ||||||
|   import Tr from "./Base/Tr.svelte" |   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") |   console.log("Loading inspector GUI") | ||||||
|   let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") |   let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") | ||||||
|  | @ -37,16 +43,45 @@ | ||||||
|   let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0")) |   let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0")) | ||||||
|   let loadingData = false |   let loadingData = false | ||||||
|   let selectedElement = new UIEventSource<Feature>(undefined) |   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, { |   let maplibremap: MapLibreAdaptor = new MapLibreAdaptor(map, { | ||||||
|     zoom, |     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) => { |   maplibremap.location.stabilized(500).addCallbackAndRunD((l) => { | ||||||
|     lat.set(l.lat) |     lat.set(l.lat) | ||||||
|     lon.set(l.lon) |     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 allLayers = HistoryUtils.personalTheme.layers | ||||||
|   let layersNoFixme = allLayers.filter((l) => l.id !== "fixme") |   let layersNoFixme = allLayers.filter((l) => l.id !== "fixme") | ||||||
|   let fixme = allLayers.find((l) => l.id === "fixme") |   let fixme = allLayers.find((l) => l.id === "fixme") | ||||||
|  | @ -59,7 +94,7 @@ | ||||||
|       Utils.waitFor(200).then(() => { |       Utils.waitFor(200).then(() => { | ||||||
|         selectedElement.set(f) |         selectedElement.set(f) | ||||||
|       }) |       }) | ||||||
|     }, |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   let osmConnection = new OsmConnection() |   let osmConnection = new OsmConnection() | ||||||
|  | @ -91,7 +126,7 @@ | ||||||
|         inspectedData.push({ |         inspectedData.push({ | ||||||
|           label: undefined, |           label: undefined, | ||||||
|           visitedTime: new Date().toISOString(), |           visitedTime: new Date().toISOString(), | ||||||
|           name: user, |           name: user | ||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|       inspectedContributors.ping() |       inspectedContributors.ping() | ||||||
|  | @ -101,7 +136,7 @@ | ||||||
|     featuresStore.set([]) |     featuresStore.set([]) | ||||||
|     const overpass = new Overpass( |     const overpass = new Overpass( | ||||||
|       undefined, |       undefined, | ||||||
|       user.split(";").map((user) => 'nw(user_touched:"' + user + '");'), |       user.split(";").map((user) => "nw(user_touched:\"" + user + "\");"), | ||||||
|       Constants.defaultOverpassUrls[0] |       Constants.defaultOverpassUrls[0] | ||||||
|     ) |     ) | ||||||
|     if (!maplibremap.bounds.data) { |     if (!maplibremap.bounds.data) { | ||||||
|  | @ -137,7 +172,7 @@ | ||||||
|     <h1 class="m-0 mx-2 flex-shrink-0"> |     <h1 class="m-0 mx-2 flex-shrink-0"> | ||||||
|       <Tr t={t.title} /> |       <Tr t={t.title} /> | ||||||
|     </h1> |     </h1> | ||||||
|     <ValidatedInput type="string" value={username} on:submit={() => load()} /> |     <ValidatedInput placeholder={t.previouslySpied.username} type="string" value={username} on:submit={() => load()} /> | ||||||
|     {#if loadingData} |     {#if loadingData} | ||||||
|       <Loading /> |       <Loading /> | ||||||
|     {:else} |     {:else} | ||||||
|  | @ -207,8 +242,16 @@ | ||||||
|       </Drawer> |       </Drawer> | ||||||
|     {/if} |     {/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} /> |       <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> |     </div> | ||||||
|   {:else if mode === "table"} |   {:else if mode === "table"} | ||||||
|     <div class="m-2 h-full overflow-y-auto"> |     <div class="m-2 h-full overflow-y-auto"> | ||||||
|  |  | ||||||
|  | @ -1,25 +1,33 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" |  | ||||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" |   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" | ||||||
|  |   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||||
|   import { GeoOperations } from "../../Logic/GeoOperations" |   import { GeoOperations } from "../../Logic/GeoOperations" | ||||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|   import { UIEventSource } from "../../Logic/UIEventSource" |   import { UIEventSource } from "../../Logic/UIEventSource" | ||||||
|   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" |   import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" | ||||||
|   import Icon from "../Map/Icon.svelte" |   import Icon from "../Map/Icon.svelte" | ||||||
|   import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte" |   import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte" | ||||||
|   import ArrowUp from "@babeard/svelte-heroicons/mini/ArrowUp" |  | ||||||
|   import DefaultIcon from "../Map/DefaultIcon.svelte" |   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 entry: GeocodeResult | ||||||
|   export let state: WithSearchState |   export let state: { | ||||||
|  |     mapProperties: MapProperties, | ||||||
|  |     theme?: ThemeConfig, | ||||||
|  |     featureProperties?: FeaturePropertiesStore, | ||||||
|  |     searchState: Partial<SearchState> | ||||||
|  |   } | ||||||
| 
 | 
 | ||||||
|   let layer: LayerConfig |   let layer: LayerConfig | ||||||
|   let tags: UIEventSource<Record<string, string>> |   let tags: UIEventSource<Record<string, string>> | ||||||
|   let descriptionTr: TagRenderingConfig = undefined |   let descriptionTr: TagRenderingConfig = undefined | ||||||
|   if (entry.feature?.properties?.id) { |   if (entry.feature?.properties?.id) { | ||||||
|     layer = state.theme.getMatchingLayer(entry.feature.properties) |     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) |     descriptionTr = layer?.tagRenderings?.find((tr) => tr.labels.indexOf("description") >= 0) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,29 +3,34 @@ | ||||||
|    * Shows all the location-results |    * Shows all the location-results | ||||||
|    */ |    */ | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|   import { Store } from "../../Logic/UIEventSource" |  | ||||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" |   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||||
|   import Loading from "../Base/Loading.svelte" |   import Loading from "../Base/Loading.svelte" | ||||||
|   import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte" |   import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte" | ||||||
|   import Tr from "../Base/Tr.svelte" |   import Tr from "../Base/Tr.svelte" | ||||||
|   import DotMenu from "../Base/DotMenu.svelte" |   import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
|   import { CogIcon } from "@rgossiaux/svelte-heroicons/solid" |  | ||||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" |  | ||||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" |   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 searchTerm = state.searchState.searchTerm | ||||||
|   let results = state.searchState.suggestions |   let results = state.searchState.suggestions | ||||||
|   let isSearching = state.searchState.suggestionsSearchRunning |   let isSearching = state.searchState.suggestionsSearchRunning ?? new ImmutableStore(false) | ||||||
|   let recentlySeen: Store<GeocodeResult[]> = state.userRelatedState.recentlyVisitedSearch.value | 
 | ||||||
|   const t = Translations.t.general.search |   const t = Translations.t.general.search | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if $searchTerm.length > 0} | {#if $searchTerm.length > 0} | ||||||
|   <SidebarUnit> |   <SidebarUnit> | ||||||
|     <h3><Tr t={t.locations} /></h3> |     <h3> | ||||||
|  |       <Tr t={t.locations} /> | ||||||
|  |     </h3> | ||||||
| 
 | 
 | ||||||
|     {#if $results?.length > 0} |     {#if $results?.length > 0} | ||||||
|       {#each $results as entry (entry)} |       {#each $results as entry (entry)} | ||||||
|  | @ -41,35 +46,12 @@ | ||||||
|       </div> |       </div> | ||||||
|     {/if} |     {/if} | ||||||
| 
 | 
 | ||||||
|     {#if !$isSearching && $results.length === 0} |     {#if !$isSearching && $results?.length === 0} | ||||||
|       <b class="flex justify-center p-4"> |       <b class="flex justify-center p-4"> | ||||||
|         <Tr t={t.nothingFor.Subs({ term: "<i>" + $searchTerm + "</i>" })} /> |         <Tr t={t.nothingFor.Subs({ term: "<i>" + $searchTerm + "</i>" })} /> | ||||||
|       </b> |       </b> | ||||||
|     {/if} |     {/if} | ||||||
|   </SidebarUnit> |   </SidebarUnit> | ||||||
| {:else if $recentlySeen?.length > 0} | {:else} | ||||||
|   <SidebarUnit> |   <slot name="if-no-results" /> | ||||||
|     <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> |  | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -10,6 +10,12 @@ | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" |   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" | ||||||
|   import { WithSearchState } from "../../Models/ThemeViewState/WithSearchState" |   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 |   export let state: WithSearchState | ||||||
|   let activeFilters: Store<(ActiveFilter & FilterSearchResult)[]> = |   let activeFilters: Store<(ActiveFilter & FilterSearchResult)[]> = | ||||||
|  | @ -26,7 +32,9 @@ | ||||||
|           return r |           return r | ||||||
|         }) |         }) | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|   let searchTerm = state.searchState.searchTerm |   let searchTerm = state.searchState.searchTerm | ||||||
|  |   let recentlySeen: Store<GeocodeResult[]> = state.userRelatedState.recentlyVisitedSearch.value | ||||||
| 
 | 
 | ||||||
|   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview |   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview | ||||||
|   let allowFilters = state.featureSwitches.featureSwitchFilter |   let allowFilters = state.featureSwitches.featureSwitchFilter | ||||||
|  | @ -47,7 +55,38 @@ | ||||||
|   {#if $allowFilters} |   {#if $allowFilters} | ||||||
|     <FilterResults {state} /> |     <FilterResults {state} /> | ||||||
|   {/if} |   {/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} |   {#if $allowOtherThemes} | ||||||
|     <ThemeResults {state} /> |     <ThemeResults {state} /> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue