Add a search bar to filter many mappings

This commit is contained in:
Pieter Vander Vennet 2023-05-05 00:34:30 +02:00
parent 9384267b74
commit e34e39d20f
3 changed files with 130 additions and 61 deletions

View file

@ -1,34 +1,38 @@
<script lang="ts"> <script lang="ts">
import { Translation } from "../../i18n/Translation"; import {Translation} from "../../i18n/Translation";
import SpecialTranslation from "./SpecialTranslation.svelte"; import SpecialTranslation from "./SpecialTranslation.svelte";
import type { SpecialVisualizationState } from "../../SpecialVisualization"; import type {SpecialVisualizationState} from "../../SpecialVisualization";
import type { Feature } from "geojson"; import type {Feature} from "geojson";
import { UIEventSource } from "../../../Logic/UIEventSource"; import {Store, UIEventSource} from "../../../Logic/UIEventSource";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"; import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import Locale from "../../i18n/Locale";
import {onDestroy} from "svelte";
export let selectedElement: Feature export let selectedElement: Feature
export let tags: UIEventSource<Record<string, string>>; export let tags: UIEventSource<Record<string, string>>;
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let layer: LayerConfig 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 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> </script>
{#if mapping.icon !== undefined} {#if mapping.icon !== undefined}
<div class="inline-flex"> <div class="inline-flex">
<img class={iconclass+" mr-1"} src={mapping.icon}> <img class={iconclass+" mr-1"} src={mapping.icon}>
<SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation> <SpecialTranslation t={mapping.then} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
</div> </div>
{:else if mapping.then !== undefined} {: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} {/if}

View 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}

View file

@ -1,9 +1,8 @@
<script lang="ts"> <script lang="ts">
import {UIEventSource} from "../../../Logic/UIEventSource"; import {Store, UIEventSource} from "../../../Logic/UIEventSource";
import type {SpecialVisualizationState} from "../../SpecialVisualization"; import type {SpecialVisualizationState} from "../../SpecialVisualization";
import Tr from "../../Base/Tr.svelte"; import Tr from "../../Base/Tr.svelte";
import If from "../../Base/If.svelte"; import If from "../../Base/If.svelte";
import TagRenderingMapping from "./TagRenderingMapping.svelte";
import type {Feature} from "geojson"; import type {Feature} from "geojson";
import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig"; import type {Mapping} from "../../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"; import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
@ -19,7 +18,7 @@
import LoginToggle from "../../Base/LoginToggle.svelte"; import LoginToggle from "../../Base/LoginToggle.svelte";
import SubtleButton from "../../Base/SubtleButton.svelte"; import SubtleButton from "../../Base/SubtleButton.svelte";
import Loading from "../../Base/Loading.svelte"; import Loading from "../../Base/Loading.svelte";
import type {Writable} from "svelte/store"; import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
export let config: TagRenderingConfig; export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>; export let tags: UIEventSource<Record<string, string>>;
@ -38,6 +37,7 @@
let selectedMapping: number = undefined; let selectedMapping: number = undefined;
let checkedMappings: boolean[]; let checkedMappings: boolean[];
$: { $: {
mappings = config.mappings
// We received a new config -> reinit // We received a new config -> reinit
console.log("Initing checkedMappings for", config) console.log("Initing checkedMappings for", config)
if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) { if (config.mappings?.length > 0 && (checkedMappings === undefined || checkedMappings?.length < config.mappings.length)) {
@ -47,25 +47,16 @@
} }
let selectedTags: TagsFilter = undefined; let selectedTags: TagsFilter = undefined;
function mappingIsHidden(mapping: Mapping): boolean {
if (mapping.hideInAnswer === undefined || mapping.hideInAnswer === false) { let mappings: Mapping[] = config?.mappings;
return false; let searchTerm: Store<string> = new UIEventSource("")
} $:{
if (mapping.hideInAnswer === true) { console.log("Seachterm:", $searchTerm)
return true;
}
return (<TagsFilter>mapping.hideInAnswer).matchesProperties(tags.data);
} }
let mappings: Mapping[];
let searchTerm: Writable<string> = new UIEventSource("")
$:{console.log("Seachterm:", $searchTerm)}
$: { $: {
mappings = config.mappings?.filter(m => !mappingIsHidden(m));
try { try {
let freeformInputValue = $freeformInput selectedTags = config?.constructChangeSpecification($freeformInput, selectedMapping, checkedMappings, tags.data);
selectedTags = config?.constructChangeSpecification(freeformInputValue, selectedMapping, checkedMappings, tags.data);
} catch (e) { } catch (e) {
console.error("Could not calculate changeSpecification:", e); console.error("Could not calculate changeSpecification:", e);
selectedTags = undefined; selectedTags = undefined;
@ -143,27 +134,27 @@
{#if config.mappings?.length >= 8} {#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}
{#if config.freeform?.key && !(mappings?.length > 0)} {#if config.freeform?.key && !(mappings?.length > 0)}
<!-- There are no options to choose from, simply show the input element: fill out the text field --> <!-- There are no options to choose from, simply show the input element: fill out the text field -->
<FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/> <FreeformInput {config} {tags} feature={selectedElement} value={freeformInput}/>
<img src="./assets/svg/search.svg" class="w-4 h-4"/>
{:else if mappings !== undefined && !config.multiAnswer} {:else if mappings !== undefined && !config.multiAnswer}
<!-- Simple radiobuttons as mapping --> <!-- Simple radiobuttons as mapping -->
<div class="flex flex-col"> <div class="flex flex-col">
{#each config.mappings as mapping, i (mapping.then)} {#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--> <!-- Even though we have a list of 'mappings' already, we still iterate over the list as to keep the original indices-->
{#if !mappingIsHidden(mapping) } <TagRenderingMappingInput {mapping} {tags} {state} {selectedElement}
<label class="flex"> {layer} {searchTerm} mappingIsSelected={selectedMapping === i}>
<input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id} <input type="radio" bind:group={selectedMapping} name={"mappings-radio-"+config.id}
value={i}> value={i}>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}
{layer}></TagRenderingMapping> </TagRenderingMappingInput>
</label>
{/if}
{/each} {/each}
{#if config.freeform?.key} {#if config.freeform?.key}
<label class="flex"> <label class="flex">
@ -178,13 +169,11 @@
<!-- Multiple answers can be chosen: checkboxes --> <!-- Multiple answers can be chosen: checkboxes -->
<div class="flex flex-col"> <div class="flex flex-col">
{#each config.mappings as mapping, i (mapping.then)} {#each config.mappings as mapping, i (mapping.then)}
{#if !mappingIsHidden(mapping)} <TagRenderingMappingInput {mapping} {tags} {state} {selectedElement}
<label class="flex"> {layer} {searchTerm} mappingIsSelected={checkedMappings[i]}>
<input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i} <input type="checkbox" name={"mappings-checkbox-"+config.id+"-"+i}
bind:checked={checkedMappings[i]}> bind:checked={checkedMappings[i]}>
<TagRenderingMapping {mapping} {tags} {state} {selectedElement}></TagRenderingMapping> </TagRenderingMappingInput>
</label>
{/if}
{/each} {/each}
{#if config.freeform?.key} {#if config.freeform?.key}
<label class="flex"> <label class="flex">