diff --git a/Logic/Tags/Tag.ts b/Logic/Tags/Tag.ts index 92f470b4fa..803b793908 100644 --- a/Logic/Tags/Tag.ts +++ b/Logic/Tags/Tag.ts @@ -13,7 +13,7 @@ export class Tag extends TagsFilter { throw "Invalid key: undefined or empty"; } if (value === undefined) { - throw "Invalid value: value is undefined"; + throw `Invalid value while constructing a Tag with key '${key}': value is undefined`; } if (value === "*") { console.warn(`Got suspicious tag ${key}=* ; did you mean ${key}~* ?`) diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 656d880de1..0328a91067 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -20,7 +20,7 @@ export interface Mapping { readonly ifnot?: TagsFilter, readonly then: TypedTranslation, readonly icon: string, - readonly iconClass: string + readonly iconClass: string | "small" | "medium" | "large" | "small-height" | "medium-height" | "large-height", readonly hideInAnswer: boolean | TagsFilter readonly addExtraTags: Tag[], readonly searchTerms?: Record diff --git a/UI/Input/SearchableMappingsSelector.ts b/UI/Input/SearchableMappingsSelector.ts index 191230ece2..8a414a879c 100644 --- a/UI/Input/SearchableMappingsSelector.ts +++ b/UI/Input/SearchableMappingsSelector.ts @@ -57,7 +57,7 @@ class SelfHidingToggle extends UIElement implements InputElement { return true } s = s?.trim()?.toLowerCase() - return searchTerms[Locale.language.data].some(t => t.indexOf(s) >= 0); + return searchTerms[Locale.language.data]?.some(t => t.indexOf(s) >= 0) ?? false; }, [selected, Locale.language]) const self = this; @@ -121,10 +121,15 @@ class SelfHidingToggle extends UIElement implements InputElement { * A searchfield can be used to filter the values */ export class SearchablePillsSelector extends Combine implements InputElement { - private selectedElements: UIEventSource; + private readonly selectedElements: UIEventSource; public readonly someMatchFound: Store; + /** + * + * @param values + * @param options + */ constructor( values: { show: BaseUIElement, value: T, mainTerm: Record, searchTerms?: Record }[], options?: { @@ -188,7 +193,6 @@ export class SearchablePillsSelector extends Combine implements InputElement< }; }) - let somethingShown: Store if (options.selectIfSingle) { let forcedSelection : { value: T, show: SelfHidingToggle } = undefined @@ -203,15 +207,15 @@ export class SearchablePillsSelector extends Combine implements InputElement< } } if (totalShown == 1) { - if (this.selectedElements.data.indexOf(lastShownValue.value) < 0) { - this.selectedElements.setData([lastShownValue.value]) + if (selectedElements.data?.indexOf(lastShownValue.value) < 0) { + selectedElements.setData([lastShownValue.value]) lastShownValue.show.forceSelected.setData(true) forcedSelection = lastShownValue } } else if (forcedSelection != undefined) { forcedSelection?.show?.forceSelected?.setData(false) forcedSelection = undefined; - this.selectedElements.setData([]) + selectedElements.setData([]) } return totalShown > 0 diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index ed00ceb826..60dab8ab9f 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -22,7 +22,7 @@ import BaseUIElement from "../BaseUIElement"; import {DropDown} from "../Input/DropDown"; import InputElementWrapper from "../Input/InputElementWrapper"; import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"; -import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"; +import TagRenderingConfig, {Mapping} from "../../Models/ThemeConfig/TagRenderingConfig"; import {Unit} from "../../Models/Unit"; import VariableInputElement from "../Input/VariableInputElement"; import Toggle from "../Input/Toggle"; @@ -32,6 +32,7 @@ import Title from "../Base/Title"; import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import {GeoOperations} from "../../Logic/GeoOperations"; import {SearchablePillsSelector} from "../Input/SearchableMappingsSelector"; +import {OsmTags} from "../../Models/OsmFeature"; /** * Shows the question element. @@ -39,7 +40,7 @@ import {SearchablePillsSelector} from "../Input/SearchableMappingsSelector"; */ export default class TagRenderingQuestion extends Combine { - constructor(tags: UIEventSource & {id: string}>, + constructor(tags: UIEventSource & { id: string }>, configuration: TagRenderingConfig, state?: FeaturePipelineState, options?: { @@ -53,7 +54,7 @@ export default class TagRenderingQuestion extends Combine { const applicableMappingsSrc = Stores.ListStabilized(tags.map(tags => { - const applicableMappings: { if: TagsFilter, icon?: string, then: TypedTranslation, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = [] + const applicableMappings: Mapping[] = [] for (const mapping of configuration.mappings ?? []) { if (mapping.hideInAnswer === true) { continue @@ -142,7 +143,7 @@ export default class TagRenderingQuestion extends Combine { private static GenerateInputElement( state: FeaturePipelineState, configuration: TagRenderingConfig, - applicableMappings: { if: TagsFilter, then: TypedTranslation, icon?: string, ifnot?: TagsFilter, addExtraTags: Tag[], searchTerms?: Record }[], + applicableMappings: Mapping[], applicableUnit: Unit, tagsSource: UIEventSource, feedback: UIEventSource @@ -231,15 +232,84 @@ export default class TagRenderingQuestion extends Combine { } + /** + * + * // Should return the search as freeform value + * const source = new UIEventSource({id: "1234"}) + * const tr = new TagRenderingConfig({ + * id:"test", + * render:"The value is {key}", + * freeform: { + * key:"key" + * }, + * + * mappings: [ + * { + * if:"x=y", + * then:"z", + * searchTerms: { + * "en" : ["z"] + * } + * } + * ] + * }, "test"); + * const selector = TagRenderingQuestion.GenerateSearchableSelector( + * undefined, + * tr, + * tr.mappings, + * source, + * { + * search: new UIEventSource("value") + * } + * ); + * selector.GetValue().data // => new And([new Tag("key","value")]) + * + * // Should return the search as freeform value, even if a previous search matched + * const source = new UIEventSource({id: "1234"}) + * const search = new UIEventSource("") + * const tr = new TagRenderingConfig({ + * id:"test", + * render:"The value is {key}", + * freeform: { + * key:"key" + * }, + * + * mappings: [ + * { + * if:"x=y", + * then:"z", + * searchTerms: { + * "en" : ["z"] + * } + * } + * ] + * }, "test"); + * const selector = TagRenderingQuestion.GenerateSearchableSelector( + * undefined, + * tr, + * tr.mappings, + * source, + * { + * search + * } + * ); + * search.setData("z") + * search.setData("zx") + * selector.GetValue().data // => new And([new Tag("key","zx")]) + */ private static GenerateSearchableSelector( state: FeaturePipelineState, configuration: TagRenderingConfig, - applicableMappings: { if: TagsFilter; ifnot?: TagsFilter, then: TypedTranslation; icon?: string; iconClass?: string, addExtraTags: Tag[], searchTerms?: Record }[], tagsSource: UIEventSource): InputElement { + applicableMappings: Mapping[], + tagsSource: UIEventSource, + options?: { + search: UIEventSource + }): InputElement { const values: { show: BaseUIElement, value: number, mainTerm: Record, searchTerms?: Record }[] = [] for (let i = 0; i < applicableMappings.length; i++) { const mapping = applicableMappings[i]; const tr = mapping.then.Subs(tagsSource.data) - const patchedMapping = <{ iconClass: "small-height", then: TypedTranslation }>{ + const patchedMapping = { ...mapping, iconClass: `small-height`, icon: mapping.icon ?? "./assets/svg/none.svg" @@ -253,7 +323,7 @@ export default class TagRenderingQuestion extends Combine { }) } - const searchValue: UIEventSource = new UIEventSource(undefined) + const searchValue: UIEventSource = options?.search ?? new UIEventSource(undefined) const ff = configuration.freeform let onEmpty: BaseUIElement = undefined if (ff !== undefined) { @@ -266,8 +336,14 @@ export default class TagRenderingQuestion extends Combine { mode: configuration.multiAnswer ? "select-many" : "select-one", searchValue, onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"), - searchAreaClass: classes + searchAreaClass: classes, }) + const fallbackTag = searchValue.map(s => { + if (s === undefined || ff?.key === undefined) { + return undefined + } + return new Tag(ff.key, s) + }); return new InputElementMap(presetSearch, (x0, x1) => { if (x0 == x1) { @@ -288,7 +364,7 @@ export default class TagRenderingQuestion extends Combine { }, (selected) => { if (ff !== undefined && searchValue.data?.length > 0 && !presetSearch.someMatchFound.data) { - const t = new Tag(ff.key, searchValue.data) + const t = fallbackTag.data; if (ff.addExtraTags) { return new And([t, ...ff.addExtraTags]) } @@ -436,13 +512,7 @@ export default class TagRenderingQuestion extends Combine { private static GenerateMappingElement( state, tagsSource: UIEventSource, - mapping: { - if: TagsFilter, - then: Translation, - addExtraTags: Tag[], - icon?: string, - iconClass?: "small" | "medium" | "large" | "small-height" - }, ifNot?: TagsFilter[]): InputElement { + mapping: Mapping, ifNot?: TagsFilter[]): InputElement { let tagging: TagsFilter = mapping.if; if (ifNot !== undefined) { @@ -459,11 +529,7 @@ export default class TagRenderingQuestion extends Combine { (t0, t1) => t1.shadows(t0)); } - private static GenerateMappingContent(mapping: { - then: Translation, - icon?: string, - iconClass?: "small" | "medium" | "large" | "small-height" | "medium-height" | "large-height" - }, tagsSource: UIEventSource, state: FeaturePipelineState): BaseUIElement { + private static GenerateMappingContent(mapping: Mapping, tagsSource: UIEventSource, state: FeaturePipelineState): BaseUIElement { const text = new SubstitutedTranslation(mapping.then, tagsSource, state) if (mapping.icon === undefined) { return text; diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index 27159a4506..0e7d24f8cd 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -140,7 +140,7 @@ }, "render": { "en": "There are {capacity:disabled} disabled parking spots", - "nl": "Er zijn capacity:disabled} parkeerplaatsen voor gehandicapten" + "nl": "Er zijn {capacity:disabled} parkeerplaatsen voor gehandicapten" } }, { diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index ab3cce9cdf..60452cb5b3 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -1,19 +1,13 @@ { "id": "mapcomplete-changes", "title": { - "en": "Changes made with MapComplete", - "nl": "Wijzigingen gemaakt met MapComplete", - "de": "Mit MapComplete vorgenommene Änderungen" + "en": "Changes made with MapComplete" }, "shortDescription": { - "en": "Shows changes made by MapComplete", - "nl": "Toont wijzigingen gemaakt met MapComplete", - "de": "Zeigt die mit MapComplete vorgenommenen Änderungen" + "en": "Shows changes made by MapComplete" }, "description": { - "en": "This maps shows all the changes made with MapComplete", - "nl": "Deze kaart toont alle wijzigingen die met MapComplete werden gemaakt", - "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen" + "en": "This maps shows all the changes made with MapComplete" }, "maintainer": "", "icon": "./assets/svg/logo.svg", @@ -28,8 +22,7 @@ { "id": "mapcomplete-changes", "name": { - "en": "Changeset centers", - "de": "Zentrum der Änderungssätze" + "en": "Changeset centers" }, "minzoom": 0, "source": { @@ -43,47 +36,35 @@ ], "title": { "render": { - "en": "Changeset for {theme}", - "nl": "Wijzigingset voor {theme}", - "de": "Änderungssatz für {theme}" + "en": "Changeset for {theme}" } }, "description": { - "en": "Shows all MapComplete changes", - "nl": "Toont alle wijzigingen met MapComplete", - "de": "Zeigt alle MapComplete Änderungen" + "en": "Shows all MapComplete changes" }, "tagRenderings": [ { "id": "render_id", "render": { - "en": "Changeset {id}", - "nl": "Wijzigingset {id}", - "de": "Änderungssatz {id}" + "en": "Changeset {id}" } }, { "id": "contributor", "render": { - "en": "Change made by {_last_edit:contributor}", - "nl": "Wijziging gemaakt door {_last_edit:contributor}", - "de": "Geändert von {_last_edit:contributor}" + "en": "Change made by {_last_edit:contributor}" } }, { "id": "theme", "render": { - "en": "Change with theme {theme}", - "nl": "Wijziging met thema {theme}", - "de": "Änderung mit Thema {theme}" + "en": "Change with theme {theme}" }, "mappings": [ { "if": "theme~http.*", "then": { - "en": "Change with unofficial theme {theme}", - "nl": "Wijziging met officieus thema {theme}", - "de": "Änderung mit inoffiziellem Thema {theme}" + "en": "Change with unofficial theme {theme}" } } ] @@ -379,9 +360,7 @@ } ], "question": { - "en": "Themename contains {search}", - "nl": "Themanaam bevat {search}", - "de": "Themenname enthält {search}" + "en": "Themename contains {search}" } } ] @@ -397,9 +376,7 @@ } ], "question": { - "en": "Made by contributor {search}", - "nl": "Gemaakt door bijdrager {search}", - "de": "Erstellt von {search}" + "en": "Made by contributor {search}" } } ] @@ -415,9 +392,7 @@ } ], "question": { - "en": "Not made by contributor {search}", - "nl": "Niet gemaakt door bijdrager {search}", - "de": "Nicht erstellt von {search}" + "en": "Not made by contributor {search}" } } ] @@ -432,9 +407,7 @@ { "id": "link_to_more", "render": { - "en": "More statistics can be found here", - "nl": "Meer statistieken kunnen hier gevonden worden", - "de": "Weitere Statistiken finden Sie hier" + "en": "More statistics can be found here" } }, { diff --git a/langs/layers/nl.json b/langs/layers/nl.json index cfba0ceeb3..11d154dcd3 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -6493,4 +6493,4 @@ } } } -} +} \ No newline at end of file