This commit is contained in:
Pieter Vander Vennet 2023-09-19 22:49:20 +02:00
parent d1aa751e18
commit 382f96596e
3 changed files with 132 additions and 78 deletions

View file

@ -132,6 +132,9 @@ export class TagUtils {
/** /**
* Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set. * Given multiple tagsfilters which can be used as answer, will take the tags with the same keys together as set.
*
* @see MatchesMultiAnswer to do the reverse
*
* E.g: * E.g:
* *
* const tag = TagUtils.ParseUploadableTag({"and": [ * const tag = TagUtils.ParseUploadableTag({"and": [

View file

@ -243,7 +243,10 @@ export default class TagRenderingConfig {
if (txt === "") { if (txt === "") {
throw context + " Rendering for language " + ln + " is empty" throw context + " Rendering for language " + ln + " is empty"
} }
if (txt.indexOf("{" + this.freeform.key + "}") >= 0 || txt.indexOf("&LBRACE" + this.freeform.key + "&RBRACE") ) { if (
txt.indexOf("{" + this.freeform.key + "}") >= 0 ||
txt.indexOf("&LBRACE" + this.freeform.key + "&RBRACE")
) {
continue continue
} }
if (txt.indexOf("{" + this.freeform.key + ":") >= 0) { if (txt.indexOf("{" + this.freeform.key + ":") >= 0) {
@ -645,6 +648,16 @@ export default class TagRenderingConfig {
/** /**
* Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply * Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply
* *
* const config = new TagRenderingConfig({"id":"bookcase-booktypes","render":{"en":"This place mostly serves {books}" },
* "question":{"en":"What kind of books can be found in this public bookcase?"},
* "freeform":{"key":"books","addExtraTags":["fixme=Freeform tag `books` used, to be doublechecked"],
* "inline":true},
* "multiAnswer":true,
* "mappings":[{"if":"books=children","then":"Mostly children books"},
* {"if":"books=adults","then": "Mostly books for adults"}]}
* , "testcase")
* config.constructChangeSpecification(undefined, undefined, [false, true, false], {amenity: "public_bookcase"}) // => new And([new Tag("books","adult")])
*
* @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set * @param freeformValue The freeform value which will be applied as 'freeform.key'. Ignored if 'freeform.key' is not set
* *
* @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform * @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform

View file

@ -1,76 +1,114 @@
<script lang="ts"> <script lang="ts">
import { Store, UIEventSource } from "../../../Logic/UIEventSource" import { 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 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";
import { TagsFilter } from "../../../Logic/Tags/TagsFilter" import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import FreeformInput from "./FreeformInput.svelte" import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js" import Translations from "../../i18n/Translations.js";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction" import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher, onDestroy } from "svelte" import { createEventDispatcher, onDestroy } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import SpecialTranslation from "./SpecialTranslation.svelte" import SpecialTranslation from "./SpecialTranslation.svelte";
import TagHint from "../TagHint.svelte" import TagHint from "../TagHint.svelte";
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 TagRenderingMappingInput from "./TagRenderingMappingInput.svelte" import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
import { Translation } from "../../i18n/Translation" import { Translation } from "../../i18n/Translation";
import Constants from "../../../Models/Constants" import Constants from "../../../Models/Constants";
import { Unit } from "../../../Models/Unit" import { Unit } from "../../../Models/Unit";
import UserRelatedState from "../../../Logic/State/UserRelatedState" import UserRelatedState from "../../../Logic/State/UserRelatedState";
import { twJoin } from "tailwind-merge" import { twJoin } from "tailwind-merge";
import { TagUtils } from "../../../Logic/Tags/TagUtils";
export let config: TagRenderingConfig export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>> export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature export let selectedElement: Feature;
export let state: SpecialVisualizationState export let state: SpecialVisualizationState;
export let layer: LayerConfig export let layer: LayerConfig;
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined) let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined);
let unit: Unit = layer.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key)) let unit: Unit = layer.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key));
// Will be bound if a freeform is available // Will be bound if a freeform is available
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]) let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
let selectedMapping: number = undefined let selectedMapping: number = undefined;
let checkedMappings: boolean[] let checkedMappings: boolean[];
$: {
let tgs = $tags /**
mappings = config.mappings?.filter((m) => { * Prepares and fills the checkedMappings
*/
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
mappings = confg.mappings?.filter((m) => {
if (typeof m.hideInAnswer === "boolean") { if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer return !m.hideInAnswer;
} }
return !m.hideInAnswer.matchesProperties(tgs) return !m.hideInAnswer.matchesProperties(tgs);
}) });
// We received a new config -> reinit // We received a new config -> reinit
unit = layer.units.find((unit) => unit.appliesToKeys.has(config.freeform?.key)) unit = layer.units.find((unit) => unit.appliesToKeys.has(confg.freeform?.key));
if ( if (
config.mappings?.length > 0 && confg.mappings?.length > 0 &&
(checkedMappings === undefined || checkedMappings?.length + 1 < config.mappings.length) (checkedMappings === undefined || checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
) { ) {
const seenFreeforms = [];
TagUtils.FlattenMultiAnswer()
checkedMappings = [ checkedMappings = [
...config.mappings.map((_) => false), ...confg.mappings.map((mapping) => {
false /*One element extra in case a freeform value is added*/, const matches = TagUtils.MatchesMultiAnswer(mapping.if, tgs)
] if (matches && confg.freeform) {
const newProps = TagUtils.changeAsProperties(mapping.if.asChange());
seenFreeforms.push(newProps[confg.freeform.key]);
}
return matches;
})
];
if(tgs !== undefined && confg.freeform){
const unseenFreeformValues = tgs[confg.freeform.key]?.split(";") ?? []
for (const seenFreeform of seenFreeforms) {
if(!seenFreeform){
continue
}
const index = unseenFreeformValues.indexOf(seenFreeform)
if(index < 0){
continue
}
unseenFreeformValues.splice(index, 1)
}
// TODO this has _to much_ values
freeformInput.setData(unseenFreeformValues.join(";"))
checkedMappings.push(unseenFreeformValues.length > 0)
}
} }
if (config.freeform?.key) { console.log("Inited 'checkMappings' to", checkedMappings);
if (!config.multiAnswer) { if (confg.freeform?.key) {
// Somehow, setting multianswer freeform values is broken if this is not set if (!confg.multiAnswer) {
freeformInput.setData(tgs[config.freeform.key]) // Somehow, setting multi-answer freeform values is broken if this is not set
freeformInput.setData(tgs[confg.freeform.key]);
} }
} else { } else {
freeformInput.setData(undefined) freeformInput.setData(undefined);
} }
feedback.setData(undefined) feedback.setData(undefined);
} }
export let selectedTags: TagsFilter = undefined
let mappings: Mapping[] = config?.mappings $: {
let searchTerm: UIEventSource<string> = new UIEventSource("") // Even though 'config' is not declared as a store, Svelte uses it as one to update the component
// We want to (re)-initialize whenever the 'tags' or 'config' change - but not when 'checkedConfig' changes
initialize($tags, config);
}
export let selectedTags: TagsFilter = undefined;
let mappings: Mapping[] = config?.mappings;
let searchTerm: UIEventSource<string> = new UIEventSource("");
$: { $: {
try { try {
@ -79,10 +117,10 @@
selectedMapping, selectedMapping,
checkedMappings, checkedMappings,
tags.data tags.data
) );
} catch (e) { } catch (e) {
console.error("Could not calculate changeSpecification:", e) console.error("Could not calculate changeSpecification:", e);
selectedTags = undefined selectedTags = undefined;
} }
} }
@ -91,53 +129,53 @@
config: TagRenderingConfig config: TagRenderingConfig
applied: TagsFilter applied: TagsFilter
} }
}>() }>();
function onSave() { function onSave() {
if (selectedTags === undefined) { if (selectedTags === undefined) {
return return;
} }
if (layer.source === null) { if (layer.source === null) {
/** /**
* This is a special, priviliged layer. * This is a special, priviliged layer.
* We simply apply the tags onto the records * We simply apply the tags onto the records
*/ */
const kv = selectedTags.asChange(tags.data) const kv = selectedTags.asChange(tags.data);
for (const { k, v } of kv) { for (const { k, v } of kv) {
if (v === undefined) { if (v === undefined) {
delete tags.data[k] delete tags.data[k];
} else { } else {
tags.data[k] = v tags.data[k] = v;
} }
} }
tags.ping() tags.ping();
return return;
} }
dispatch("saved", { config, applied: selectedTags }) dispatch("saved", { config, applied: selectedTags });
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, { const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
theme: state.layout.id, theme: state.layout.id,
changeType: "answer", changeType: "answer"
}) });
freeformInput.setData(undefined) freeformInput.setData(undefined);
selectedMapping = undefined selectedMapping = undefined;
selectedTags = undefined selectedTags = undefined;
change change
.CreateChangeDescriptions() .CreateChangeDescriptions()
.then((changes) => state.changes.applyChanges(changes)) .then((changes) => state.changes.applyChanges(changes))
.catch(console.error) .catch(console.error);
} }
let featureSwitchIsTesting = state.featureSwitchIsTesting let featureSwitchIsTesting = state.featureSwitchIsTesting;
let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging;
let showTags = state.userRelatedState.showTags let showTags = state.userRelatedState.showTags;
let numberOfCs = state.osmConnection.userDetails.data.csCount let numberOfCs = state.osmConnection.userDetails.data.csCount;
onDestroy( onDestroy(
state.osmConnection.userDetails.addCallbackAndRun((ud) => { state.osmConnection.userDetails.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount numberOfCs = ud.csCount;
}) })
) );
</script> </script>
{#if config.question !== undefined} {#if config.question !== undefined}