forked from MapComplete/MapComplete
		
	Add various improvements and fixes to studio, should fix #2055
This commit is contained in:
		
							parent
							
								
									b19d9ef077
								
							
						
					
					
						commit
						d1ec9a43fc
					
				
					 19 changed files with 532 additions and 419 deletions
				
			
		| 
						 | 
				
			
			@ -73,15 +73,20 @@ export abstract class DesugaringStep<T> extends Conversion<T, T> {}
 | 
			
		|||
export class Pipe<TIn, TInter, TOut> extends Conversion<TIn, TOut> {
 | 
			
		||||
    private readonly _step0: Conversion<TIn, TInter>
 | 
			
		||||
    private readonly _step1: Conversion<TInter, TOut>
 | 
			
		||||
    private readonly _failfast: boolean
 | 
			
		||||
 | 
			
		||||
    constructor(step0: Conversion<TIn, TInter>, step1: Conversion<TInter, TOut>) {
 | 
			
		||||
    constructor(step0: Conversion<TIn, TInter>, step1: Conversion<TInter, TOut>, failfast = false) {
 | 
			
		||||
        super("Merges two steps with different types", [], `Pipe(${step0.name}, ${step1.name})`)
 | 
			
		||||
        this._step0 = step0
 | 
			
		||||
        this._step1 = step1
 | 
			
		||||
        this._failfast = failfast
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convert(json: TIn, context: ConversionContext): TOut {
 | 
			
		||||
        const r0 = this._step0.convert(json, context.inOperation(this._step0.name))
 | 
			
		||||
        if(context.hasErrors() && this._failfast){
 | 
			
		||||
            return undefined
 | 
			
		||||
        }
 | 
			
		||||
        return this._step1.convert(r0, context.inOperation(this._step1.name))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ import DependencyCalculator from "../DependencyCalculator"
 | 
			
		|||
import { AddContextToTranslations } from "./AddContextToTranslations"
 | 
			
		||||
import ValidationUtils from "./ValidationUtils"
 | 
			
		||||
import { ConversionContext } from "./ConversionContext"
 | 
			
		||||
import { PrevalidateTheme } from "./Validation"
 | 
			
		||||
 | 
			
		||||
class SubstituteLayer extends Conversion<string | LayerConfigJson, LayerConfigJson[]> {
 | 
			
		||||
    private readonly _state: DesugaringContext
 | 
			
		||||
| 
						 | 
				
			
			@ -664,7 +665,6 @@ export class PrepareTheme extends Fuse<LayoutConfigJson> {
 | 
			
		|||
    ) {
 | 
			
		||||
        super(
 | 
			
		||||
            "Fully prepares and expands a theme",
 | 
			
		||||
 | 
			
		||||
            new AddContextToTranslationsInLayout(),
 | 
			
		||||
            new PreparePersonalTheme(state),
 | 
			
		||||
            new WarnForUnsubstitutedLayersInTheme(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
 | 
			
		|||
        super(
 | 
			
		||||
            "Checks that the given object is fully translated in the specified languages",
 | 
			
		||||
            [],
 | 
			
		||||
            "ValidateLanguageCompleteness"
 | 
			
		||||
            "ValidateLanguageCompleteness",
 | 
			
		||||
        )
 | 
			
		||||
        this._languages = languages ?? ["en"]
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -50,18 +50,18 @@ class ValidateLanguageCompleteness extends DesugaringStep<LayoutConfig> {
 | 
			
		|||
                .filter(
 | 
			
		||||
                    (t) =>
 | 
			
		||||
                        t.tr.translations[neededLanguage] === undefined &&
 | 
			
		||||
                        t.tr.translations["*"] === undefined
 | 
			
		||||
                        t.tr.translations["*"] === undefined,
 | 
			
		||||
                )
 | 
			
		||||
                .forEach((missing) => {
 | 
			
		||||
                    context
 | 
			
		||||
                        .enter(missing.context.split("."))
 | 
			
		||||
                        .err(
 | 
			
		||||
                            `The theme ${obj.id} should be translation-complete for ` +
 | 
			
		||||
                                neededLanguage +
 | 
			
		||||
                                ", but it lacks a translation for " +
 | 
			
		||||
                                missing.context +
 | 
			
		||||
                                ".\n\tThe known translation is " +
 | 
			
		||||
                                missing.tr.textFor("en")
 | 
			
		||||
                            neededLanguage +
 | 
			
		||||
                            ", but it lacks a translation for " +
 | 
			
		||||
                            missing.context +
 | 
			
		||||
                            ".\n\tThe known translation is " +
 | 
			
		||||
                            missing.tr.textFor("en"),
 | 
			
		||||
                        )
 | 
			
		||||
                })
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +78,7 @@ export class DoesImageExist extends DesugaringStep<string> {
 | 
			
		|||
    constructor(
 | 
			
		||||
        knownImagePaths: Set<string>,
 | 
			
		||||
        checkExistsSync: (path: string) => boolean = undefined,
 | 
			
		||||
        ignore?: Set<string>
 | 
			
		||||
        ignore?: Set<string>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super("Checks if an image exists", [], "DoesImageExist")
 | 
			
		||||
        this._ignore = ignore
 | 
			
		||||
| 
						 | 
				
			
			@ -114,15 +114,15 @@ export class DoesImageExist extends DesugaringStep<string> {
 | 
			
		|||
        if (!this._knownImagePaths.has(image)) {
 | 
			
		||||
            if (this.doesPathExist === undefined) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    `Image with path ${image} not found or not attributed; it is used in ${context}`
 | 
			
		||||
                    `Image with path ${image} not found or not attributed; it is used in ${context}`,
 | 
			
		||||
                )
 | 
			
		||||
            } else if (!this.doesPathExist(image)) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    `Image with path ${image} does not exist.\n     Check for typo's and missing directories in the path.`
 | 
			
		||||
                    `Image with path ${image} does not exist.\n     Check for typo's and missing directories in the path.`,
 | 
			
		||||
                )
 | 
			
		||||
            } else {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`
 | 
			
		||||
                    `Image with path ${image} is not attributed (but it exists); execute 'npm run query:licenses' to add the license information and/or run 'npm run generate:licenses' to compile all the license info`,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -146,7 +146,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
        doesImageExist: DoesImageExist,
 | 
			
		||||
        path: string,
 | 
			
		||||
        isBuiltin: boolean,
 | 
			
		||||
        sharedTagRenderings?: Set<string>
 | 
			
		||||
        sharedTagRenderings?: Set<string>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme")
 | 
			
		||||
        this._validateImage = doesImageExist
 | 
			
		||||
| 
						 | 
				
			
			@ -165,15 +165,15 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
                if (json["units"] !== undefined) {
 | 
			
		||||
                    context.err(
 | 
			
		||||
                        "The theme " +
 | 
			
		||||
                            json.id +
 | 
			
		||||
                            " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
 | 
			
		||||
                        json.id +
 | 
			
		||||
                        " has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) ",
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                if (json["roamingRenderings"] !== undefined) {
 | 
			
		||||
                    context.err(
 | 
			
		||||
                        "Theme " +
 | 
			
		||||
                            json.id +
 | 
			
		||||
                            " contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
 | 
			
		||||
                        json.id +
 | 
			
		||||
                        " contains an old 'roamingRenderings'. Use an 'overrideAll' instead",
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -191,10 +191,10 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
            for (const remoteImage of remoteImages) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    "Found a remote image: " +
 | 
			
		||||
                        remoteImage.path +
 | 
			
		||||
                        " in theme " +
 | 
			
		||||
                        json.id +
 | 
			
		||||
                        ", please download it."
 | 
			
		||||
                    remoteImage.path +
 | 
			
		||||
                    " in theme " +
 | 
			
		||||
                    json.id +
 | 
			
		||||
                    ", please download it.",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            for (const image of images) {
 | 
			
		||||
| 
						 | 
				
			
			@ -210,17 +210,17 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
 | 
			
		||||
                const filename = this._path.substring(
 | 
			
		||||
                    this._path.lastIndexOf("/") + 1,
 | 
			
		||||
                    this._path.length - 5
 | 
			
		||||
                    this._path.length - 5,
 | 
			
		||||
                )
 | 
			
		||||
                if (theme.id !== filename) {
 | 
			
		||||
                    context.err(
 | 
			
		||||
                        "Theme ids should be the same as the name.json, but we got id: " +
 | 
			
		||||
                            theme.id +
 | 
			
		||||
                            " and filename " +
 | 
			
		||||
                            filename +
 | 
			
		||||
                            " (" +
 | 
			
		||||
                            this._path +
 | 
			
		||||
                            ")"
 | 
			
		||||
                        theme.id +
 | 
			
		||||
                        " and filename " +
 | 
			
		||||
                        filename +
 | 
			
		||||
                        " (" +
 | 
			
		||||
                        this._path +
 | 
			
		||||
                        ")",
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
                this._validateImage.convert(theme.icon, context.enter("icon"))
 | 
			
		||||
| 
						 | 
				
			
			@ -228,13 +228,13 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
            const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"]))
 | 
			
		||||
            if (dups.length > 0) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`
 | 
			
		||||
                    `The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            if (json["mustHaveLanguage"] !== undefined) {
 | 
			
		||||
                new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert(
 | 
			
		||||
                    theme,
 | 
			
		||||
                    context
 | 
			
		||||
                    context,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) {
 | 
			
		||||
| 
						 | 
				
			
			@ -242,7 +242,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
                const targetLanguage = theme.title.SupportedLanguages()[0]
 | 
			
		||||
                if (targetLanguage !== "en") {
 | 
			
		||||
                    context.err(
 | 
			
		||||
                        `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`
 | 
			
		||||
                        `TargetLanguage is not 'en' for public theme ${theme.id}, it is ${targetLanguage}. Move 'en' up in the title of the theme and set it as the first key`,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -286,7 +286,7 @@ export class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
                    .err(
 | 
			
		||||
                        `This layer ID is not known: ${backgroundId}. Perhaps you meant one of ${nearby
 | 
			
		||||
                            .slice(0, 5)
 | 
			
		||||
                            .join(", ")}`
 | 
			
		||||
                            .join(", ")}`,
 | 
			
		||||
                    )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -309,7 +309,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
 | 
			
		|||
        doesImageExist: DoesImageExist,
 | 
			
		||||
        path: string,
 | 
			
		||||
        isBuiltin: boolean,
 | 
			
		||||
        sharedTagRenderings?: Set<string>
 | 
			
		||||
        sharedTagRenderings?: Set<string>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super(
 | 
			
		||||
            "Validates a theme and the contained layers",
 | 
			
		||||
| 
						 | 
				
			
			@ -319,10 +319,10 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
 | 
			
		|||
                new Each(
 | 
			
		||||
                    new Bypass(
 | 
			
		||||
                        (layer) => Constants.added_by_default.indexOf(<any>layer.id) < 0,
 | 
			
		||||
                        new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true)
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
                        new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true),
 | 
			
		||||
                    ),
 | 
			
		||||
                ),
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -332,7 +332,7 @@ class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
        super(
 | 
			
		||||
            "Checks that an 'overrideAll' does not override a single override",
 | 
			
		||||
            [],
 | 
			
		||||
            "OverrideShadowingCheck"
 | 
			
		||||
            "OverrideShadowingCheck",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -378,6 +378,9 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
        if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) {
 | 
			
		||||
            context.err("The theme " + json.id + " has no 'layers' defined")
 | 
			
		||||
        }
 | 
			
		||||
        if (!Array.isArray(json.layers)) {
 | 
			
		||||
            context.enter("layers").err("The 'layers'-field should be an array, but it is not. Did you pase a layer identifier and forget to add the '[' and ']'?")
 | 
			
		||||
        }
 | 
			
		||||
        if (json.socialImage === "") {
 | 
			
		||||
            context.warn("Social image for theme " + json.id + " is the emtpy string")
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -406,7 +409,7 @@ class MiscThemeChecks extends DesugaringStep<LayoutConfigJson> {
 | 
			
		|||
            context
 | 
			
		||||
                .enter("overideAll")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them."
 | 
			
		||||
                    "'overrideAll' is spelled with _two_ `r`s. You only wrote a single one of them.",
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
        return json
 | 
			
		||||
| 
						 | 
				
			
			@ -418,7 +421,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
 | 
			
		|||
        super(
 | 
			
		||||
            "Various consistency checks on the raw JSON",
 | 
			
		||||
            new MiscThemeChecks(),
 | 
			
		||||
            new OverrideShadowingCheck()
 | 
			
		||||
            new OverrideShadowingCheck(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -428,7 +431,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
 | 
			
		|||
        super(
 | 
			
		||||
            "The `if`-part in a mapping might set some keys. Those keys are not allowed to be set in the `addExtraTags`, as this might result in conflicting values",
 | 
			
		||||
            [],
 | 
			
		||||
            "DetectConflictingAddExtraTags"
 | 
			
		||||
            "DetectConflictingAddExtraTags",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -455,7 +458,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep<TagRenderingCo
 | 
			
		|||
                        .enters("mappings", i)
 | 
			
		||||
                        .err(
 | 
			
		||||
                            "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(", ")
 | 
			
		||||
                            duplicateKeys.join(", "),
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -473,13 +476,13 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
 | 
			
		|||
        super(
 | 
			
		||||
            "A tagRendering might set a freeform key (e.g. `name` and have an option that _should_ erase this name, e.g. `noname=yes`). Under normal circumstances, every mapping/freeform should affect all touched keys",
 | 
			
		||||
            [],
 | 
			
		||||
            "DetectNonErasedKeysInMappings"
 | 
			
		||||
            "DetectNonErasedKeysInMappings",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convert(
 | 
			
		||||
        json: QuestionableTagRenderingConfigJson,
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
        context: ConversionContext,
 | 
			
		||||
    ): QuestionableTagRenderingConfigJson {
 | 
			
		||||
        if (json.multiAnswer) {
 | 
			
		||||
            // No need to check this here, this has its own validation
 | 
			
		||||
| 
						 | 
				
			
			@ -533,8 +536,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
 | 
			
		|||
                        .enters("freeform")
 | 
			
		||||
                        .warn(
 | 
			
		||||
                            "The freeform block does not modify the key `" +
 | 
			
		||||
                                neededKey +
 | 
			
		||||
                                "` which is set in a mapping. Use `addExtraTags` to overwrite it"
 | 
			
		||||
                            neededKey +
 | 
			
		||||
                            "` which is set in a mapping. Use `addExtraTags` to overwrite it",
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -552,8 +555,8 @@ export class DetectNonErasedKeysInMappings extends DesugaringStep<QuestionableTa
 | 
			
		|||
                        .enters("mappings", i)
 | 
			
		||||
                        .warn(
 | 
			
		||||
                            "This mapping does not modify the key `" +
 | 
			
		||||
                                neededKey +
 | 
			
		||||
                                "` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it"
 | 
			
		||||
                            neededKey +
 | 
			
		||||
                            "` which is set in a mapping or by the freeform block. Use `addExtraTags` to overwrite it",
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -570,7 +573,7 @@ export class DetectMappingsShadowedByCondition extends DesugaringStep<TagRenderi
 | 
			
		|||
        super(
 | 
			
		||||
            "Checks that, if the tagrendering has a condition, that a mapping is not contradictory to it, i.e. that there are no dead mappings",
 | 
			
		||||
            [],
 | 
			
		||||
            "DetectMappingsShadowedByCondition"
 | 
			
		||||
            "DetectMappingsShadowedByCondition",
 | 
			
		||||
        )
 | 
			
		||||
        this._forceError = forceError
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -642,7 +645,7 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
 | 
			
		|||
     * DetectShadowedMappings.extractCalculatedTagNames({calculatedTags: ["_abc=js()"]}) // => ["_abc"]
 | 
			
		||||
     */
 | 
			
		||||
    private static extractCalculatedTagNames(
 | 
			
		||||
        layerConfig?: LayerConfigJson | { calculatedTags: string[] }
 | 
			
		||||
        layerConfig?: LayerConfigJson | { calculatedTags: string[] },
 | 
			
		||||
    ) {
 | 
			
		||||
        return (
 | 
			
		||||
            layerConfig?.calculatedTags?.map((ct) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -728,16 +731,16 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
 | 
			
		|||
                    json.mappings[i]["hideInAnswer"] !== true
 | 
			
		||||
                ) {
 | 
			
		||||
                    context.warn(
 | 
			
		||||
                        `Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`
 | 
			
		||||
                        `Mapping ${i} is shadowed by mapping ${j}. However, mapping ${j} has 'hideInAnswer' set, which will result in a different rendering in question-mode.`,
 | 
			
		||||
                    )
 | 
			
		||||
                } else if (doesMatch) {
 | 
			
		||||
                    // The current mapping is shadowed!
 | 
			
		||||
                    context.err(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
 | 
			
		||||
    The mapping ${parsedConditions[i].asHumanString(
 | 
			
		||||
        false,
 | 
			
		||||
        false,
 | 
			
		||||
        {}
 | 
			
		||||
    )} is fully matched by a previous mapping (namely ${j}), which matches:
 | 
			
		||||
                        false,
 | 
			
		||||
                        false,
 | 
			
		||||
                        {},
 | 
			
		||||
                    )} is fully matched by a previous mapping (namely ${j}), which matches:
 | 
			
		||||
    ${parsedConditions[j].asHumanString(false, false, {})}.
 | 
			
		||||
 | 
			
		||||
    To fix this problem, you can try to:
 | 
			
		||||
| 
						 | 
				
			
			@ -764,7 +767,7 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
 | 
			
		|||
        super(
 | 
			
		||||
            "Checks that 'then'clauses in mappings don't have images, but use 'icon' instead",
 | 
			
		||||
            [],
 | 
			
		||||
            "DetectMappingsWithImages"
 | 
			
		||||
            "DetectMappingsWithImages",
 | 
			
		||||
        )
 | 
			
		||||
        this._doesImageExist = doesImageExist
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -804,14 +807,14 @@ export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJ
 | 
			
		|||
                if (!ignore) {
 | 
			
		||||
                    ctx.err(
 | 
			
		||||
                        `A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": <your-image>\` instead. The images found are ${images.join(
 | 
			
		||||
                            ", "
 | 
			
		||||
                        )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`
 | 
			
		||||
                            ", ",
 | 
			
		||||
                        )}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`,
 | 
			
		||||
                    )
 | 
			
		||||
                } else {
 | 
			
		||||
                    ctx.info(
 | 
			
		||||
                        `Ignored image ${images.join(
 | 
			
		||||
                            ", "
 | 
			
		||||
                        )} in 'then'-clause of a mapping as this check has been disabled`
 | 
			
		||||
                            ", ",
 | 
			
		||||
                        )} in 'then'-clause of a mapping as this check has been disabled`,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                    for (const image of images) {
 | 
			
		||||
| 
						 | 
				
			
			@ -832,7 +835,7 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin
 | 
			
		|||
        super(
 | 
			
		||||
            "Given a possible set of translations, validates that <a href=... target='_blank'> does have `rel='noopener'` set",
 | 
			
		||||
            [],
 | 
			
		||||
            "ValidatePossibleLinks"
 | 
			
		||||
            "ValidatePossibleLinks",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -862,21 +865,21 @@ class ValidatePossibleLinks extends DesugaringStep<string | Record<string, strin
 | 
			
		|||
 | 
			
		||||
    convert(
 | 
			
		||||
        json: string | Record<string, string>,
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
        context: ConversionContext,
 | 
			
		||||
    ): string | Record<string, string> {
 | 
			
		||||
        if (typeof json === "string") {
 | 
			
		||||
            if (this.isTabnabbingProne(json)) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    "The string " +
 | 
			
		||||
                        json +
 | 
			
		||||
                        " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping"
 | 
			
		||||
                    json +
 | 
			
		||||
                    " has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            for (const k in json) {
 | 
			
		||||
                if (this.isTabnabbingProne(json[k])) {
 | 
			
		||||
                    context.err(
 | 
			
		||||
                        `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`
 | 
			
		||||
                        `The translation for ${k} '${json[k]}' has a link targeting \`_blank\`, but it doesn't have \`rel='noopener'\` set. This gives rise to reverse tabnapping`,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -894,7 +897,7 @@ class CheckTranslation extends DesugaringStep<Translatable> {
 | 
			
		|||
        super(
 | 
			
		||||
            "Checks that a translation is valid and internally consistent",
 | 
			
		||||
            ["*"],
 | 
			
		||||
            "CheckTranslation"
 | 
			
		||||
            "CheckTranslation",
 | 
			
		||||
        )
 | 
			
		||||
        this._allowUndefined = allowUndefined
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -935,6 +938,7 @@ class CheckTranslation extends DesugaringStep<Translatable> {
 | 
			
		|||
 | 
			
		||||
class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		||||
    private readonly _layerConfig: LayerConfigJson
 | 
			
		||||
 | 
			
		||||
    constructor(layerConfig?: LayerConfigJson) {
 | 
			
		||||
        super("Miscellaneous checks on the tagrendering", ["special"], "MiscTagRenderingChecks")
 | 
			
		||||
        this._layerConfig = layerConfig
 | 
			
		||||
| 
						 | 
				
			
			@ -942,17 +946,17 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
 | 
			
		||||
    convert(
 | 
			
		||||
        json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
        context: ConversionContext,
 | 
			
		||||
    ): TagRenderingConfigJson {
 | 
			
		||||
        if (json["special"] !== undefined) {
 | 
			
		||||
            context.err(
 | 
			
		||||
                'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
 | 
			
		||||
                "Detected `special` on the top level. Did you mean `{\"render\":{ \"special\": ... }}`",
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Object.keys(json).length === 1 && typeof json["render"] === "string") {
 | 
			
		||||
            context.warn(
 | 
			
		||||
                `use the content directly instead of {render: ${JSON.stringify(json["render"])}}`
 | 
			
		||||
                `use the content directly instead of {render: ${JSON.stringify(json["render"])}}`,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -964,7 +968,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                const mapping: MappingConfigJson = json.mappings[i]
 | 
			
		||||
                CheckTranslation.noUndefined.convert(
 | 
			
		||||
                    mapping.then,
 | 
			
		||||
                    context.enters("mappings", i, "then")
 | 
			
		||||
                    context.enters("mappings", i, "then"),
 | 
			
		||||
                )
 | 
			
		||||
                if (!mapping.if) {
 | 
			
		||||
                    console.log(
 | 
			
		||||
| 
						 | 
				
			
			@ -973,7 +977,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                        "if",
 | 
			
		||||
                        mapping.if,
 | 
			
		||||
                        context.path.join("."),
 | 
			
		||||
                        mapping.then
 | 
			
		||||
                        mapping.then,
 | 
			
		||||
                    )
 | 
			
		||||
                    context.enters("mappings", i, "if").err("No `if` is defined")
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -983,7 +987,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                            context
 | 
			
		||||
                                .enters("mappings", i, "addExtraTags", j)
 | 
			
		||||
                                .err(
 | 
			
		||||
                                    "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item"
 | 
			
		||||
                                    "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item",
 | 
			
		||||
                                )
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -994,18 +998,18 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                    context
 | 
			
		||||
                        .enters("mappings", i, "then")
 | 
			
		||||
                        .warn(
 | 
			
		||||
                            "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' <i>without</i> the question, resulting in a weird phrasing in the information box"
 | 
			
		||||
                            "A mapping should not start with 'yes' or 'no'. If the attribute is known, it will only show 'yes' or 'no' <i>without</i> the question, resulting in a weird phrasing in the information box",
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (json["group"]) {
 | 
			
		||||
            context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead')
 | 
			
		||||
            context.err("Groups are deprecated, use `\"label\": [\"" + json["group"] + "\"]` instead")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (json["question"] && json.freeform?.key === undefined && json.mappings === undefined) {
 | 
			
		||||
            context.err(
 | 
			
		||||
                "A question is defined, but no mappings nor freeform (key) are. Add at least one of them"
 | 
			
		||||
                "A question is defined, but no mappings nor freeform (key) are. Add at least one of them",
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
        if (json["question"] && !json.freeform && (json.mappings?.length ?? 0) == 1) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1015,7 +1019,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
            context
 | 
			
		||||
                .enter("questionHint")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "A questionHint is defined, but no question is given. As such, the questionHint will never be shown"
 | 
			
		||||
                    "A questionHint is defined, but no question is given. As such, the questionHint will never be shown",
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1023,7 +1027,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
            context
 | 
			
		||||
                .enters("icon", "size")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`"
 | 
			
		||||
                    "size is not a valid attribute. Did you mean 'class'? Class can be one of `small`, `medium` or `large`",
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1033,10 +1037,10 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                    .enter("render")
 | 
			
		||||
                    .err(
 | 
			
		||||
                        "This tagRendering allows to set a value to key " +
 | 
			
		||||
                            json.freeform.key +
 | 
			
		||||
                            ", but does not define a `render`. Please, add a value here which contains `{" +
 | 
			
		||||
                            json.freeform.key +
 | 
			
		||||
                            "}`"
 | 
			
		||||
                        json.freeform.key +
 | 
			
		||||
                        ", but does not define a `render`. Please, add a value here which contains `{" +
 | 
			
		||||
                        json.freeform.key +
 | 
			
		||||
                        "}`",
 | 
			
		||||
                    )
 | 
			
		||||
            } else {
 | 
			
		||||
                const render = new Translation(<any>json.render)
 | 
			
		||||
| 
						 | 
				
			
			@ -1067,7 +1071,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                    const keyFirstArg = ["canonical", "fediverse_link", "translated"]
 | 
			
		||||
                    if (
 | 
			
		||||
                        keyFirstArg.some(
 | 
			
		||||
                            (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0
 | 
			
		||||
                            (funcName) => txt.indexOf(`{${funcName}(${json.freeform.key}`) >= 0,
 | 
			
		||||
                        )
 | 
			
		||||
                    ) {
 | 
			
		||||
                        continue
 | 
			
		||||
| 
						 | 
				
			
			@ -1091,16 +1095,16 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                        context
 | 
			
		||||
                            .enter("render")
 | 
			
		||||
                            .err(
 | 
			
		||||
                                `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. Did you perhaps forget to set "freeform.type: 'wikidata'"?`
 | 
			
		||||
                                `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. Did you perhaps forget to set "freeform.type: 'wikidata'"?`,
 | 
			
		||||
                            )
 | 
			
		||||
                        continue
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if(txt.indexOf(json.freeform.key) >= 0 && txt.indexOf("{"+json.freeform.key+"}") < 0){
 | 
			
		||||
                    if (txt.indexOf(json.freeform.key) >= 0 && txt.indexOf("{" + json.freeform.key + "}") < 0) {
 | 
			
		||||
                        context
 | 
			
		||||
                            .enter("render")
 | 
			
		||||
                            .err(
 | 
			
		||||
                                `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. However, it does contain ${json.freeform.key} without braces. Did you forget the braces?\n\tThe current text is ${txt}`
 | 
			
		||||
                                `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. However, it does contain ${json.freeform.key} without braces. Did you forget the braces?\n\tThe current text is ${txt}`,
 | 
			
		||||
                            )
 | 
			
		||||
                        continue
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1109,7 +1113,7 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                    context
 | 
			
		||||
                        .enter("render")
 | 
			
		||||
                        .err(
 | 
			
		||||
                            `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!\n\tThe current text is ${txt}`
 | 
			
		||||
                            `The rendering for language ${ln} does not contain \`{${json.freeform.key}}\`. This is a bug, as this rendering should show exactly this freeform key!\n\tThe current text is ${txt}`,
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1124,22 +1128,22 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                        .enters("freeform", "type")
 | 
			
		||||
                        .err(
 | 
			
		||||
                            "No entry found in the 'Name Suggestion Index'. None of the 'osmSource'-tags match an entry in the NSI.\n\tOsmSource-tags are " +
 | 
			
		||||
                                tags.map((t) => new Tag(t.key, t.value).asHumanString()).join(" ; ")
 | 
			
		||||
                            tags.map((t) => new Tag(t.key, t.value).asHumanString()).join(" ; "),
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            } else if (json.freeform.type === "nsi") {
 | 
			
		||||
                context
 | 
			
		||||
                    .enters("freeform", "type")
 | 
			
		||||
                    .warn(
 | 
			
		||||
                        "No need to explicitly set type to 'NSI', autodetected based on freeform type"
 | 
			
		||||
                        "No need to explicitly set type to 'NSI', autodetected based on freeform type",
 | 
			
		||||
                    )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (json.render && json["question"] && json.freeform === undefined) {
 | 
			
		||||
            context.err(
 | 
			
		||||
                `Detected a tagrendering which takes input without freeform key in ${context}; the question is ${new Translation(
 | 
			
		||||
                    json["question"]
 | 
			
		||||
                ).textFor("en")}`
 | 
			
		||||
                    json["question"],
 | 
			
		||||
                ).textFor("en")}`,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1150,9 +1154,9 @@ class MiscTagRenderingChecks extends DesugaringStep<TagRenderingConfigJson> {
 | 
			
		|||
                    .enters("freeform", "type")
 | 
			
		||||
                    .err(
 | 
			
		||||
                        "Unknown type: " +
 | 
			
		||||
                            freeformType +
 | 
			
		||||
                            "; try one of " +
 | 
			
		||||
                            Validators.availableTypes.join(", ")
 | 
			
		||||
                        freeformType +
 | 
			
		||||
                        "; try one of " +
 | 
			
		||||
                        Validators.availableTypes.join(", "),
 | 
			
		||||
                    )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1190,7 +1194,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
 | 
			
		|||
            new On("question", new ValidatePossibleLinks()),
 | 
			
		||||
            new On("questionHint", new ValidatePossibleLinks()),
 | 
			
		||||
            new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))),
 | 
			
		||||
            new MiscTagRenderingChecks(layerConfig)
 | 
			
		||||
            new MiscTagRenderingChecks(layerConfig),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1209,7 +1213,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
        path: string,
 | 
			
		||||
        isBuiltin: boolean,
 | 
			
		||||
        doesImageExist: DoesImageExist,
 | 
			
		||||
        studioValidations: boolean
 | 
			
		||||
        studioValidations: boolean,
 | 
			
		||||
    ) {
 | 
			
		||||
        super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer")
 | 
			
		||||
        this._path = path
 | 
			
		||||
| 
						 | 
				
			
			@ -1235,7 +1239,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
            context
 | 
			
		||||
                .enter("source")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "No source section is defined; please define one as data is not loaded otherwise"
 | 
			
		||||
                    "No source section is defined; please define one as data is not loaded otherwise",
 | 
			
		||||
                )
 | 
			
		||||
        } else {
 | 
			
		||||
            if (json.source === "special" || json.source === "special:library") {
 | 
			
		||||
| 
						 | 
				
			
			@ -1243,7 +1247,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                context
 | 
			
		||||
                    .enters("source", "osmTags")
 | 
			
		||||
                    .err(
 | 
			
		||||
                        "No osmTags defined in the source section - these should always be present, even for geojson layer"
 | 
			
		||||
                        "No osmTags defined in the source section - these should always be present, even for geojson layer",
 | 
			
		||||
                    )
 | 
			
		||||
            } else {
 | 
			
		||||
                const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags")
 | 
			
		||||
| 
						 | 
				
			
			@ -1252,7 +1256,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                        .enters("source", "osmTags")
 | 
			
		||||
                        .err(
 | 
			
		||||
                            "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" +
 | 
			
		||||
                                osmTags.asHumanString(false, false, {})
 | 
			
		||||
                            osmTags.asHumanString(false, false, {}),
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1278,10 +1282,10 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                .enter("syncSelection")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "Invalid sync-selection: must be one of " +
 | 
			
		||||
                        LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") +
 | 
			
		||||
                        " but got '" +
 | 
			
		||||
                        json.syncSelection +
 | 
			
		||||
                        "'"
 | 
			
		||||
                    LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") +
 | 
			
		||||
                    " but got '" +
 | 
			
		||||
                    json.syncSelection +
 | 
			
		||||
                    "'",
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
        if (json["pointRenderings"]?.length > 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1300,7 +1304,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        json.pointRendering?.forEach((pr, i) =>
 | 
			
		||||
            this._validatePointRendering.convert(pr, context.enters("pointeRendering", i))
 | 
			
		||||
            this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (json["mapRendering"]) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1317,8 +1321,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
            if (!Constants.priviliged_layers.find((x) => x == json.id)) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    "Layer " +
 | 
			
		||||
                        json.id +
 | 
			
		||||
                        " uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
 | 
			
		||||
                    json.id +
 | 
			
		||||
                    " uses 'special' as source.osmTags. However, this layer is not a priviliged layer",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1333,19 +1337,19 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                context
 | 
			
		||||
                    .enter("title")
 | 
			
		||||
                    .err(
 | 
			
		||||
                        "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) {
 | 
			
		||||
                context.info(
 | 
			
		||||
                    "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.",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            {
 | 
			
		||||
                // Check for multiple, identical builtin questions - usability for studio users
 | 
			
		||||
                const duplicates = Utils.Duplicates(
 | 
			
		||||
                    <string[]>json.tagRenderings.filter((tr) => typeof tr === "string")
 | 
			
		||||
                    <string[]>json.tagRenderings.filter((tr) => typeof tr === "string"),
 | 
			
		||||
                )
 | 
			
		||||
                for (let i = 0; i < json.tagRenderings.length; i++) {
 | 
			
		||||
                    const tagRendering = json.tagRenderings[i]
 | 
			
		||||
| 
						 | 
				
			
			@ -1375,7 +1379,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
        {
 | 
			
		||||
            // duplicate ids in tagrenderings check
 | 
			
		||||
            const duplicates = Utils.NoNull(
 | 
			
		||||
                Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"])))
 | 
			
		||||
                Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))),
 | 
			
		||||
            )
 | 
			
		||||
            if (duplicates.length > 0) {
 | 
			
		||||
                // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
 | 
			
		||||
| 
						 | 
				
			
			@ -1383,11 +1387,11 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                    .enter("tagRenderings")
 | 
			
		||||
                    .err(
 | 
			
		||||
                        "Some tagrenderings have a duplicate id: " +
 | 
			
		||||
                            duplicates.join(", ") +
 | 
			
		||||
                            "\n" +
 | 
			
		||||
                            JSON.stringify(
 | 
			
		||||
                                json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0)
 | 
			
		||||
                            )
 | 
			
		||||
                        duplicates.join(", ") +
 | 
			
		||||
                        "\n" +
 | 
			
		||||
                        JSON.stringify(
 | 
			
		||||
                            json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0),
 | 
			
		||||
                        ),
 | 
			
		||||
                    )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1420,8 +1424,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
            if (json["overpassTags"] !== undefined) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    "Layer " +
 | 
			
		||||
                        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)'
 | 
			
		||||
                    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)",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
            const forbiddenTopLevel = [
 | 
			
		||||
| 
						 | 
				
			
			@ -1441,7 +1445,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
            }
 | 
			
		||||
            if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'"
 | 
			
		||||
                    "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'",
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1458,9 +1462,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
            if (this._path != undefined && this._path.indexOf(expected) < 0) {
 | 
			
		||||
                context.err(
 | 
			
		||||
                    "Layer is in an incorrect place. The path is " +
 | 
			
		||||
                        this._path +
 | 
			
		||||
                        ", but expected " +
 | 
			
		||||
                        expected
 | 
			
		||||
                    this._path +
 | 
			
		||||
                    ", but expected " +
 | 
			
		||||
                    expected,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1478,13 +1482,13 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                    .enter(["tagRenderings", ...emptyIndexes])
 | 
			
		||||
                    .err(
 | 
			
		||||
                        `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join(
 | 
			
		||||
                            ","
 | 
			
		||||
                        )}])`
 | 
			
		||||
                            ",",
 | 
			
		||||
                        )}])`,
 | 
			
		||||
                    )
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const duplicateIds = Utils.Duplicates(
 | 
			
		||||
                (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions")
 | 
			
		||||
                (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions"),
 | 
			
		||||
            )
 | 
			
		||||
            if (duplicateIds.length > 0 && !Utils.runningFromConsole) {
 | 
			
		||||
                context
 | 
			
		||||
| 
						 | 
				
			
			@ -1508,7 +1512,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
        if (json.tagRenderings !== undefined) {
 | 
			
		||||
            new On(
 | 
			
		||||
                "tagRenderings",
 | 
			
		||||
                new Each(new ValidateTagRenderings(json, this._doesImageExist))
 | 
			
		||||
                new Each(new ValidateTagRenderings(json, this._doesImageExist)),
 | 
			
		||||
            ).convert(json, context)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1535,7 +1539,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                        context
 | 
			
		||||
                            .enters("pointRendering", i, "marker", indexM, "icon", "condition")
 | 
			
		||||
                            .err(
 | 
			
		||||
                                "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead."
 | 
			
		||||
                                "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead.",
 | 
			
		||||
                            )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -1573,9 +1577,9 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
                        .enters("presets", i, "tags")
 | 
			
		||||
                        .err(
 | 
			
		||||
                            "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: " +
 | 
			
		||||
                                tags.asHumanString(false, false, {}) +
 | 
			
		||||
                                "\n    The required tags are: " +
 | 
			
		||||
                                baseTags.asHumanString(false, false, {})
 | 
			
		||||
                            tags.asHumanString(false, false, {}) +
 | 
			
		||||
                            "\n    The required tags are: " +
 | 
			
		||||
                            baseTags.asHumanString(false, false, {}),
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1592,7 +1596,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
        isBuiltin: boolean,
 | 
			
		||||
        doesImageExist: DoesImageExist,
 | 
			
		||||
        studioValidations: boolean = false,
 | 
			
		||||
        skipDefaultLayers: boolean = false
 | 
			
		||||
        skipDefaultLayers: boolean = false,
 | 
			
		||||
    ) {
 | 
			
		||||
        super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig")
 | 
			
		||||
        this.validator = new ValidateLayer(
 | 
			
		||||
| 
						 | 
				
			
			@ -1600,7 +1604,7 @@ export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
 | 
			
		|||
            isBuiltin,
 | 
			
		||||
            doesImageExist,
 | 
			
		||||
            studioValidations,
 | 
			
		||||
            skipDefaultLayers
 | 
			
		||||
            skipDefaultLayers,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1628,7 +1632,7 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> {
 | 
			
		|||
            context
 | 
			
		||||
                .enter("markers")
 | 
			
		||||
                .err(
 | 
			
		||||
                    `Detected a field 'markerS' in pointRendering. It is written as a singular case`
 | 
			
		||||
                    `Detected a field 'markerS' in pointRendering. It is written as a singular case`,
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
        if (json.marker && !Array.isArray(json.marker)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1638,7 +1642,7 @@ class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJson> {
 | 
			
		|||
            context
 | 
			
		||||
                .enter("location")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "A pointRendering should have at least one 'location' to defined where it should be rendered. "
 | 
			
		||||
                    "A pointRendering should have at least one 'location' to defined where it should be rendered. ",
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
        return json
 | 
			
		||||
| 
						 | 
				
			
			@ -1657,26 +1661,26 @@ export class ValidateLayer extends Conversion<
 | 
			
		|||
        isBuiltin: boolean,
 | 
			
		||||
        doesImageExist: DoesImageExist,
 | 
			
		||||
        studioValidations: boolean = false,
 | 
			
		||||
        skipDefaultLayers: boolean = false
 | 
			
		||||
        skipDefaultLayers: boolean = false,
 | 
			
		||||
    ) {
 | 
			
		||||
        super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer")
 | 
			
		||||
        this._prevalidation = new PrevalidateLayer(
 | 
			
		||||
            path,
 | 
			
		||||
            isBuiltin,
 | 
			
		||||
            doesImageExist,
 | 
			
		||||
            studioValidations
 | 
			
		||||
            studioValidations,
 | 
			
		||||
        )
 | 
			
		||||
        this._skipDefaultLayers = skipDefaultLayers
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convert(
 | 
			
		||||
        json: LayerConfigJson,
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
        context: ConversionContext,
 | 
			
		||||
    ): { parsed: LayerConfig; raw: LayerConfigJson } {
 | 
			
		||||
        context = context.inOperation(this.name)
 | 
			
		||||
        if (typeof json === "string") {
 | 
			
		||||
            context.err(
 | 
			
		||||
                `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`
 | 
			
		||||
                `Not a valid layer: the layerConfig is a string. 'npm run generate:layeroverview' might be needed`,
 | 
			
		||||
            )
 | 
			
		||||
            return undefined
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1707,7 +1711,7 @@ export class ValidateLayer extends Conversion<
 | 
			
		|||
                context
 | 
			
		||||
                    .enters("calculatedTags", i)
 | 
			
		||||
                    .err(
 | 
			
		||||
                        `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n    ${code}`
 | 
			
		||||
                        `Invalid function definition: the custom javascript is invalid:${e}. The offending javascript code is:\n    ${code}`,
 | 
			
		||||
                    )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1755,7 +1759,7 @@ export class ValidateLayer extends Conversion<
 | 
			
		|||
            context
 | 
			
		||||
                .enters("allowMove", "enableAccuracy")
 | 
			
		||||
                .err(
 | 
			
		||||
                    "`enableAccuracy` is written with two C in the first occurrence and only one in the last"
 | 
			
		||||
                    "`enableAccuracy` is written with two C in the first occurrence and only one in the last",
 | 
			
		||||
                )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1786,8 +1790,8 @@ export class ValidateFilter extends DesugaringStep<FilterConfigJson> {
 | 
			
		|||
                        .enters("fields", i)
 | 
			
		||||
                        .err(
 | 
			
		||||
                            `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from(
 | 
			
		||||
                                Validators.availableTypes
 | 
			
		||||
                            ).join(",")}`
 | 
			
		||||
                                Validators.availableTypes,
 | 
			
		||||
                            ).join(",")}`,
 | 
			
		||||
                        )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1804,13 +1808,13 @@ export class DetectDuplicateFilters extends DesugaringStep<{
 | 
			
		|||
        super(
 | 
			
		||||
            "Tries to detect layers where a shared filter can be used (or where similar filters occur)",
 | 
			
		||||
            [],
 | 
			
		||||
            "DetectDuplicateFilters"
 | 
			
		||||
            "DetectDuplicateFilters",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convert(
 | 
			
		||||
        json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] },
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
        context: ConversionContext,
 | 
			
		||||
    ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } {
 | 
			
		||||
        const { layers, themes } = json
 | 
			
		||||
        const perOsmTag = new Map<
 | 
			
		||||
| 
						 | 
				
			
			@ -1874,7 +1878,7 @@ export class DetectDuplicateFilters extends DesugaringStep<{
 | 
			
		|||
                filter: FilterConfigJson
 | 
			
		||||
            }[]
 | 
			
		||||
        >,
 | 
			
		||||
        layout?: LayoutConfigJson | undefined
 | 
			
		||||
        layout?: LayoutConfigJson | undefined,
 | 
			
		||||
    ): void {
 | 
			
		||||
        if (layer.filter === undefined || layer.filter === null) {
 | 
			
		||||
            return
 | 
			
		||||
| 
						 | 
				
			
			@ -1914,7 +1918,7 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
 | 
			
		|||
        super(
 | 
			
		||||
            "Detects mappings which have identical (english) names or identical mappings.",
 | 
			
		||||
            ["presets"],
 | 
			
		||||
            "DetectDuplicatePresets"
 | 
			
		||||
            "DetectDuplicatePresets",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1925,13 +1929,13 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
 | 
			
		|||
        if (new Set(enNames).size != enNames.length) {
 | 
			
		||||
            const dups = Utils.Duplicates(enNames)
 | 
			
		||||
            const layersWithDup = json.layers.filter((l) =>
 | 
			
		||||
                l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0)
 | 
			
		||||
                l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0),
 | 
			
		||||
            )
 | 
			
		||||
            const layerIds = layersWithDup.map((l) => l.id)
 | 
			
		||||
            context.err(
 | 
			
		||||
                `This theme has multiple presets which are named:${dups}, namely layers ${layerIds.join(
 | 
			
		||||
                    ", "
 | 
			
		||||
                )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`
 | 
			
		||||
                    ", ",
 | 
			
		||||
                )} this is confusing for contributors and is probably the result of reusing the same layer multiple times. Use \`{"override": {"=presets": []}}\` to remove some presets`,
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1946,17 +1950,17 @@ export class DetectDuplicatePresets extends DesugaringStep<LayoutConfig> {
 | 
			
		|||
                    Utils.SameObject(presetATags, presetBTags) &&
 | 
			
		||||
                    Utils.sameList(
 | 
			
		||||
                        presetA.preciseInput.snapToLayers,
 | 
			
		||||
                        presetB.preciseInput.snapToLayers
 | 
			
		||||
                        presetB.preciseInput.snapToLayers,
 | 
			
		||||
                    )
 | 
			
		||||
                ) {
 | 
			
		||||
                    context.err(
 | 
			
		||||
                        `This theme has multiple presets with the same tags: ${presetATags.asHumanString(
 | 
			
		||||
                            false,
 | 
			
		||||
                            false,
 | 
			
		||||
                            {}
 | 
			
		||||
                            {},
 | 
			
		||||
                        )}, namely the preset '${presets[i].title.textFor("en")}' and '${presets[
 | 
			
		||||
                            j
 | 
			
		||||
                        ].title.textFor("en")}'`
 | 
			
		||||
                            ].title.textFor("en")}'`,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -1981,13 +1985,13 @@ export class ValidateThemeEnsemble extends Conversion<
 | 
			
		|||
        super(
 | 
			
		||||
            "Validates that all themes together are logical, i.e. no duplicate ids exists within (overriden) themes",
 | 
			
		||||
            [],
 | 
			
		||||
            "ValidateThemeEnsemble"
 | 
			
		||||
            "ValidateThemeEnsemble",
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convert(
 | 
			
		||||
        json: LayoutConfig[],
 | 
			
		||||
        context: ConversionContext
 | 
			
		||||
        context: ConversionContext,
 | 
			
		||||
    ): Map<
 | 
			
		||||
        string,
 | 
			
		||||
        {
 | 
			
		||||
| 
						 | 
				
			
			@ -2038,11 +2042,11 @@ export class ValidateThemeEnsemble extends Conversion<
 | 
			
		|||
                context.err(
 | 
			
		||||
                    [
 | 
			
		||||
                        "The layer with id '" +
 | 
			
		||||
                            id +
 | 
			
		||||
                            "' is found in multiple themes with different tag definitions:",
 | 
			
		||||
                        id +
 | 
			
		||||
                        "' is found in multiple themes with different tag definitions:",
 | 
			
		||||
                        "\t In theme " + oldTheme + ":\t" + oldTags.asHumanString(false, false, {}),
 | 
			
		||||
                        "\tIn theme " + theme.id + ":\t" + tags.asHumanString(false, false, {}),
 | 
			
		||||
                    ].join("\n")
 | 
			
		||||
                    ].join("\n"),
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue