Fix ability to add a freeform tag when using the searchableMappings element, add test

This commit is contained in:
pietervdvn 2022-07-13 23:05:50 +02:00
parent f805178b9b
commit 9327d96550
7 changed files with 115 additions and 72 deletions

View file

@ -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}~* ?`)

View file

@ -20,7 +20,7 @@ export interface Mapping {
readonly ifnot?: TagsFilter,
readonly then: TypedTranslation<object>,
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<string, string[]>

View file

@ -57,7 +57,7 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
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<boolean> {
* A searchfield can be used to filter the values
*/
export class SearchablePillsSelector<T> extends Combine implements InputElement<T[]> {
private selectedElements: UIEventSource<T[]>;
private readonly selectedElements: UIEventSource<T[]>;
public readonly someMatchFound: Store<boolean>;
/**
*
* @param values
* @param options
*/
constructor(
values: { show: BaseUIElement, value: T, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[],
options?: {
@ -188,7 +193,6 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
};
})
let somethingShown: Store<boolean>
if (options.selectIfSingle) {
let forcedSelection : { value: T, show: SelfHidingToggle } = undefined
@ -203,15 +207,15 @@ export class SearchablePillsSelector<T> 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

View file

@ -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<Record<string, string> & {id: string}>,
constructor(tags: UIEventSource<Record<string, string> & { 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<object>, 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<object>, icon?: string, ifnot?: TagsFilter, addExtraTags: Tag[], searchTerms?: Record<string, string[]> }[],
applicableMappings: Mapping[],
applicableUnit: Unit,
tagsSource: UIEventSource<any>,
feedback: UIEventSource<Translation>
@ -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<string>("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<string>("")
* 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<object>; icon?: string; iconClass?: string, addExtraTags: Tag[], searchTerms?: Record<string, string[]> }[], tagsSource: UIEventSource<any>): InputElement<TagsFilter> {
applicableMappings: Mapping[],
tagsSource: UIEventSource<OsmTags>,
options?: {
search: UIEventSource<string>
}): InputElement<TagsFilter> {
const values: { show: BaseUIElement, value: number, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[] = []
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<object> }>{
const patchedMapping = <Mapping>{
...mapping,
iconClass: `small-height`,
icon: mapping.icon ?? "./assets/svg/none.svg"
@ -253,7 +323,7 @@ export default class TagRenderingQuestion extends Combine {
})
}
const searchValue: UIEventSource<string> = new UIEventSource<string>(undefined)
const searchValue: UIEventSource<string> = options?.search ?? new UIEventSource<string>(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<number[], And>(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<any>,
mapping: {
if: TagsFilter,
then: Translation,
addExtraTags: Tag[],
icon?: string,
iconClass?: "small" | "medium" | "large" | "small-height"
}, ifNot?: TagsFilter[]): InputElement<TagsFilter> {
mapping: Mapping, ifNot?: TagsFilter[]): InputElement<TagsFilter> {
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<any>, state: FeaturePipelineState): BaseUIElement {
private static GenerateMappingContent(mapping: Mapping, tagsSource: UIEventSource<any>, state: FeaturePipelineState): BaseUIElement {
const text = new SubstitutedTranslation(mapping.then, tagsSource, state)
if (mapping.icon === undefined) {
return text;

View file

@ -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"
}
},
{

View file

@ -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 <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
"nl": "Wijzigingset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
"de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
}
},
{
"id": "contributor",
"render": {
"en": "Change made by <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>",
"nl": "Wijziging gemaakt door <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>",
"de": "Geändert von <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>"
"en": "Change made by <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>"
}
},
{
"id": "theme",
"render": {
"en": "Change with theme <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
"nl": "Wijziging met thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
"de": "Änderung mit Thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
"en": "Change with theme <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
},
"mappings": [
{
"if": "theme~http.*",
"then": {
"en": "Change with <b>unofficial</b> theme <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>",
"nl": "Wijziging met <b>officieus</b> thema <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>",
"de": "Änderung mit <b>inoffiziellem</b> Thema <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>"
"en": "Change with <b>unofficial</b> theme <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>"
}
}
]
@ -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": "<b>Not</b> made by contributor {search}",
"nl": "<b>Niet</b> gemaakt door bijdrager {search}",
"de": "<b>Nicht</b> erstellt von {search}"
"en": "<b>Not</b> made by contributor {search}"
}
}
]
@ -432,9 +407,7 @@
{
"id": "link_to_more",
"render": {
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
"nl": "Meer statistieken kunnen <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a> gevonden worden",
"de": "Weitere Statistiken finden Sie <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>"
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>"
}
},
{

View file

@ -6493,4 +6493,4 @@
}
}
}
}
}