forked from MapComplete/MapComplete
		
	Search: similar filters in different layers are now merged, fix #
This commit is contained in:
		
							parent
							
								
									6ebc0632a3
								
							
						
					
					
						commit
						48186aa530
					
				
					 15 changed files with 156 additions and 63 deletions
				
			
		|  | @ -26,5 +26,5 @@ To validate the 'search with filters', the tester was tasked with searching all | ||||||
| 
 | 
 | ||||||
| ## To improve | ## To improve | ||||||
| 
 | 
 | ||||||
| [ ] Why are there multiple "Open Now" filters? | [x] Why are there multiple "Open Now" filters? Because of different layers with a similar filter, they are now shown merged | ||||||
| [x] Special layers (e.g. gps location) are disabled as well (fixed now) | [x] Special layers (e.g. gps location) are disabled as well (fixed now) | ||||||
|  |  | ||||||
|  | @ -406,6 +406,7 @@ | ||||||
|             "error": "Something went wrong…", |             "error": "Something went wrong…", | ||||||
|             "instructions": "Use the search bar above to search for locations, filters or other thematic maps", |             "instructions": "Use the search bar above to search for locations, filters or other thematic maps", | ||||||
|             "locations": "Locations", |             "locations": "Locations", | ||||||
|  |             "nMoreFilters": "{n} more", | ||||||
|             "nothing": "Nothing found…", |             "nothing": "Nothing found…", | ||||||
|             "nothingFor": "No results found for {term}", |             "nothingFor": "No results found for {term}", | ||||||
|             "otherMaps": "Other maps", |             "otherMaps": "Other maps", | ||||||
|  |  | ||||||
|  | @ -7208,7 +7208,7 @@ | ||||||
|         }, |         }, | ||||||
|         "description": "Obchod", |         "description": "Obchod", | ||||||
|         "filter": { |         "filter": { | ||||||
|             "1": { |             "0": { | ||||||
|                 "options": { |                 "options": { | ||||||
|                     "0": { |                     "0": { | ||||||
|                         "question": "Zobrazit pouze obchody prodávající použité zboží" |                         "question": "Zobrazit pouze obchody prodávající použité zboží" | ||||||
|  |  | ||||||
|  | @ -9087,7 +9087,7 @@ | ||||||
|         }, |         }, | ||||||
|         "description": "Ein Geschäft", |         "description": "Ein Geschäft", | ||||||
|         "filter": { |         "filter": { | ||||||
|             "1": { |             "0": { | ||||||
|                 "options": { |                 "options": { | ||||||
|                     "0": { |                     "0": { | ||||||
|                         "question": "Nur Second-Hand-Geschäfte anzeigen" |                         "question": "Nur Second-Hand-Geschäfte anzeigen" | ||||||
|  |  | ||||||
|  | @ -9122,7 +9122,7 @@ | ||||||
|         }, |         }, | ||||||
|         "description": "A shop", |         "description": "A shop", | ||||||
|         "filter": { |         "filter": { | ||||||
|             "1": { |             "0": { | ||||||
|                 "options": { |                 "options": { | ||||||
|                     "0": { |                     "0": { | ||||||
|                         "question": "Only show shops selling second-hand items" |                         "question": "Only show shops selling second-hand items" | ||||||
|  |  | ||||||
|  | @ -1188,14 +1188,14 @@ input[type="range"].range-lg::-moz-range-thumb { | ||||||
|   left: 25%; |   left: 25%; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .bottom-4 { |  | ||||||
|   bottom: 1rem; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .top-6 { | .top-6 { | ||||||
|   top: 1.5rem; |   top: 1.5rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .bottom-4 { | ||||||
|  |   bottom: 1rem; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .bottom-5 { | .bottom-5 { | ||||||
|   bottom: 1.25rem; |   bottom: 1.25rem; | ||||||
| } | } | ||||||
|  | @ -2760,6 +2760,10 @@ input[type="range"].range-lg::-moz-range-thumb { | ||||||
|   overflow-y: auto; |   overflow-y: auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .overflow-x-hidden { | ||||||
|  |   overflow-x: hidden; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .overflow-y-scroll { | .overflow-y-scroll { | ||||||
|   overflow-y: scroll; |   overflow-y: scroll; | ||||||
| } | } | ||||||
|  | @ -4719,6 +4723,11 @@ input[type="range"].range-lg::-moz-range-thumb { | ||||||
|   box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); |   box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .shadow-transparent { | ||||||
|  |   --tw-shadow-color: transparent; | ||||||
|  |   --tw-shadow: var(--tw-shadow-colored); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .shadow-gray-300 { | .shadow-gray-300 { | ||||||
|   --tw-shadow-color: #D1D5DB; |   --tw-shadow-color: #D1D5DB; | ||||||
|   --tw-shadow: var(--tw-shadow-colored); |   --tw-shadow: var(--tw-shadow-colored); | ||||||
|  | @ -5353,8 +5362,8 @@ h2.group { | ||||||
|   align-items: center; |   align-items: center; | ||||||
|   white-space: nowrap; |   white-space: nowrap; | ||||||
|   border-radius: 999rem; |   border-radius: 999rem; | ||||||
|   padding-left: 0.5rem; |   padding-left: 0.25rem; | ||||||
|   padding-right: 0.5rem; |   padding-right: 0.25rem; | ||||||
|   border: 1px solid var(--subtle-detail-color-light-contrast); |   border: 1px solid var(--subtle-detail-color-light-contrast); | ||||||
|   background-color: var(--low-interaction-background); |   background-color: var(--low-interaction-background); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import { SpecialVisualizationState } from "../../UI/SpecialVisualization" |  | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import Locale from "../../UI/i18n/Locale" | import Locale from "../../UI/i18n/Locale" | ||||||
| import Constants from "../../Models/Constants" | import Constants from "../../Models/Constants" | ||||||
|  | @ -110,4 +109,23 @@ export default class FilterSearch { | ||||||
|         Utils.shuffle(result) |         Utils.shuffle(result) | ||||||
|         return result.slice(0, 6) |         return result.slice(0, 6) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Partitions the list of filters in such a way that identically appearing filters will be in the same sublist. | ||||||
|  |      * | ||||||
|  |      * Note that this depends on the language and the displayed text. For example, two filters {"en": "A", "nl": "B"} and {"en": "X", "nl": "B"} will be joined for dutch but not for English | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     static mergeSemiIdenticalLayers<T extends FilterSearchResult = FilterSearchResult>(filters: ReadonlyArray<T>, language: string):T[][]  { | ||||||
|  |         const results : Record<string, T[]> = {} | ||||||
|  |         for (const filter of filters) { | ||||||
|  |             const txt = filter.option.question.textFor(language) | ||||||
|  |             if(results[txt]){ | ||||||
|  |                 results[txt].push(filter) | ||||||
|  |             }else{ | ||||||
|  |                 results[txt] = [filter] | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return Object.values(results) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ export default class SearchState { | ||||||
|                     return !foundMatch |                     return !foundMatch | ||||||
|                 }) |                 }) | ||||||
|             }, [state.layerState.activeFilters]) |             }, [state.layerState.activeFilters]) | ||||||
|         this.locationResults =new GeocodingFeatureSource(this.suggestions.stabilized(250)) |         this.locationResults = new GeocodingFeatureSource(this.suggestions.stabilized(250)) | ||||||
| 
 | 
 | ||||||
|         this.showSearchDrawer = new UIEventSource(false) |         this.showSearchDrawer = new UIEventSource(false) | ||||||
| 
 | 
 | ||||||
|  | @ -92,7 +92,7 @@ export default class SearchState { | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public async apply(result: FilterSearchResult | LayerConfig) { |     public async apply(result: FilterSearchResult[] | LayerConfig) { | ||||||
|         if (result instanceof LayerConfig) { |         if (result instanceof LayerConfig) { | ||||||
|             return this.applyLayer(result) |             return this.applyLayer(result) | ||||||
|         } |         } | ||||||
|  | @ -105,29 +105,35 @@ export default class SearchState { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private async applyFilter(payload: FilterSearchResult) { |     private async applyFilter(payload: FilterSearchResult[]) { | ||||||
|         const state = this.state |         const state = this.state | ||||||
| 
 | 
 | ||||||
|         const { layer, filter, index } = payload |         const layers = payload.map(fsr => fsr.layer.id) | ||||||
|         for (const [name, otherLayer] of state.layerState.filteredLayers) { |         for (const [name, otherLayer] of state.layerState.filteredLayers) { | ||||||
|             const layer = otherLayer.layerDef |             const layer = otherLayer.layerDef | ||||||
|             if(!layer.isNormal()){ |             if (!layer.isNormal()) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             otherLayer.isDisplayed.setData(payload.layer.id === layer.id) |             if(otherLayer.layerDef.minzoom > state.mapProperties.minzoom.data) { | ||||||
|  |                 // Currently not displayed, we don't hide
 | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  |             otherLayer.isDisplayed.setData(layers.indexOf(layer.id) > 0) | ||||||
|         } |         } | ||||||
|         const flayer = state.layerState.filteredLayers.get(layer.id) |         for (const  { filter, index, layer } of payload) { | ||||||
|         flayer.isDisplayed.set(true) |             const flayer = state.layerState.filteredLayers.get(layer.id) | ||||||
|         const filtercontrol = flayer.appliedFilters.get(filter.id) |             flayer.isDisplayed.set(true) | ||||||
|         if (filtercontrol.data === index) { |             const filtercontrol = flayer.appliedFilters.get(filter.id) | ||||||
|             filtercontrol.setData(undefined) |             if (filtercontrol.data === index) { | ||||||
|         } else { |                 filtercontrol.setData(undefined) | ||||||
|             filtercontrol.setData(index) |             } else { | ||||||
|  |                 filtercontrol.setData(index) | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     closeIfFullscreen() { |     closeIfFullscreen() { | ||||||
|         if(window.innerWidth < 640){ |         if (window.innerWidth < 640) { | ||||||
|             this.showSearchDrawer.set(false) |             this.showSearchDrawer.set(false) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -135,7 +141,7 @@ export default class SearchState { | ||||||
|     clickedOnMap(feature: Feature) { |     clickedOnMap(feature: Feature) { | ||||||
|         const osmid = feature.properties.osm_id |         const osmid = feature.properties.osm_id | ||||||
|         const localElement = this.state.indexedFeatures.featuresById.data.get(osmid) |         const localElement = this.state.indexedFeatures.featuresById.data.get(osmid) | ||||||
|         if(localElement){ |         if (localElement) { | ||||||
|             this.state.selectedElement.set(localElement) |             this.state.selectedElement.set(localElement) | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ | ||||||
|         background: var(--background-color); |         background: var(--background-color); | ||||||
|         padding: 0.5rem; |         padding: 0.5rem; | ||||||
|         border-radius: 0.5rem; |         border-radius: 0.5rem; | ||||||
|  |         overflow-y: auto; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     :global(.sidebar-unit > h3) { |     :global(.sidebar-unit > h3) { | ||||||
|  |  | ||||||
|  | @ -2,11 +2,16 @@ | ||||||
|   import { Accordion, AccordionItem } from "flowbite-svelte" |   import { Accordion, AccordionItem } from "flowbite-svelte" | ||||||
| 
 | 
 | ||||||
|   export let expanded = false |   export let expanded = false | ||||||
|  |   export let noBorder = false | ||||||
|  | let defaultClass: string = undefined | ||||||
|  |   if(noBorder){ | ||||||
|  |     defaultClass = "unstyled w-full flex-grow" | ||||||
|  |   } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <Accordion> | <Accordion> | ||||||
|   <AccordionItem open={expanded} paddingDefault="p-0" inactiveClass="text-black"> |   <AccordionItem open={expanded} paddingDefault="p-0" inactiveClass="text-black" {defaultClass}> | ||||||
|     <span slot="header" class="w-full p-2 text-base"> |     <span slot="header" class={!noBorder ? "w-full p-2 text-base" : "w-full"}> | ||||||
|       <slot name="header" /> |       <slot name="header" /> | ||||||
|     </span> |     </span> | ||||||
|     <div class="low-interaction rounded-b p-2"> |     <div class="low-interaction rounded-b p-2"> | ||||||
|  |  | ||||||
|  | @ -3,25 +3,36 @@ | ||||||
|   import FilterOption from "./FilterOption.svelte" |   import FilterOption from "./FilterOption.svelte" | ||||||
|   import Loading from "../Base/Loading.svelte" |   import Loading from "../Base/Loading.svelte" | ||||||
|   import FilterToggle from "./FilterToggle.svelte" |   import FilterToggle from "./FilterToggle.svelte" | ||||||
|  |   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   export let activeFilter: ActiveFilter |   export let activeFilter: ActiveFilter[] | ||||||
|   let { control, filter } = activeFilter |   let { control, filter } = activeFilter[0] | ||||||
|   let option = control.map(c => filter.options[c] ?? filter.options[0]) |   let option = control.map(c => filter.options[c] ?? filter.options[0]) | ||||||
|   let loading = false |   let loading = false | ||||||
| 
 | 
 | ||||||
|   function clear() { |   function clear() { | ||||||
|     loading = true |     loading = true | ||||||
|     requestIdleCallback(() => { |     requestIdleCallback(() => { | ||||||
|       control.setData(undefined) |       for (const af of activeFilter) { | ||||||
|  |         af.control.setData(undefined) | ||||||
|  |       } | ||||||
|       loading = false |       loading = false | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   export let state: SpecialVisualizationState | ||||||
|  |   let debug = state.featureSwitches.featureSwitchIsDebugging | ||||||
| </script> | </script> | ||||||
| {#if loading} | {#if loading} | ||||||
|   <Loading /> |   <Loading /> | ||||||
| {:else } | {:else } | ||||||
|   <FilterToggle  on:click={() => clear()}> |   <FilterToggle on:click={() => clear()}> | ||||||
|     <FilterOption option={$option} /> |     <FilterOption option={$option} /> | ||||||
|  |     {#if $debug} | ||||||
|  |       <span class="subtle"> | ||||||
|  |         ({activeFilter.map(af => af.layer.id).join(", ")}) | ||||||
|  |       </span> | ||||||
|  |     {/if} | ||||||
|   </FilterToggle> |   </FilterToggle> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -8,10 +8,17 @@ | ||||||
|   import FilterToggle from "./FilterToggle.svelte" |   import FilterToggle from "./FilterToggle.svelte" | ||||||
|   import ToSvelte from "../Base/ToSvelte.svelte" |   import ToSvelte from "../Base/ToSvelte.svelte" | ||||||
|   import Tr from "../Base/Tr.svelte" |   import Tr from "../Base/Tr.svelte" | ||||||
|   import { Store } from "../../Logic/UIEventSource" |   import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|  |   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" | ||||||
|  |   import FilterSearch from "../../Logic/Search/FilterSearch" | ||||||
| 
 | 
 | ||||||
|   export let activeFilters: ActiveFilter[] |   import Locale from "../i18n/Locale" | ||||||
|  | 
 | ||||||
|  |   export let activeFilters: ( FilterSearchResult & ActiveFilter)[] | ||||||
|  |   let language = Locale.language | ||||||
|  |   let mergedActiveFilters = FilterSearch.mergeSemiIdenticalLayers(activeFilters, $language) | ||||||
|  |   $:mergedActiveFilters = FilterSearch.mergeSemiIdenticalLayers(activeFilters, $language) | ||||||
|   export let state: SpecialVisualizationState |   export let state: SpecialVisualizationState | ||||||
|   let loading = false |   let loading = false | ||||||
|   const t =Translations.t.general.search |   const t =Translations.t.general.search | ||||||
|  | @ -33,8 +40,6 @@ | ||||||
|     loading = true |     loading = true | ||||||
|     requestIdleCallback(() => { |     requestIdleCallback(() => { | ||||||
|       enableAllLayers() |       enableAllLayers() | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|       for (const activeFilter of activeFilters) { |       for (const activeFilter of activeFilters) { | ||||||
|         activeFilter.control.setData(undefined) |         activeFilter.control.setData(undefined) | ||||||
|       } |       } | ||||||
|  | @ -44,7 +49,7 @@ | ||||||
|   } |   } | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if activeFilters.length > 0 || $nonactiveLayers.length > 0} | {#if mergedActiveFilters.length > 0 || $nonactiveLayers.length > 0} | ||||||
|   <SidebarUnit> |   <SidebarUnit> | ||||||
|     <div class="flex justify-between"> |     <div class="flex justify-between"> | ||||||
|       <h3><Tr t={t.activeFilters}/></h3> |       <h3><Tr t={t.activeFilters}/></h3> | ||||||
|  | @ -81,9 +86,9 @@ | ||||||
|         {/if} |         {/if} | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         {#each activeFilters as activeFilter (activeFilter)} |         {#each mergedActiveFilters as activeFilter (activeFilter)} | ||||||
|           <div> |           <div> | ||||||
|             <ActiveFilterSvelte {activeFilter} /> |             <ActiveFilterSvelte {activeFilter} {state}/> | ||||||
|           </div> |           </div> | ||||||
|         {/each} |         {/each} | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -8,13 +8,19 @@ | ||||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|   import Loading from "../Base/Loading.svelte" |   import Loading from "../Base/Loading.svelte" | ||||||
| 
 | 
 | ||||||
|   export let entry: FilterSearchResult | LayerConfig |   export let entry: FilterSearchResult[] | LayerConfig | ||||||
|   let isLayer = entry instanceof LayerConfig |   let asFilter: FilterSearchResult[] | ||||||
|   let asLayer = <LayerConfig>entry |   let asLayer: LayerConfig | ||||||
|   let asFilter = <FilterSearchResult>entry |   if(Array.isArray(entry)){ | ||||||
|  |       asFilter = entry | ||||||
|  |   }else{ | ||||||
|  |     asLayer = <LayerConfig>entry | ||||||
|  | 
 | ||||||
|  |   } | ||||||
|   export let state: SpecialVisualizationState |   export let state: SpecialVisualizationState | ||||||
| 
 | 
 | ||||||
|   let loading = false |   let loading = false | ||||||
|  |   let debug = state.featureSwitches.featureSwitchIsDebugging | ||||||
| 
 | 
 | ||||||
|   function apply() { |   function apply() { | ||||||
|     loading = true |     loading = true | ||||||
|  | @ -34,7 +40,7 @@ | ||||||
|   {/if} |   {/if} | ||||||
|   <div class="flex flex-col items-start"> |   <div class="flex flex-col items-start"> | ||||||
|     <div class="flex items-center gap-x-1"> |     <div class="flex items-center gap-x-1"> | ||||||
|       {#if isLayer} |       {#if asLayer} | ||||||
|         <div class="w-8 h-8 p-1"> |         <div class="w-8 h-8 p-1"> | ||||||
|           <ToSvelte construct={asLayer.defaultIcon()} /> |           <ToSvelte construct={asLayer.defaultIcon()} /> | ||||||
|         </div> |         </div> | ||||||
|  | @ -42,8 +48,11 @@ | ||||||
|           <Tr t={asLayer.name} /> |           <Tr t={asLayer.name} /> | ||||||
|         </b> |         </b> | ||||||
|       {:else} |       {:else} | ||||||
|         <Icon icon={asFilter.option.icon ?? asFilter.option.emoji} clss="w-4 h-4" emojiHeight="14px" /> |         <Icon icon={asFilter[0].option.icon ?? asFilter[0].option.emoji} clss="w-4 h-4" emojiHeight="14px" /> | ||||||
|         <Tr cls="whitespace-nowrap" t={asFilter.option.question} /> |         <Tr cls="whitespace-nowrap" t={asFilter[0].option.question} /> | ||||||
|  |         {#if $debug} | ||||||
|  |         <span class="subtle">({asFilter.map(f => f.layer.id).join(", ")})</span> | ||||||
|  |           {/if} | ||||||
|       {/if} |       {/if} | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -4,15 +4,21 @@ | ||||||
| 
 | 
 | ||||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" |   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|  |   import FilterSearch from "../../Logic/Search/FilterSearch" | ||||||
|   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" |   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" | ||||||
|  | 
 | ||||||
|   import Tr from "../Base/Tr.svelte" |   import Tr from "../Base/Tr.svelte" | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|  |   import Locale from "../i18n/Locale" | ||||||
|  |   import { Store } from "../../Logic/UIEventSource" | ||||||
|  |   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" | ||||||
| 
 | 
 | ||||||
|   export let state: SpecialVisualizationState |   export let state: SpecialVisualizationState | ||||||
|   let searchTerm = state.searchState.searchTerm |   let searchTerm = state.searchState.searchTerm | ||||||
|   let activeLayers = state.layerState.activeLayers |   let activeLayers = state.layerState.activeLayers | ||||||
|   let filterResults = state.searchState.filterSuggestions |   let filterResults = state.searchState.filterSuggestions | ||||||
| 
 | 
 | ||||||
|  |   let filtersMerged = filterResults.map(filters => FilterSearch.mergeSemiIdenticalLayers(filters, Locale.language.data), [Locale.language]) | ||||||
| 
 | 
 | ||||||
|   let layerResults = state.searchState.layerSuggestions.map(layers => { |   let layerResults = state.searchState.layerSuggestions.map(layers => { | ||||||
|     const nowActive = activeLayers.data.filter(al => al.layerDef.isNormal()) |     const nowActive = activeLayers.data.filter(al => al.layerDef.isNormal()) | ||||||
|  | @ -22,30 +28,44 @@ | ||||||
|     } |     } | ||||||
|     return layers |     return layers | ||||||
|   }, [activeLayers]) |   }, [activeLayers]) | ||||||
|   let filterResultsClipped = filterResults.mapD(filters => { |   let filterResultsClipped: Store<{ | ||||||
|  |     clipped: (FilterSearchResult[] | LayerConfig)[], | ||||||
|  |     rest?: (FilterSearchResult[] | LayerConfig)[] | ||||||
|  |   }> = filtersMerged.mapD(filters => { | ||||||
|     let layers = layerResults.data |     let layers = layerResults.data | ||||||
|     const ls: (FilterSearchResult | LayerConfig)[] = [].concat(layers, filters) |     const ls: (FilterSearchResult[] | LayerConfig)[] = [].concat(layers, filters) | ||||||
|     if (ls.length <= 6) { |     if (ls.length <= 6) { | ||||||
|       return ls |       return { clipped: ls } | ||||||
|     } |     } | ||||||
|     return ls.slice(0, 4) |     return { clipped: ls.slice(0, 4), rest: ls.slice(4) } | ||||||
|   }, [layerResults, activeLayers]) |   }, [layerResults, activeLayers, Locale.language]) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| {#if $searchTerm.length > 0 && ($filterResults.length > 0 || $layerResults.length > 0)} | {#if $searchTerm.length > 0 && ($filterResults.length > 0 || $layerResults.length > 0)} | ||||||
|   <SidebarUnit> |   <SidebarUnit> | ||||||
| 
 | 
 | ||||||
|     <h3><Tr t={Translations.t.general.search.pickFilter} /></h3> |     <h3> | ||||||
|  |       <Tr t={Translations.t.general.search.pickFilter} /> | ||||||
|  |     </h3> | ||||||
| 
 | 
 | ||||||
|     <div class="flex flex-wrap"> |     <div class="flex flex-wrap"> | ||||||
|       {#each $filterResultsClipped as filterResult (filterResult)} |       {#each $filterResultsClipped.clipped as filterResult (filterResult)} | ||||||
|         <FilterResultSvelte {state} entry={filterResult} /> |         <FilterResultSvelte {state} entry={filterResult} /> | ||||||
|       {/each} |       {/each} | ||||||
|     </div> |     </div> | ||||||
|     {#if $filterResults.length + $layerResults.length > $filterResultsClipped.length} |     {#if $filtersMerged.length + $layerResults.length > $filterResultsClipped.clipped.length} | ||||||
|       <div class="flex justify-center"> |       <AccordionSingle noBorder> | ||||||
|         ... and {$filterResults.length + $layerResults.length - $filterResultsClipped.length} more ... |         <div class="flex justify-end text-sm subtle" slot="header"> | ||||||
|       </div> |           <Tr t={Translations.t.general.search.nMoreFilters.Subs( | ||||||
|  |           {n: $filtersMerged.length + $layerResults.length - $filterResultsClipped.clipped.length} | ||||||
|  |           )}/> | ||||||
|  |         </div> | ||||||
|  |         <div class="flex flex-wrap overflow-y-auto"> | ||||||
|  |           {#each $filterResultsClipped.rest as filterResult (filterResult)} | ||||||
|  |             <FilterResultSvelte {state} entry={filterResult} /> | ||||||
|  |           {/each} | ||||||
|  |         </div> | ||||||
|  |       </AccordionSingle> | ||||||
|     {/if} |     {/if} | ||||||
|   </SidebarUnit> |   </SidebarUnit> | ||||||
| {/if} | {/if} | ||||||
|  |  | ||||||
|  | @ -4,16 +4,22 @@ | ||||||
|   import Constants from "../../Models/Constants" |   import Constants from "../../Models/Constants" | ||||||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" |   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||||
|   import ThemeViewState from "../../Models/ThemeViewState" |   import ThemeViewState from "../../Models/ThemeViewState" | ||||||
|    import ThemeResults from "./ThemeResults.svelte" |   import ThemeResults from "./ThemeResults.svelte" | ||||||
|   import GeocodeResults from "./GeocodeResults.svelte" |   import GeocodeResults from "./GeocodeResults.svelte" | ||||||
|   import FilterResults from "./FilterResults.svelte" |   import FilterResults from "./FilterResults.svelte" | ||||||
|   import Tr from "../Base/Tr.svelte" |   import Tr from "../Base/Tr.svelte" | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|  |   import type { FilterSearchResult } from "../../Logic/Search/FilterSearch" | ||||||
| 
 | 
 | ||||||
|   export let state: ThemeViewState |   export let state: ThemeViewState | ||||||
|   let activeFilters: Store<ActiveFilter[]> = state.layerState.activeFilters.map(fs => fs.filter(f => |   let activeFilters: Store<(ActiveFilter & FilterSearchResult)[]> = state.layerState.activeFilters.map(fs => fs.filter(f => | ||||||
|     (f.filter.options[0].fields.length === 0) && |     (f.filter.options[0].fields.length === 0) && | ||||||
|     Constants.priviliged_layers.indexOf(<any>f.layer.id) < 0)) |     Constants.priviliged_layers.indexOf(<any>f.layer.id) < 0) | ||||||
|  |     .map(af => { | ||||||
|  |       const index = <number> af.control.data | ||||||
|  |       const r : FilterSearchResult & ActiveFilter = { ...af, index, option: af.filter.options[index] } | ||||||
|  |       return r | ||||||
|  |     })) | ||||||
|   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview |   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview | ||||||
|   let searchTerm = state.searchState.searchTerm |   let searchTerm = state.searchState.searchTerm | ||||||
| </script> | </script> | ||||||
|  | @ -23,13 +29,15 @@ | ||||||
| 
 | 
 | ||||||
|   {#if $searchTerm.length === 0 && $activeFilters.length === 0 } |   {#if $searchTerm.length === 0 && $activeFilters.length === 0 } | ||||||
|     <div class="p-8 items-center text-center"> |     <div class="p-8 items-center text-center"> | ||||||
|       <b><Tr t={Translations.t.general.search.instructions}/></b> |       <b> | ||||||
|  |         <Tr t={Translations.t.general.search.instructions} /> | ||||||
|  |       </b> | ||||||
|     </div> |     </div> | ||||||
|   {/if} |   {/if} | ||||||
| 
 | 
 | ||||||
|   <FilterResults {state}/> |   <FilterResults {state} /> | ||||||
| 
 | 
 | ||||||
|   <GeocodeResults {state}/> |   <GeocodeResults {state} /> | ||||||
| 
 | 
 | ||||||
|   {#if $allowOtherThemes} |   {#if $allowOtherThemes} | ||||||
|     <ThemeResults {state} /> |     <ThemeResults {state} /> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue