forked from MapComplete/MapComplete
		
	Add layers to search menu
This commit is contained in:
		
							parent
							
								
									e6dab1a83f
								
							
						
					
					
						commit
						c591770eab
					
				
					 33 changed files with 332 additions and 195 deletions
				
			
		|  | @ -8,7 +8,7 @@ | |||
|   import Loading from "./Loading.svelte" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { GeocodingUtils } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
| 
 | ||||
|   export let state: SpecialVisualizationState | ||||
|  |  | |||
|  | @ -66,7 +66,7 @@ export default class MoreScreen { | |||
|             return Infinity | ||||
|         } | ||||
|         language ??= Locale.language.data | ||||
|         const queryParts = query.split(" ").map(q => Utils.simplifyStringForSearch(q)) | ||||
|         const queryParts = query.trim().split(" ").map(q => Utils.simplifyStringForSearch(q)) | ||||
|         let terms: string[] | ||||
|         if (Array.isArray(keywords)) { | ||||
|             terms = keywords | ||||
|  | @ -90,9 +90,12 @@ export default class MoreScreen { | |||
|         return distanceSummed | ||||
|     } | ||||
| 
 | ||||
|     public static scoreLayers(query: string): Record<string, number> { | ||||
|     public static scoreLayers(query: string, layerWhitelist?: Set<string>): Record<string, number> { | ||||
|         const result: Record<string, number> = {} | ||||
|         for (const id in this.officialThemes.layers) { | ||||
|             if(layerWhitelist !== undefined && !layerWhitelist.has(id)){ | ||||
|                 continue | ||||
|             } | ||||
|             const keywords = this.officialThemes.layers[id] | ||||
|             const distance = this.scoreKeywords(query, keywords) | ||||
|             result[id] = distance | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
|    * Shows the current address when shaken | ||||
|    **/ | ||||
|   import Motion from "../../Sensors/Motion" | ||||
|   import { NominatimGeocoding } from "../../Logic/Geocoding/NominatimGeocoding" | ||||
|   import { NominatimGeocoding } from "../../Logic/Search/NominatimGeocoding" | ||||
|   import Hotkeys from "../Base/Hotkeys" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import Locale from "../i18n/Locale" | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| <script lang="ts"> | ||||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import FilterOption from "./FilterOption.svelte" | ||||
|   import { XMarkIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import FilterToggle from "./FilterToggle.svelte" | ||||
| 
 | ||||
| 
 | ||||
|   export let activeFilter: ActiveFilter | ||||
|  | @ -21,10 +21,7 @@ | |||
| {#if loading} | ||||
|   <Loading /> | ||||
| {:else } | ||||
|   <div class="badge button-unstyled w-fit"> | ||||
|   <FilterToggle  on:click={() => clear()}> | ||||
|     <FilterOption option={$option} /> | ||||
|     <button on:click={() => clear()}> | ||||
|       <XMarkIcon class="w-5 h-5 pl-1" color="gray" /> | ||||
|     </button> | ||||
|   </div> | ||||
|   </FilterToggle> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -3,13 +3,33 @@ | |||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import FilteredLayer from "../../Models/FilteredLayer" | ||||
|   import FilterToggle from "./FilterToggle.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
| 
 | ||||
|   export let activeFilters: ActiveFilter[] | ||||
|   export let state: SpecialVisualizationState | ||||
|   let loading = false | ||||
| 
 | ||||
| 
 | ||||
|   let activeLayers: Store<FilteredLayer[]> = state.layerState.activeLayers.mapD(l => l.filter(l => l.layerDef.isNormal())) | ||||
|   let nonactiveLayers: Store<FilteredLayer[]> = state.layerState.nonactiveLayers.mapD(l => l.filter(l => l.layerDef.isNormal())) | ||||
| 
 | ||||
|   function enableAllLayers() { | ||||
|     for (const flayer of $nonactiveLayers) { | ||||
|       flayer.isDisplayed.set(true) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function clear() { | ||||
|     loading = true | ||||
|     requestIdleCallback(() => { | ||||
|       enableAllLayers() | ||||
| 
 | ||||
| 
 | ||||
|       for (const activeFilter of activeFilters) { | ||||
|         activeFilter.control.setData(undefined) | ||||
|  | @ -18,25 +38,52 @@ | |||
|     }) | ||||
|   } | ||||
| </script> | ||||
| {#if activeFilters.length > 0} | ||||
| 
 | ||||
| {#if activeFilters.length > 0 || $activeLayers.length === 1 || $nonactiveLayers.length > 0} | ||||
|   <SidebarUnit> | ||||
|     <h3>Active filters</h3> | ||||
| 
 | ||||
|     {#if loading} | ||||
|       <Loading /> | ||||
|     {:else} | ||||
|       <div class="flex flex-wrap gap-x-1 gap-y-2"> | ||||
| 
 | ||||
|       {#each activeFilters as activeFilter (activeFilter)} | ||||
|         <div> | ||||
|         <ActiveFilterSvelte {activeFilter} /> | ||||
|         </div> | ||||
|       {/each} | ||||
|       </div> | ||||
|     <div class="flex justify-between"> | ||||
|       <h3>Active filters</h3> | ||||
| 
 | ||||
|       <button class="as-link subtle self-end" on:click={() => clear()} style="margin-right: 0.75rem"> | ||||
|         Clear filters | ||||
|       </button> | ||||
|     </div> | ||||
|     {#if loading} | ||||
|       <Loading /> | ||||
|     {:else} | ||||
| 
 | ||||
|       <div class="flex flex-wrap gap-x-1 gap-y-2 overflow-x-hidden overflow-y-auto"> | ||||
|         {#if $activeLayers.length === 1} | ||||
|           <FilterToggle on:click={() => enableAllLayers()}> | ||||
|             <div class="w-8 h-8 p-1"> | ||||
|               <ToSvelte construct={$activeLayers[0].layerDef.defaultIcon()} /> | ||||
|             </div> | ||||
|             <b> | ||||
|               <Tr t={$activeLayers[0].layerDef.name} /> | ||||
|             </b> | ||||
|           </FilterToggle> | ||||
|         {:else if $nonactiveLayers.length > 0} | ||||
|           {#each $nonactiveLayers as nonActive (nonActive.layerDef.id)} | ||||
|             <FilterToggle on:click={() => nonActive.isDisplayed.set(true)}> | ||||
|               <div class="w-8 h-8 p-1"> | ||||
|                 <ToSvelte construct={nonActive.layerDef.defaultIcon()} /> | ||||
|               </div> | ||||
|               <del class="block-ruby"> | ||||
|                 <Tr t={nonActive.layerDef.name} /> | ||||
|               </del> | ||||
|             </FilterToggle> | ||||
|           {/each} | ||||
|         {/if} | ||||
| 
 | ||||
| 
 | ||||
|         {#each activeFilters as activeFilter (activeFilter)} | ||||
|           <div> | ||||
|             <ActiveFilterSvelte {activeFilter} /> | ||||
|           </div> | ||||
|         {/each} | ||||
|       </div> | ||||
| 
 | ||||
| 
 | ||||
|     {/if} | ||||
|   </SidebarUnit> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,27 +1,36 @@ | |||
| <script lang="ts"> | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import type { FilterPayload } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { FilterPayload, FilterResult, LayerResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { createEventDispatcher } from "svelte" | ||||
|   import Icon from "../Map/Icon.svelte" | ||||
|   import Marker from "../Map/Marker.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
| 
 | ||||
|   export let entry: FilterPayload | ||||
|   let { option } = entry | ||||
|   export let entry: FilterResult | LayerResult | ||||
|   export let state: SpecialVisualizationState | ||||
|   let dispatch = createEventDispatcher<{ select }>() | ||||
| 
 | ||||
| 
 | ||||
|   function apply() { | ||||
|     state.searchState.apply(entry) | ||||
|     dispatch("select") | ||||
|       state.searchState.apply(entry) | ||||
|       dispatch("select") | ||||
|   } | ||||
| </script> | ||||
| <button on:click={() => apply()}> | ||||
|   <div class="flex flex-col items-start"> | ||||
| 
 | ||||
|     <div class="flex items-center gap-x-1"> | ||||
|       <Icon icon={option.icon ?? option.emoji} clss="w-4 h-4" emojiHeight="14px" /> | ||||
|       <Tr cls="whitespace-nowrap" t={option.question} /> | ||||
|       {#if entry.category === "layer"} | ||||
|         <div class="w-8 h-8 p-1"> | ||||
|           <ToSvelte construct={entry.payload.defaultIcon()} /> | ||||
|         </div> | ||||
|         <b> | ||||
|           <Tr t={entry.payload.name} /> | ||||
|         </b> | ||||
|       {:else} | ||||
|         <Icon icon={entry.payload.option.icon ?? entry.payload. option.emoji} clss="w-4 h-4" emojiHeight="14px" /> | ||||
|         <Tr cls="whitespace-nowrap" t={entry.payload.option.question} /> | ||||
|       {/if} | ||||
|     </div> | ||||
|   </div> | ||||
| </button> | ||||
|  |  | |||
							
								
								
									
										9
									
								
								src/UI/Search/FilterToggle.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/UI/Search/FilterToggle.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| <script lang="ts"> | ||||
|   import { XMarkIcon } from "@babeard/svelte-heroicons/mini" | ||||
| </script> | ||||
| <div class="badge button-unstyled w-fit"> | ||||
|   <slot/> | ||||
|     <button on:click> | ||||
|       <XMarkIcon class="w-5 h-5 pl-1" color="gray" /> | ||||
|     </button> | ||||
| </div> | ||||
|  | @ -1,6 +1,6 @@ | |||
| <script lang="ts"> | ||||
|   import { GeocodingUtils } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { GeocodeResult } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { GeoOperations } from "../../Logic/GeoOperations" | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <script lang="ts"> | ||||
|   import type { SearchResult } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { SearchResult } from "../../Logic/Search/GeocodingProvider" | ||||
| 
 | ||||
|   import ThemeResult from "../Search/ThemeResult.svelte" | ||||
|   import FilterResult from "./FilterResult.svelte" | ||||
|  |  | |||
|  | @ -1,51 +1,79 @@ | |||
| <script lang="ts"> | ||||
|   import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import { default as SearchResultSvelte } from "./SearchResult.svelte" | ||||
|   import MoreScreen from "../BigComponents/MoreScreen" | ||||
|   import type { GeocodeResult } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { FilterResult, GeocodeResult, LayerResult } from "../../Logic/Search/GeocodingProvider" | ||||
| 
 | ||||
|   import ActiveFilters from "./ActiveFilters.svelte" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
|   import FilterResult from "./FilterResult.svelte" | ||||
|   import {default as FilterResultSvelte} from "./FilterResult.svelte" | ||||
|   import ThemeResult from "./ThemeResult.svelte" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import { Dropdown, DropdownItem } from "flowbite-svelte" | ||||
|   import DotsCircleHorizontal from "@rgossiaux/svelte-heroicons/solid/DotsCircleHorizontal" | ||||
|   import DotMenu from "../Base/DotMenu.svelte" | ||||
|   import { CogIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   let activeFilters: Store<ActiveFilter[]> = state.layerState.activeFilters.map(fs => fs.filter(f => Constants.priviliged_layers.indexOf(<any>f.layer.id) < 0)) | ||||
|   let recentlySeen: Store<GeocodeResult[]> = state.userRelatedState.recentlyVisitedSearch.value | ||||
|   let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map(themes => themes.filter(th => th.id !== state.layout.id).slice(0, 6)) | ||||
|   let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map(themes => themes.filter(th => th !== state.layout.id).slice(0, 6)) | ||||
|   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview | ||||
|   let searchTerm = state.searchState.searchTerm | ||||
|   let results = state.searchState.suggestions | ||||
|   let isSearching = state.searchState.suggestionsSearchRunning | ||||
|   let filterResults = state.searchState.filterSuggestions | ||||
|   let activeLayers = state.layerState.activeLayers | ||||
|   let layerResults = state.searchState.layerSuggestions.map(layers => { | ||||
|     const nowActive = activeLayers.data.filter(al => al.layerDef.isNormal()) | ||||
|     if(nowActive.length === 1){ | ||||
|       const shownInActiveFiltersView = nowActive[0] | ||||
|       layers = layers.filter(l => l.payload.id !== shownInActiveFiltersView.layerDef.id) | ||||
|     } | ||||
|     return layers | ||||
|   }, [activeLayers]) | ||||
| 
 | ||||
| 
 | ||||
|   let filterResultsClipped = filterResults.mapD(filters => { | ||||
|     let layers = layerResults.data | ||||
|     const ls : (FilterResult | LayerResult)[] = [].concat(layers, filters) | ||||
|     if (ls.length <= 8) { | ||||
|       return ls | ||||
|     } | ||||
|     return ls.slice(0, 6) | ||||
|   }, [layerResults, activeLayers]) | ||||
|   let themeResults = state.searchState.themeSuggestions | ||||
| 
 | ||||
| </script> | ||||
| <div class="p-4 low-interaction flex gap-y-2 flex-col"> | ||||
| 
 | ||||
|   <ActiveFilters activeFilters={$activeFilters} /> | ||||
|   <ActiveFilters {state} activeFilters={$activeFilters} /> | ||||
| 
 | ||||
|   {#if $searchTerm.length > 0 && $filterResults.length > 0} | ||||
|   {#if $searchTerm.length === 0 && $filterResults.length === 0 && $activeFilters.length === 0 && $recentThemes.length === 0} | ||||
|     <div class="p-8 items-center text-center"> | ||||
|       <b>Use the search bar above to search for locations, filters and other maps</b> | ||||
|     </div> | ||||
|   {/if} | ||||
| 
 | ||||
|   {#if $searchTerm.length > 0 && ($filterResults.length > 0 || $layerResults.length > 0)} | ||||
|     <SidebarUnit> | ||||
| 
 | ||||
|       <h3>Pick a filter below</h3> | ||||
| 
 | ||||
|       <div class="flex flex-wrap"> | ||||
|         {#each $filterResults as filterResult (filterResult)} | ||||
|           <FilterResult {state} entry={filterResult} /> | ||||
|         {#each $filterResultsClipped as filterResult (filterResult)} | ||||
|           <FilterResultSvelte {state} entry={filterResult} /> | ||||
|         {/each} | ||||
|       </div> | ||||
|       {#if $filterResults.length + $layerResults.length > $filterResultsClipped.length} | ||||
|         <div class="flex justify-center"> | ||||
|           ... and {$filterResults.length + $layerResults.length - $filterResultsClipped.length} more ... | ||||
|         </div> | ||||
|       {/if} | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
|  | @ -68,7 +96,7 @@ | |||
| 
 | ||||
|       {:else if !$isSearching} | ||||
|         <b class="flex justify-center p-4"> | ||||
|           <Tr t={Translations.t.general.search.nothingFor.Subs({term: $searchTerm})} /> | ||||
|           <Tr t={Translations.t.general.search.nothingFor.Subs({term: "<i>"+$searchTerm+"</i>"})} /> | ||||
|         </b> | ||||
|       {/if} | ||||
|     </SidebarUnit> | ||||
|  | @ -88,8 +116,16 @@ | |||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
|   {#if $searchTerm.length === 0 && $recentlySeen?.length === 0 && $recentThemes.length === 0} | ||||
|     <SidebarUnit> | ||||
|       <h3> | ||||
| 
 | ||||
|   {#if $searchTerm.length == 0 && $recentlySeen?.length > 0} | ||||
|         Suggestions | ||||
|       </h3> | ||||
| 
 | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
|   {#if $searchTerm.length === 0 && $recentlySeen?.length > 0} | ||||
|     <SidebarUnit> | ||||
|       <div class="flex justify-between"> | ||||
| 
 | ||||
|  |  | |||
|  | @ -326,7 +326,7 @@ | |||
| 
 | ||||
|     <div | ||||
|       id="top-bar" | ||||
|       class="flex bg-black-light-transparent pointer-events-auto items-center justify-between px-4 py-1 flex-wrap-reverse"> | ||||
|       class="flex bg-black-light-transparent pointer-events-auto items-center justify-between px-4 py-1 flex-wrap"> | ||||
|       <!-- Top bar with tools --> | ||||
|       <div class="flex items-center"> | ||||
| 
 | ||||
|  | @ -361,7 +361,7 @@ | |||
|       {/if} | ||||
| 
 | ||||
|       <If condition={state.featureSwitches.featureSwitchSearch}> | ||||
|         <div class="flex items-center"> | ||||
|         <div class="flex items-center flex-grow justify-end"> | ||||
|           <div class="w-full sm:w-64"> | ||||
|             <Searchbar value={state.searchState.searchTerm} isFocused={state.searchState.searchIsFocused} /> | ||||
|           </div> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue