forked from MapComplete/MapComplete
parent
d1aa751e18
commit
382f96596e
3 changed files with 132 additions and 78 deletions
|
@ -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": [
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue