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">
|
<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}
|
||||||
|
|
||||||
|
|
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">
|
<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">
|
||||||
|
|
Loading…
Reference in a new issue