forked from MapComplete/MapComplete
		
	Add a search bar to filter many mappings
This commit is contained in:
		
							parent
							
								
									9384267b74
								
							
						
					
					
						commit
						e34e39d20f
					
				
					 3 changed files with 130 additions and 61 deletions
				
			
		|  | @ -1,34 +1,38 @@ | |||
| <script lang="ts"> | ||||
|   import { Translation } from "../../i18n/Translation"; | ||||
|   import SpecialTranslation from "./SpecialTranslation.svelte"; | ||||
|   import type { SpecialVisualizationState } from "../../SpecialVisualization"; | ||||
|   import type { Feature } from "geojson"; | ||||
|   import { UIEventSource } from "../../../Logic/UIEventSource"; | ||||
|   import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||
|     import {Translation} from "../../i18n/Translation"; | ||||
|     import SpecialTranslation from "./SpecialTranslation.svelte"; | ||||
|     import type {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||
|     import type {Feature} from "geojson"; | ||||
|     import {Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||
|     import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||
|     import Locale from "../../i18n/Locale"; | ||||
|     import {onDestroy} from "svelte"; | ||||
| 
 | ||||
|   export let selectedElement: Feature | ||||
|   export let tags: UIEventSource<Record<string, string>>; | ||||
|   export let state: SpecialVisualizationState | ||||
|   export let layer: LayerConfig | ||||
|   export let mapping: { | ||||
|     then: Translation; icon?: string; iconClass?: | "small" | ||||
|       | "medium" | ||||
|       | "large" | ||||
|       | "small-height" | ||||
|       | "medium-height" | ||||
|       | "large-height" | ||||
|   }; | ||||
|   let iconclass = "mapping-icon-" + mapping.iconClass; | ||||
|     export let selectedElement: Feature | ||||
|     export let tags: UIEventSource<Record<string, string>>; | ||||
|     export let state: SpecialVisualizationState | ||||
|     export let layer: LayerConfig | ||||
| 
 | ||||
|     export let mapping: { | ||||
|         readonly then: Translation; | ||||
|         readonly searchTerms?: Record<string, string[]> | ||||
|         readonly icon?: string; | ||||
|         readonly iconClass?: | "small" | ||||
|             | "medium" | ||||
|             | "large" | ||||
|             | "small-height" | ||||
|             | "medium-height" | ||||
|             | "large-height" | ||||
|     }; | ||||
|     let iconclass = "mapping-icon-" + mapping.iconClass; | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| {#if mapping.icon !== undefined} | ||||
|   <div class="inline-flex"> | ||||
|     <img class={iconclass+" mr-1"} src={mapping.icon}> | ||||
|     <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation> | ||||
|   </div> | ||||
|     <div class="inline-flex"> | ||||
|         <img class={iconclass+" mr-1"} src={mapping.icon}> | ||||
|         <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation> | ||||
|     </div> | ||||
| {:else if mapping.then !== undefined} | ||||
|   <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation> | ||||
|     <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation> | ||||
| {/if} | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										76
									
								
								UI/Popup/TagRendering/TagRenderingMappingInput.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								UI/Popup/TagRendering/TagRenderingMappingInput.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | |||
| <script lang="ts">/** | ||||
|  * A thin wrapper around 'TagRenderingMapping'. | ||||
|  * As extra, it contains: | ||||
|  * - a slot to place an input element (such as a radio or checkbox) | ||||
|  * - It'll hide the mapping if the searchterm does not match | ||||
|  */ | ||||
| import type {Feature} from "geojson"; | ||||
| import {ImmutableStore, Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||
| import type {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||
| import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; | ||||
| import Locale from "../../i18n/Locale"; | ||||
| import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig"; | ||||
| import {TagsFilter} from "../../../Logic/Tags/TagsFilter"; | ||||
| import {onDestroy} from "svelte"; | ||||
| import TagRenderingMapping from "./TagRenderingMapping.svelte"; | ||||
| 
 | ||||
| export let selectedElement: Feature | ||||
| export let tags: UIEventSource<Record<string, string>>; | ||||
| export let state: SpecialVisualizationState | ||||
| export let layer: LayerConfig | ||||
| 
 | ||||
| export let mapping: Mapping | ||||
| /** | ||||
|  * If the mapping is selected, it should always be shown | ||||
|  */ | ||||
| export let mappingIsSelected: boolean | ||||
| 
 | ||||
| /** | ||||
|  * If there are many mappings, we might hide it. | ||||
|  * This is the searchterm where it might hide | ||||
|  */ | ||||
| export let searchTerm: undefined | UIEventSource<string> | ||||
| let matchesTerm: Store<boolean> | undefined = searchTerm?.map(search => { | ||||
|     if (!search) { | ||||
|         return true | ||||
|     } | ||||
|     if(mappingIsSelected){ | ||||
|         return true | ||||
|     } | ||||
|     search = search.toLowerCase() | ||||
|     // There is a searchterm - this might hide the mapping | ||||
|     if (mapping.priorityIf?.matchesProperties(tags.data)) { | ||||
|         return true | ||||
|     } | ||||
|     if (mapping.then.txt.toLowerCase().indexOf(search) >= 0) { | ||||
|         return true | ||||
|     } | ||||
|     const searchTerms = mapping?.searchTerms[Locale.language.data] | ||||
|     if (searchTerms?.some(t => t.toLowerCase().indexOf(search) >= 0)) { | ||||
|         return true | ||||
|     } | ||||
|     return false | ||||
| }, [], onDestroy) ?? new ImmutableStore(true) | ||||
| 
 | ||||
| let mappingIsHidden: Store<boolean> = tags.map(tags => { | ||||
|     if (mapping.hideInAnswer === undefined || mapping.hideInAnswer === false) { | ||||
|         return false; | ||||
|     } | ||||
|     if (mapping.hideInAnswer === true) { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     return (<TagsFilter>mapping.hideInAnswer).matchesProperties(tags); | ||||
| }, [], onDestroy) | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| {#if $matchesTerm && !$mappingIsHidden } | ||||
| 
 | ||||
|     <label class="flex"> | ||||
|         <slot/> | ||||
|         <TagRenderingMapping {mapping} {tags} {state} {selectedElement} | ||||
|                              {layer}></TagRenderingMapping> | ||||
|     </label> | ||||
| {/if} | ||||
|  | @ -1,9 +1,8 @@ | |||
| <script lang="ts"> | ||||
|     import {UIEventSource} from "../../../Logic/UIEventSource"; | ||||
|     import {Store, UIEventSource} from "../../../Logic/UIEventSource"; | ||||
|     import type {SpecialVisualizationState} from "../../SpecialVisualization"; | ||||
|     import Tr from "../../Base/Tr.svelte"; | ||||
|     import If from "../../Base/If.svelte"; | ||||
|     import TagRenderingMapping from "./TagRenderingMapping.svelte"; | ||||
|     import type {Feature} from "geojson"; | ||||
|     import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig"; | ||||
|     import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; | ||||
|  | @ -19,7 +18,7 @@ | |||
|     import LoginToggle from "../../Base/LoginToggle.svelte"; | ||||
|     import SubtleButton from "../../Base/SubtleButton.svelte"; | ||||
|     import Loading from "../../Base/Loading.svelte"; | ||||
|     import type {Writable} from "svelte/store"; | ||||
|     import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"; | ||||
| 
 | ||||
|     export let config: TagRenderingConfig; | ||||
|     export let tags: UIEventSource<Record<string, string>>; | ||||
|  | @ -38,6 +37,7 @@ | |||
|     let selectedMapping: number = undefined; | ||||
|     let checkedMappings: boolean[]; | ||||
|     $: { | ||||
|         mappings = config.mappings | ||||
|         // We received a new config -> reinit | ||||
|         console.log("Initing checkedMappings for", config) | ||||
|         if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) { | ||||
|  | @ -47,25 +47,16 @@ | |||
|     } | ||||
|     let selectedTags: TagsFilter = undefined; | ||||
| 
 | ||||
|     function mappingIsHidden(mapping: Mapping): boolean { | ||||
|         if (mapping.hideInAnswer === undefined || mapping.hideInAnswer === false) { | ||||
|             return false; | ||||
|         } | ||||
|         if (mapping.hideInAnswer === true) { | ||||
|             return true; | ||||
|         } | ||||
|         return (<TagsFilter>mapping.hideInAnswer).matchesProperties(tags.data); | ||||
| 
 | ||||
|     let mappings: Mapping[] = config?.mappings; | ||||
|     let searchTerm: Store<string> = new UIEventSource("") | ||||
|     $:{ | ||||
|         console.log("Seachterm:", $searchTerm) | ||||
|     } | ||||
| 
 | ||||
|     let mappings: Mapping[]; | ||||
|     let searchTerm: Writable<string> = new UIEventSource("") | ||||
|     $:{console.log("Seachterm:", $searchTerm)} | ||||
|      | ||||
|     $: { | ||||
|         mappings = config.mappings?.filter(m => !mappingIsHidden(m)); | ||||
|         try { | ||||
|             let freeformInputValue = $freeformInput | ||||
|             selectedTags = config?.constructChangeSpecification(freeformInputValue, selectedMapping, checkedMappings, tags.data); | ||||
|             selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings, tags.data); | ||||
|         } catch (e) { | ||||
|             console.error("Could not calculate changeSpecification:", e); | ||||
|             selectedTags = undefined; | ||||
|  | @ -143,27 +134,27 @@ | |||
| 
 | ||||
| 
 | ||||
|         {#if config.mappings?.length >= 8} | ||||
|             <input type="text" bind:value={$searchTerm}> | ||||
|             <div class="flex w-full"> | ||||
|                 <img src="./assets/svg/search.svg" class="w-6 h-6"/> | ||||
|                 <input type="text" bind:value={$searchTerm} class="w-full"> | ||||
|             </div> | ||||
|         {/if} | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         {#if config.freeform?.key && !(mappings?.length > 0)} | ||||
|             <!-- There are no options to choose from, simply show the input element: fill out the text field --> | ||||
|             <FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/> | ||||
|             <img src="./assets/svg/search.svg" class="w-4 h-4"/> | ||||
|         {:else if mappings !== undefined && !config.multiAnswer} | ||||
|             <!-- Simple radiobuttons as mapping --> | ||||
|             <div class="flex flex-col"> | ||||
|                 {#each config.mappings as mapping, i (mapping.then)} | ||||
|                     <!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices--> | ||||
|                     {#if !mappingIsHidden(mapping)  } | ||||
|                         <label class="flex"> | ||||
|                             <input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} | ||||
|                                    value={i}> | ||||
|                             <TagRenderingMapping {mapping} {tags} {state} {selectedElement} | ||||
|                                                  {layer}></TagRenderingMapping> | ||||
|                         </label> | ||||
|                     {/if} | ||||
|                     <TagRenderingMappingInput {mapping} {tags} {state} {selectedElement} | ||||
|                                               {layer} {searchTerm} mappingIsSelected={selectedMapping === i}> | ||||
|                         <input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} | ||||
|                                value={i}> | ||||
| 
 | ||||
|                     </TagRenderingMappingInput> | ||||
|                 {/each} | ||||
|                 {#if config.freeform?.key} | ||||
|                     <label class="flex"> | ||||
|  | @ -178,13 +169,11 @@ | |||
|             <!-- Multiple answers can be chosen: checkboxes --> | ||||
|             <div class="flex flex-col"> | ||||
|                 {#each config.mappings as mapping, i (mapping.then)} | ||||
|                     {#if !mappingIsHidden(mapping)} | ||||
|                         <label class="flex"> | ||||
|                             <input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} | ||||
|                                    bind:checked={checkedMappings[i]}> | ||||
|                             <TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping> | ||||
|                         </label> | ||||
|                     {/if} | ||||
|                     <TagRenderingMappingInput {mapping} {tags} {state} {selectedElement} | ||||
|                                               {layer} {searchTerm} mappingIsSelected={checkedMappings[i]}> | ||||
|                         <input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} | ||||
|                                bind:checked={checkedMappings[i]}> | ||||
|                     </TagRenderingMappingInput> | ||||
|                 {/each} | ||||
|                 {#if config.freeform?.key} | ||||
|                     <label class="flex"> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue