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.
*
* @see MatchesMultiAnswer to do the reverse
*
* E.g:
*
* const tag = TagUtils.ParseUploadableTag({"and": [

View file

@ -243,7 +243,10 @@ export default class TagRenderingConfig {
if (txt === "") {
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
}
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
*
* 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 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">
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Tr from "../../Base/Tr.svelte"
import type { Feature } from "geojson"
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig"
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import FreeformInput from "./FreeformInput.svelte"
import Translations from "../../i18n/Translations.js"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import { createEventDispatcher, onDestroy } from "svelte"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import SpecialTranslation from "./SpecialTranslation.svelte"
import TagHint from "../TagHint.svelte"
import LoginToggle from "../../Base/LoginToggle.svelte"
import SubtleButton from "../../Base/SubtleButton.svelte"
import Loading from "../../Base/Loading.svelte"
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte"
import { Translation } from "../../i18n/Translation"
import Constants from "../../../Models/Constants"
import { Unit } from "../../../Models/Unit"
import UserRelatedState from "../../../Logic/State/UserRelatedState"
import { twJoin } from "tailwind-merge"
import { UIEventSource } from "../../../Logic/UIEventSource";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import Tr from "../../Base/Tr.svelte";
import type { Feature } from "geojson";
import type { Mapping } from "../../../Models/ThemeConfig/TagRenderingConfig";
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig";
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher, onDestroy } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import SpecialTranslation from "./SpecialTranslation.svelte";
import TagHint from "../TagHint.svelte";
import LoginToggle from "../../Base/LoginToggle.svelte";
import SubtleButton from "../../Base/SubtleButton.svelte";
import Loading from "../../Base/Loading.svelte";
import TagRenderingMappingInput from "./TagRenderingMappingInput.svelte";
import { Translation } from "../../i18n/Translation";
import Constants from "../../../Models/Constants";
import { Unit } from "../../../Models/Unit";
import UserRelatedState from "../../../Logic/State/UserRelatedState";
import { twJoin } from "tailwind-merge";
import { TagUtils } from "../../../Logic/Tags/TagUtils";
export let config: TagRenderingConfig
export let tags: UIEventSource<Record<string, string>>
export let selectedElement: Feature
export let state: SpecialVisualizationState
export let layer: LayerConfig
export let config: TagRenderingConfig;
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
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
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
let selectedMapping: number = undefined
let checkedMappings: boolean[]
$: {
let tgs = $tags
mappings = config.mappings?.filter((m) => {
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key]);
let selectedMapping: number = undefined;
let checkedMappings: boolean[];
/**
* Prepares and fills the checkedMappings
*/
function initialize(tgs: Record<string, string>, confg: TagRenderingConfig) {
mappings = confg.mappings?.filter((m) => {
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
unit = layer.units.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
unit = layer.units.find((unit) => unit.appliesToKeys.has(confg.freeform?.key));
if (
config.mappings?.length > 0 &&
(checkedMappings === undefined || checkedMappings?.length + 1 < config.mappings.length)
confg.mappings?.length > 0 &&
(checkedMappings === undefined || checkedMappings?.length < confg.mappings.length + (confg.freeform ? 1 : 0))
) {
const seenFreeforms = [];
TagUtils.FlattenMultiAnswer()
checkedMappings = [
...config.mappings.map((_) => false),
false /*One element extra in case a freeform value is added*/,
]
...confg.mappings.map((mapping) => {
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) {
if (!config.multiAnswer) {
// Somehow, setting multianswer freeform values is broken if this is not set
freeformInput.setData(tgs[config.freeform.key])
console.log("Inited 'checkMappings' to", checkedMappings);
if (confg.freeform?.key) {
if (!confg.multiAnswer) {
// Somehow, setting multi-answer freeform values is broken if this is not set
freeformInput.setData(tgs[confg.freeform.key]);
}
} 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 {
@ -79,10 +117,10 @@
selectedMapping,
checkedMappings,
tags.data
)
);
} catch (e) {
console.error("Could not calculate changeSpecification:", e)
selectedTags = undefined
console.error("Could not calculate changeSpecification:", e);
selectedTags = undefined;
}
}
@ -91,53 +129,53 @@
config: TagRenderingConfig
applied: TagsFilter
}
}>()
}>();
function onSave() {
if (selectedTags === undefined) {
return
return;
}
if (layer.source === null) {
/**
* This is a special, priviliged layer.
* 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) {
if (v === undefined) {
delete tags.data[k]
delete tags.data[k];
} else {
tags.data[k] = v
tags.data[k] = v;
}
}
tags.ping()
return
tags.ping();
return;
}
dispatch("saved", { config, applied: selectedTags })
dispatch("saved", { config, applied: selectedTags });
const change = new ChangeTagAction(tags.data.id, selectedTags, tags.data, {
theme: state.layout.id,
changeType: "answer",
})
freeformInput.setData(undefined)
selectedMapping = undefined
selectedTags = undefined
changeType: "answer"
});
freeformInput.setData(undefined);
selectedMapping = undefined;
selectedTags = undefined;
change
.CreateChangeDescriptions()
.then((changes) => state.changes.applyChanges(changes))
.catch(console.error)
.catch(console.error);
}
let featureSwitchIsTesting = state.featureSwitchIsTesting
let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging
let showTags = state.userRelatedState.showTags
let numberOfCs = state.osmConnection.userDetails.data.csCount
let featureSwitchIsTesting = state.featureSwitchIsTesting;
let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging;
let showTags = state.userRelatedState.showTags;
let numberOfCs = state.osmConnection.userDetails.data.csCount;
onDestroy(
state.osmConnection.userDetails.addCallbackAndRun((ud) => {
numberOfCs = ud.csCount
numberOfCs = ud.csCount;
})
)
);
</script>
{#if config.question !== undefined}