Merge branch 'master' into develop

This commit is contained in:
Pieter Vander Vennet 2023-08-09 14:18:10 +02:00
commit e1c831884a
16 changed files with 242 additions and 150 deletions

View file

@ -135,7 +135,6 @@
"de": "Kletterschuhe können hier ausgeliehen werden" "de": "Kletterschuhe können hier ausgeliehen werden"
}, },
"addExtraTags": [ "addExtraTags": [
"service:climbing_shoes:rental:fee=",
"service:climbing_shoes:rental:charge=" "service:climbing_shoes:rental:charge="
] ]
}, },

View file

@ -2154,20 +2154,23 @@
{ {
"id": "survey_date", "id": "survey_date",
"question": { "question": {
"en": "When was this object last surveyed?" "en": "When was this object last surveyed?",
"de": "Wann wurde dieses Objekt zuletzt geprüft?"
}, },
"freeform": { "freeform": {
"key": "survey:date", "key": "survey:date",
"type": "date" "type": "date"
}, },
"render": { "render": {
"en": "This object was last surveyed on <b>{survey:date}</b>" "en": "This object was last surveyed on <b>{survey:date}</b>",
"de": "Dieses Objekt wurde zuletzt geprüft am <b>{survey:date}</b>"
}, },
"mappings": [ "mappings": [
{ {
"if": "survey:date:={_now:date}", "if": "survey:date:={_now:date}",
"then": { "then": {
"en": "This object was last surveyed today" "en": "This object was last surveyed today",
"de": "Dieses Objekt wurde heute zuletzt geprüft"
} }
} }
] ]

View file

@ -39,7 +39,8 @@
"if": "__url_parameter_initialized:language=yes", "if": "__url_parameter_initialized:language=yes",
"icon": "./assets/layers/usersettings/translate_disabled.svg", "icon": "./assets/layers/usersettings/translate_disabled.svg",
"then": { "then": {
"en": "The language was set via an URL-parameter and cannot be set by the user.²" "en": "The language was set via an URL-parameter and cannot be set by the user.²",
"de": "Die Sprache wurde über einen URL-Parameter gesetzt und kann nicht vom Benutzer eingestellt werden.²"
} }
} }
] ]

View file

@ -231,12 +231,7 @@
} }
}, },
{ {
"if": { "if": "fee=no",
"and": [
"fee=no",
"charge="
]
},
"then": { "then": {
"en": "Can be used for free", "en": "Can be used for free",
"id": "Boleh digunakan tanpa bayaran", "id": "Boleh digunakan tanpa bayaran",
@ -1562,4 +1557,4 @@
] ]
}, },
"credits": "joost schouppe" "credits": "joost schouppe"
} }

View file

@ -14,7 +14,7 @@
"available": "Diese Gemeinschaft spricht {native}", "available": "Diese Gemeinschaft spricht {native}",
"intro": "Treten Sie mit anderen Menschen in Kontakt, um sie kennen zu lernen, von ihnen zu lernen, …", "intro": "Treten Sie mit anderen Menschen in Kontakt, um sie kennen zu lernen, von ihnen zu lernen, …",
"notAvailable": "Diese Gemeinschaft spricht nicht {native}", "notAvailable": "Diese Gemeinschaft spricht nicht {native}",
"title": "Index der Community" "title": "Mit anderen in Kontakt treten"
}, },
"delete": { "delete": {
"cancel": "Abbrechen", "cancel": "Abbrechen",

View file

@ -5163,13 +5163,13 @@
"hs-club-mate": { "hs-club-mate": {
"mappings": { "mappings": {
"0": { "0": {
"then": "In diesem Hackerspace gibt es Club Mate" "then": "In diesem Hackerspace gibt es Club-Mate"
}, },
"1": { "1": {
"then": "In diesem Hackerspace gibt es kein Club Mate" "then": "In diesem Hackerspace gibt es kein Club-Mate"
} }
}, },
"question": "Gibt es in diesem Hackerspace Club Mate?" "question": "Gibt es in diesem Hackerspace Club-Mate?"
}, },
"is_makerspace": { "is_makerspace": {
"mappings": { "mappings": {
@ -7318,6 +7318,15 @@
}, },
"question": "Ist das Rauchen in {title()} erlaubt?" "question": "Ist das Rauchen in {title()} erlaubt?"
}, },
"survey_date": {
"mappings": {
"0": {
"then": "Dieses Objekt wurde heute zuletzt geprüft"
}
},
"question": "Wann wurde dieses Objekt zuletzt geprüft?",
"render": "Dieses Objekt wurde zuletzt geprüft am <b>{survey:date}</b>"
},
"website": { "website": {
"question": "Wie lautet die Webseite von {title()}?" "question": "Wie lautet die Webseite von {title()}?"
}, },
@ -9406,6 +9415,13 @@
} }
} }
}, },
"language_picker": {
"mappings": {
"0": {
"then": "Die Sprache wurde über einen URL-Parameter gesetzt und kann nicht vom Benutzer eingestellt werden.²"
}
}
},
"mangrove-keys": { "mangrove-keys": {
"render": "<a href='data:application/json,{mangroveidentity}' download='mangrove_private_key_{_name}'>Laden Sie den privaten Schlüssel für Ihr Mangrove-Konto herunter</a> <p>Jeder, der diese Datei besitzt, kann mit Ihrer Identität Rezensionen vornehmen</p>" "render": "<a href='data:application/json,{mangroveidentity}' download='mangrove_private_key_{_name}'>Laden Sie den privaten Schlüssel für Ihr Mangrove-Konto herunter</a> <p>Jeder, der diese Datei besitzt, kann mit Ihrer Identität Rezensionen vornehmen</p>"
}, },

View file

@ -5163,13 +5163,13 @@
"hs-club-mate": { "hs-club-mate": {
"mappings": { "mappings": {
"0": { "0": {
"then": "This hackerspace serves club mate" "then": "This hackerspace serves Club-Mate"
}, },
"1": { "1": {
"then": "This hackerspace does not serve club mate" "then": "This hackerspace does not serve Club-Mate"
} }
}, },
"question": "Does this hackerspace serve Club Mate?" "question": "Does this hackerspace serve Club-Mate?"
}, },
"is_makerspace": { "is_makerspace": {
"mappings": { "mappings": {

View file

@ -4939,13 +4939,13 @@
"hs-club-mate": { "hs-club-mate": {
"mappings": { "mappings": {
"0": { "0": {
"then": "Deze hackerspace biedt clube-mate aan" "then": "Deze hackerspace biedt Club-Mate aan"
}, },
"1": { "1": {
"then": "Deze hackerspace biedt geen club-mate aan" "then": "Deze hackerspace biedt geen Club-Mate aan"
} }
}, },
"question": "Biedt deze hackerspace club-mate aan?" "question": "Biedt deze hackerspace Club-Mate aan?"
}, },
"is_makerspace": { "is_makerspace": {
"mappings": { "mappings": {

View file

@ -1,4 +1,7 @@
{ {
"advanced": {
"title": "Avanserte funksjoner"
},
"centerMessage": { "centerMessage": {
"loadingData": "Laster inn data …", "loadingData": "Laster inn data …",
"ready": "Ferdig", "ready": "Ferdig",

View file

@ -557,7 +557,7 @@ function MergeTranslation(source: any, target: any, language: string, context: s
if (context.endsWith(".tagRenderings")) { if (context.endsWith(".tagRenderings")) {
keyRemapping = new Map<string, string>() keyRemapping = new Map<string, string>()
for (const key in target) { for (const key in target) {
keyRemapping.set(target[key].id, key) keyRemapping.set(target[key].id ?? target[key].builtin, key)
} }
} }

View file

@ -1,21 +1,22 @@
import { DesugaringStep, Each, Fuse, On } from "./Conversion" import {DesugaringStep, Each, Fuse, On} from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson" import {LayerConfigJson} from "../Json/LayerConfigJson"
import LayerConfig from "../LayerConfig" import LayerConfig from "../LayerConfig"
import { Utils } from "../../../Utils" import {Utils} from "../../../Utils"
import Constants from "../../Constants" import Constants from "../../Constants"
import { Translation } from "../../../UI/i18n/Translation" import {Translation} from "../../../UI/i18n/Translation"
import { LayoutConfigJson } from "../Json/LayoutConfigJson" import {LayoutConfigJson} from "../Json/LayoutConfigJson"
import LayoutConfig from "../LayoutConfig" import LayoutConfig from "../LayoutConfig"
import { TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson"
import { TagUtils } from "../../../Logic/Tags/TagUtils" import {TagUtils} from "../../../Logic/Tags/TagUtils"
import { ExtractImages } from "./FixImages" import {ExtractImages} from "./FixImages"
import { And } from "../../../Logic/Tags/And" import {And} from "../../../Logic/Tags/And"
import Translations from "../../../UI/i18n/Translations" import Translations from "../../../UI/i18n/Translations"
import Svg from "../../../Svg" import Svg from "../../../Svg"
import FilterConfigJson from "../Json/FilterConfigJson" import FilterConfigJson from "../Json/FilterConfigJson"
import DeleteConfig from "../DeleteConfig" import DeleteConfig from "../DeleteConfig"
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson"
import Validators from "../../../UI/InputElement/Validators" import Validators from "../../../UI/InputElement/Validators"
import TagRenderingConfig from "../TagRenderingConfig";
class ValidateLanguageCompleteness extends DesugaringStep<any> { class ValidateLanguageCompleteness extends DesugaringStep<any> {
private readonly _languages: string[] private readonly _languages: string[]
@ -46,12 +47,12 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
.forEach((missing) => { .forEach((missing) => {
errors.push( errors.push(
context + context +
"A theme should be translation-complete for " + "A theme should be translation-complete for " +
neededLanguage + neededLanguage +
", but it lacks a translation for " + ", but it lacks a translation for " +
missing.context + missing.context +
".\n\tThe known translation is " + ".\n\tThe known translation is " +
missing.tr.textFor("en") missing.tr.textFor("en")
) )
}) })
} }
@ -85,7 +86,7 @@ export class DoesImageExist extends DesugaringStep<string> {
context: string context: string
): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } { ): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
if (this._ignore?.has(image)) { if (this._ignore?.has(image)) {
return { result: image } return {result: image}
} }
const errors = [] const errors = []
@ -93,22 +94,22 @@ export class DoesImageExist extends DesugaringStep<string> {
const information = [] const information = []
if (image.indexOf("{") >= 0) { if (image.indexOf("{") >= 0) {
information.push("Ignoring image with { in the path: " + image) information.push("Ignoring image with { in the path: " + image)
return { result: image } return {result: image}
} }
if (image === "assets/SocialImage.png") { if (image === "assets/SocialImage.png") {
return { result: image } return {result: image}
} }
if (image.match(/[a-z]*/)) { if (image.match(/[a-z]*/)) {
if (Svg.All[image + ".svg"] !== undefined) { if (Svg.All[image + ".svg"] !== undefined) {
// This is a builtin img, e.g. 'checkmark' or 'crosshair' // This is a builtin img, e.g. 'checkmark' or 'crosshair'
return { result: image } return {result: image}
} }
} }
if (image.startsWith("<") && image.endsWith(">")) { if (image.startsWith("<") && image.endsWith(">")) {
// This is probably HTML, you're on your own here // This is probably HTML, you're on your own here
return { result: image } return {result: image}
} }
if (!this._knownImagePaths.has(image)) { if (!this._knownImagePaths.has(image)) {
@ -177,15 +178,15 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
if (json["units"] !== undefined) { if (json["units"] !== undefined) {
errors.push( errors.push(
"The theme " + "The theme " +
json.id + json.id +
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) " " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
) )
} }
if (json["roamingRenderings"] !== undefined) { if (json["roamingRenderings"] !== undefined) {
errors.push( errors.push(
"Theme " + "Theme " +
json.id + json.id +
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead" " contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
) )
} }
} }
@ -197,10 +198,10 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
for (const remoteImage of remoteImages) { for (const remoteImage of remoteImages) {
errors.push( errors.push(
"Found a remote image: " + "Found a remote image: " +
remoteImage + remoteImage +
" in theme " + " in theme " +
json.id + json.id +
", please download it." ", please download it."
) )
} }
for (const image of images) { for (const image of images) {
@ -227,12 +228,12 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
if (theme.id !== filename) { if (theme.id !== filename) {
errors.push( errors.push(
"Theme ids should be the same as the name.json, but we got id: " + "Theme ids should be the same as the name.json, but we got id: " +
theme.id + theme.id +
" and filename " + " and filename " +
filename + filename +
" (" + " (" +
this._path + this._path +
")" ")"
) )
} }
this._validateImage.convertJoin( this._validateImage.convertJoin(
@ -312,7 +313,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } { ): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
const overrideAll = json.overrideAll const overrideAll = json.overrideAll
if (overrideAll === undefined) { if (overrideAll === undefined) {
return { result: json } return {result: json}
} }
const errors = [] const errors = []
@ -339,7 +340,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
} }
} }
return { result: json, errors } return {result: json, errors}
} }
} }
@ -383,6 +384,51 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
} }
} }
export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingConfigJson> {
constructor() {
super("The `if`-part in a mapping might set some keys. Those key are not allowed to be set in the `addExtraTags`, as this might result in conflicting values", [], "DetectConflictingAddExtraTags");
}
convert(json: TagRenderingConfigJson, context: string): {
result: TagRenderingConfigJson;
errors?: string[];
warnings?: string[];
information?: string[]
} {
if (!(json.mappings?.length > 0)) {
return {result: json}
}
const tagRendering = new TagRenderingConfig(json)
const errors = []
for (let i = 0; i < tagRendering.mappings.length; i++) {
const mapping = tagRendering.mappings[i];
if (!mapping.addExtraTags) {
continue
}
const keysInMapping = new Set(mapping.if.usedKeys())
const keysInAddExtraTags = mapping.addExtraTags.map(t => t.key)
const duplicateKeys = keysInAddExtraTags.filter(k => keysInMapping.has(k))
if (duplicateKeys.length > 0) {
errors.push(
"At " + context + ".mappings[" + i + "]: AddExtraTags overrides a key that is set in the `if`-clause of this mapping. Selecting this answer might thus first set one value (needed to match as answer) and then override it with a different value, resulting in an unsaveable question. The offending `addExtraTags` is " + duplicateKeys.join(", ")
)
}
}
return {
result: json,
errors
};
}
}
export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> { export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
private readonly _calculatedTagNames: string[] private readonly _calculatedTagNames: string[]
@ -449,7 +495,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
const errors = [] const errors = []
const warnings = [] const warnings = []
if (json.mappings === undefined || json.mappings.length === 0) { if (json.mappings === undefined || json.mappings.length === 0) {
return { result: json } return {result: json}
} }
const defaultProperties = {} const defaultProperties = {}
for (const calculatedTagName of this._calculatedTagNames) { for (const calculatedTagName of this._calculatedTagNames) {
@ -475,7 +521,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
} }
const keyValues = parsedConditions[i].asChange(defaultProperties) const keyValues = parsedConditions[i].asChange(defaultProperties)
const properties = {} const properties = {}
keyValues.forEach(({ k, v }) => { keyValues.forEach(({k, v}) => {
properties[k] = v properties[k] = v
}) })
for (let j = 0; j < i; j++) { for (let j = 0; j < i; j++) {
@ -492,10 +538,10 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
// The current mapping is shadowed! // The current mapping is shadowed!
errors.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j} and will thus never be shown: errors.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
The mapping ${parsedConditions[i].asHumanString( The mapping ${parsedConditions[i].asHumanString(
false, false,
false, false,
{} {}
)} is fully matched by a previous mapping (namely ${j}), which matches: )} is fully matched by a previous mapping (namely ${j}), which matches:
${parsedConditions[j].asHumanString(false, false, {})}. ${parsedConditions[j].asHumanString(false, false, {})}.
To fix this problem, you can try to: To fix this problem, you can try to:
@ -564,7 +610,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
const warnings: string[] = [] const warnings: string[] = []
const information: string[] = [] const information: string[] = []
if (json.mappings === undefined || json.mappings.length === 0) { if (json.mappings === undefined || json.mappings.length === 0) {
return { result: json } return {result: json}
} }
const ignoreToken = "ignore-image-in-then" const ignoreToken = "ignore-image-in-then"
for (let i = 0; i < json.mappings.length; i++) { for (let i = 0; i < json.mappings.length; i++) {
@ -626,17 +672,17 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
if (json["special"] !== undefined) { if (json["special"] !== undefined) {
errors.push( errors.push(
"At " + "At " +
context + context +
': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' ': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
) )
} }
if (json["group"]) { if (json["group"]) {
errors.push( errors.push(
"At " + "At " +
context + context +
': groups are deprecated, use `"label": ["' + ': groups are deprecated, use `"label": ["' +
json["group"] + json["group"] +
'"]` instead' '"]` instead'
) )
} }
const freeformType = json["freeform"]?.["type"] const freeformType = json["freeform"]?.["type"]
@ -669,6 +715,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
super( super(
"Various validation on tagRenderingConfigs", "Various validation on tagRenderingConfigs",
new DetectShadowedMappings(layerConfig), new DetectShadowedMappings(layerConfig),
new DetectConflictingAddExtraTags(),
new DetectMappingsWithImages(doesImageExist), new DetectMappingsWithImages(doesImageExist),
new MiscTagRenderingChecks(options) new MiscTagRenderingChecks(options)
) )
@ -711,9 +758,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (!Constants.priviliged_layers.find((x) => x == json.id)) { if (!Constants.priviliged_layers.find((x) => x == json.id)) {
errors.push( errors.push(
context + context +
": layer " + ": layer " +
json.id + json.id +
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer" " uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
) )
} }
} }
@ -722,13 +769,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (json.title === undefined && json.source !== "special:library") { if (json.title === undefined && json.source !== "special:library") {
errors.push( errors.push(
context + context +
": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." ": this layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error."
) )
} }
if (json.title === null) { if (json.title === null) {
information.push( information.push(
context + context +
": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." ": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
) )
} }
} }
@ -755,9 +802,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
console.log(json.tagRenderings) console.log(json.tagRenderings)
errors.push( errors.push(
"At " + "At " +
context + context +
": some tagrenderings have a duplicate id: " + ": some tagrenderings have a duplicate id: " +
duplicates.join(", ") duplicates.join(", ")
) )
} }
} }
@ -775,8 +822,8 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (json["overpassTags"] !== undefined) { if (json["overpassTags"] !== undefined) {
errors.push( errors.push(
"Layer " + "Layer " +
json.id + json.id +
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)' 'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)'
) )
} }
const forbiddenTopLevel = [ const forbiddenTopLevel = [
@ -794,18 +841,18 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (json[forbiddenKey] !== undefined) if (json[forbiddenKey] !== undefined)
errors.push( errors.push(
context + context +
": layer " + ": layer " +
json.id + json.id +
" still has a forbidden key " + " still has a forbidden key " +
forbiddenKey forbiddenKey
) )
} }
if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
errors.push( errors.push(
context + context +
": layer " + ": layer " +
json.id + json.id +
" contains an old 'hideUnderlayingFeaturesMinPercentage'" " contains an old 'hideUnderlayingFeaturesMinPercentage'"
) )
} }
@ -822,9 +869,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (this._path != undefined && this._path.indexOf(expected) < 0) { if (this._path != undefined && this._path.indexOf(expected) < 0) {
errors.push( errors.push(
"Layer is in an incorrect place. The path is " + "Layer is in an incorrect place. The path is " +
this._path + this._path +
", but expected " + ", but expected " +
expected expected
) )
} }
} }
@ -865,6 +912,13 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
} }
} }
if (json.filter) {
const r = new On("filter", new Each( new ValidateFilter())).convert(json, context)
warnings.push(...(r.warnings ?? []))
errors.push(...(r.errors ?? []))
information.push(...(r.information ?? []))
}
if (json.tagRenderings !== undefined) { if (json.tagRenderings !== undefined) {
const r = new On( const r = new On(
"tagRenderings", "tagRenderings",
@ -886,9 +940,9 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (hasCondition?.length > 0) { if (hasCondition?.length > 0) {
errors.push( errors.push(
"At " + "At " +
context + context +
":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" + ":\n One or more icons in the mapRenderings have a condition set. Don't do this, as this will result in an invisible but clickable element. Use extra filters in the source instead. The offending mapRenderings are:\n" +
JSON.stringify(hasCondition, null, " ") JSON.stringify(hasCondition, null, " ")
) )
} }
} }
@ -903,7 +957,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
const preset = json.presets[i] const preset = json.presets[i]
const tags: { k: string; v: string }[] = new And( const tags: { k: string; v: string }[] = new And(
preset.tags.map((t) => TagUtils.Tag(t)) preset.tags.map((t) => TagUtils.Tag(t))
).asChange({ id: "node/-1" }) ).asChange({id: "node/-1"})
const properties = {} const properties = {}
for (const tag of tags) { for (const tag of tags) {
properties[tag.k] = tag.v properties[tag.k] = tag.v
@ -912,12 +966,12 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
if (!doMatch) { if (!doMatch) {
errors.push( errors.push(
context + context +
".presets[" + ".presets[" +
i + i +
"]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + "]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
JSON.stringify(properties) + JSON.stringify(properties) +
"\n The required tags are: " + "\n The required tags are: " +
baseTags.asHumanString(false, false, {}) baseTags.asHumanString(false, false, {})
) )
} }
} }
@ -949,9 +1003,14 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
warnings?: string[] warnings?: string[]
information?: string[] information?: string[]
} { } {
if (typeof filter === "string") {
// Calling another filter, we skip
return {result: filter}
}
const errors = [] const errors = []
for (const option of filter.options) { for (const option of filter.options) {
for (let i = 0; i < option.fields.length; i++) {
for (let i = 0; i < option.fields?.length ?? 0; i++) {
const field = option.fields[i] const field = option.fields[i]
const type = field.type ?? "string" const type = field.type ?? "string"
if (Validators.availableTypes.find((t) => t === type) === undefined) { if (Validators.availableTypes.find((t) => t === type) === undefined) {
@ -962,7 +1021,7 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
} }
} }
} }
return { result: filter, errors } return {result: filter, errors}
} }
} }
@ -991,7 +1050,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
const warnings: string[] = [] const warnings: string[] = []
const information: string[] = [] const information: string[] = []
const { layers, themes } = json const {layers, themes} = json
const perOsmTag = new Map< const perOsmTag = new Map<
string, string,
{ {
@ -1027,7 +1086,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
return return
} }
let msg = "Possible duplicate filter: " + key let msg = "Possible duplicate filter: " + key
for (const { filter, layer, layout } of value) { for (const {filter, layer, layout} of value) {
let id = "" let id = ""
if (layout !== undefined) { if (layout !== undefined) {
id = layout.id + ":" id = layout.id + ":"

View file

@ -58,7 +58,7 @@ export default class TagRenderingConfig {
public readonly freeform?: { public readonly freeform?: {
readonly key: string readonly key: string
readonly type: string readonly type: ValidatorType
readonly placeholder: Translation readonly placeholder: Translation
readonly addExtraTags: UploadableTag[] readonly addExtraTags: UploadableTag[]
readonly inline: boolean readonly inline: boolean
@ -133,7 +133,17 @@ export default class TagRenderingConfig {
) { ) {
throw `Freeform.addExtraTags should be a list of strings - not a single string (at ${context})` throw `Freeform.addExtraTags should be a list of strings - not a single string (at ${context})`
} }
const type = json.freeform.type ?? "string" if (
json.freeform.type &&
Validators.availableTypes.indexOf(<any>json.freeform.type) < 0
) {
throw `At ${context}: invalid type, perhaps you meant ${Utils.sortedByLevenshteinDistance(
json.freeform.key,
<any>Validators.availableTypes,
(s) => <any>s
)}`
}
const type: ValidatorType = <any>json.freeform.type ?? "string"
let placeholder: Translation = Translations.T(json.freeform.placeholder) let placeholder: Translation = Translations.T(json.freeform.placeholder)
if (placeholder === undefined) { if (placeholder === undefined) {
@ -622,7 +632,7 @@ export default class TagRenderingConfig {
* *
* @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
* @param multiSelectedMapping (Only used if multiAnswer == true): all the mappings that must be applied. Set multiSelectedMapping[mappings.length] to use the freeform as well * @param multiSelectedMapping (Only used if multiAnswer == true): all the mappings that must be applied. Set multiSelectedMapping[mappings.length] to use the freeform as well
* @param currentProperties: The current properties of the object for which the question should be answered * @param currentProperties The current properties of the object for which the question should be answered
*/ */
public constructChangeSpecification( public constructChangeSpecification(
freeformValue: string | undefined, freeformValue: string | undefined,
@ -685,38 +695,42 @@ export default class TagRenderingConfig {
return undefined return undefined
} }
return and return and
} else { }
// Is at least one mapping shown in the answer?
const someMappingIsShown = this.mappings.some((m) => { // Is at least one mapping shown in the answer?
if (typeof m.hideInAnswer === "boolean") { const someMappingIsShown = this.mappings.some((m) => {
return !m.hideInAnswer if (typeof m.hideInAnswer === "boolean") {
} return !m.hideInAnswer
const isHidden = m.hideInAnswer.matchesProperties(currentProperties)
return !isHidden
})
// If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key
const useFreeform =
freeformValue !== undefined &&
(singleSelectedMapping === this.mappings.length || !someMappingIsShown)
if (useFreeform) {
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
} else if (singleSelectedMapping !== undefined) {
return new And([
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
} else {
console.warn("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
})
return undefined
} }
const isHidden = m.hideInAnswer.matchesProperties(currentProperties)
return !isHidden
})
// If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key
const useFreeform =
freeformValue !== undefined &&
(singleSelectedMapping === this.mappings.length ||
!someMappingIsShown ||
singleSelectedMapping === undefined)
if (useFreeform) {
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
} else if (singleSelectedMapping !== undefined) {
return new And([
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
} else {
console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
useFreeform,
})
return undefined
} }
} }

View file

@ -9,7 +9,7 @@
import { Unit } from "../../Models/Unit" import { Unit } from "../../Models/Unit"
import UnitInput from "../Popup/UnitInput.svelte" import UnitInput from "../Popup/UnitInput.svelte"
export let type: ValidatorType export let type: ValidatorType
export let feedback: UIEventSource<Translation> | undefined = undefined export let feedback: UIEventSource<Translation> | undefined = undefined
export let getCountry: () => string | undefined export let getCountry: () => string | undefined
export let placeholder: string | Translation | undefined export let placeholder: string | Translation | undefined
@ -63,6 +63,7 @@
} }
if (unit && isNaN(Number(v))) { if (unit && isNaN(Number(v))) {
console.debug("Not a number, but a unit is required")
value.setData(undefined) value.setData(undefined)
return return
} }

View file

@ -1,6 +1,6 @@
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement";
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation";
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations";
/** /**
* A 'TextFieldValidator' contains various methods to check and cleanup an entered value or to give feedback. * A 'TextFieldValidator' contains various methods to check and cleanup an entered value or to give feedback.
@ -16,13 +16,13 @@ export abstract class Validator {
/** /**
* What HTML-inputmode to use * What HTML-inputmode to use
*/ */
public readonly inputmode?: string public readonly inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
public readonly textArea: boolean public readonly textArea: boolean
constructor( constructor(
name: string, name: string,
explanation: string | BaseUIElement, explanation: string | BaseUIElement,
inputmode?: string, inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search',
textArea?: false | boolean textArea?: false | boolean
) { ) {
this.name = name this.name = name

View file

@ -1,11 +1,12 @@
import { Translation } from "../../i18n/Translation" import { Translation } from "../../i18n/Translation"
import Translations from "../../i18n/Translations" import Translations from "../../i18n/Translations"
import { Validator } from "../Validator" import { Validator } from "../Validator"
import { ValidatorType } from "../Validators";
export default class FloatValidator extends Validator { export default class FloatValidator extends Validator {
inputmode = "decimal" inputmode: "decimal" = "decimal"
constructor(name?: string, explanation?: string) { constructor(name?: ValidatorType, explanation?: string) {
super(name ?? "float", explanation ?? "A decimal number", "decimal") super(name ?? "float", explanation ?? "A decimal number", "decimal")
} }

View file

@ -70,7 +70,7 @@
export let selectedTags: TagsFilter = undefined export let selectedTags: TagsFilter = undefined
let mappings: Mapping[] = config?.mappings let mappings: Mapping[] = config?.mappings
let searchTerm: Store<string> = new UIEventSource("") let searchTerm: UIEventSource<string> = new UIEventSource("")
$: { $: {
try { try {