values
}
- const information: string[] = []
- const warnings: string[] = []
- const errors: string[] = []
const step = this._step
const result: Y[] = []
+
for (let i = 0; i < values.length; i++) {
- const r = step.convert(values[i], context + "[" + i + "]")
- Utils.PushList(information, r.information)
- Utils.PushList(warnings, r.warnings)
- Utils.PushList(errors, r.errors)
- result.push(r.result)
- }
- return {
- information,
- errors,
- warnings,
- result,
+ const context_ = context.enter(i).inOperation("each")
+ const r = step.convert(values[i], context_)
+ result.push(r)
}
+ return result
}
}
@@ -180,23 +204,17 @@ export class On extends DesugaringStep {
this.key = key
}
- convert(
- json: T,
- context: string
- ): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } {
+ convert(json: T, context: ConversionContext): T {
json = { ...json }
const step = this.step(json)
const key = this.key
const value: P = json[key]
if (value === undefined || value === null) {
- return { result: json }
- }
- const r = step.convert(value, context + "." + key)
- json[key] = r.result
- return {
- ...r,
- result: json,
+ return undefined
}
+
+ json[key] = step.convert(value, context.enter(key).inOperation("on[" + key + "]"))
+ return json
}
}
@@ -205,13 +223,8 @@ export class Pass extends Conversion {
super(message ?? "Does nothing, often to swap out steps in testing", [], "Pass")
}
- convert(
- json: T,
- context: string
- ): { result: T; errors?: string[]; warnings?: string[]; information?: string[] } {
- return {
- result: json,
- }
+ convert(json: T, context: ConversionContext): T {
+ return json
}
}
@@ -227,25 +240,13 @@ export class Concat extends Conversion {
this._step = step
}
- convert(
- values: X[],
- context: string
- ): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } {
+ convert(values: X[], context: ConversionContext): T[] {
if (values === undefined || values === null) {
// Move on - nothing to see here!
- return {
- result: undefined,
- }
- }
- const r = new Each(this._step).convert(values, context)
- const vals: T[][] = r.result
-
- const flattened: T[] = [].concat(...vals)
-
- return {
- ...r,
- result: flattened,
+ return values
}
+ const vals: T[][] = new Each(this._step).convert(values, context.inOperation("concat"))
+ return [].concat(...vals)
}
}
@@ -261,15 +262,12 @@ export class FirstOf extends Conversion {
this._conversion = conversion
}
- convert(
- json: T,
- context: string
- ): { result: X; errors?: string[]; warnings?: string[]; information?: string[] } {
- const reslt = this._conversion.convert(json, context)
- return {
- ...reslt,
- result: reslt.result[0],
+ convert(json: T, context: ConversionContext): X {
+ const values = this._conversion.convert(json, context.inOperation("firstOf"))
+ if (values.length === 0) {
+ return undefined
}
+ return values[0]
}
}
@@ -287,38 +285,24 @@ export class Fuse extends DesugaringStep {
this.steps = Utils.NoNull(steps)
}
- convert(
- json: T,
- context: string
- ): { result: T; errors: string[]; warnings: string[]; information: string[] } {
- const errors = []
- const warnings = []
- const information = []
+ convert(json: T, context: ConversionContext): T {
for (let i = 0; i < this.steps.length; i++) {
const step = this.steps[i]
try {
- let r = step.convert(json, "While running step " + step.name + ": " + context)
- if (r.result["tagRenderings"]?.some((tr) => tr === undefined)) {
- throw step.name + " introduced an undefined tagRendering"
- }
- errors.push(...(r.errors ?? []))
- warnings.push(...(r.warnings ?? []))
- information.push(...(r.information ?? []))
- json = r.result
- if (errors.length > 0) {
+ const r = step.convert(json, context.inOperation(step.name))
+ if (r === undefined) {
break
}
+ if (context.hasErrors()) {
+ break
+ }
+ json = r
} catch (e) {
console.error("Step " + step.name + " failed due to ", e, e.stack)
throw e
}
}
- return {
- result: json,
- errors,
- warnings,
- information,
- }
+ return json
}
}
@@ -334,14 +318,15 @@ export class SetDefault extends DesugaringStep {
this._overrideEmptyString = overrideEmptyString
}
- convert(json: T, context: string): { result: T } {
+ convert(json: T, context: ConversionContext): T {
+ if (json === undefined) {
+ return undefined
+ }
if (json[this.key] === undefined || (json[this.key] === "" && this._overrideEmptyString)) {
json = { ...json }
json[this.key] = this.value
}
- return {
- result: json,
- }
+ return json
}
}
diff --git a/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts b/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts
index 5824a72d7e..26d29811d1 100644
--- a/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts
+++ b/src/Models/ThemeConfig/Conversion/CreateNoteImportLayer.ts
@@ -1,4 +1,4 @@
-import { Conversion } from "./Conversion"
+import { Conversion, ConversionContext } from "./Conversion"
import LayerConfig from "../LayerConfig"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import Translations from "../../../UI/i18n/Translations"
@@ -23,7 +23,7 @@ export default class CreateNoteImportLayer extends Conversion {
- private _isOfficial: boolean
- private _sharedTagRenderings: Set
-
private static readonly layoutMetaPaths = metapaths.filter((mp) => {
const typeHint = mp.hints.typehint
return (
@@ -25,6 +23,8 @@ export class ExtractImages extends Conversion<
)
})
private static readonly tagRenderingMetaPaths = tagrenderingmetapaths
+ private _isOfficial: boolean
+ private _sharedTagRenderings: Set
constructor(isOfficial: boolean, sharedTagRenderings: Set) {
super("Extract all images from a layoutConfig using the meta paths.", [], "ExctractImages")
@@ -89,11 +89,9 @@ export class ExtractImages extends Conversion<
*/
convert(
json: LayoutConfigJson,
- context: string
- ): { result: { path: string; context: string }[]; errors: string[]; warnings: string[] } {
+ context: ConversionContext
+ ): { path: string; context: string }[] {
const allFoundImages: { path: string; context: string }[] = []
- const errors = []
- const warnings = []
for (const metapath of ExtractImages.layoutMetaPaths) {
const mightBeTr = ExtractImages.mightBeTagRendering(metapath)
const allRenderedValuesAreImages =
@@ -110,7 +108,7 @@ export class ExtractImages extends Conversion<
}
if (foundImage == "") {
- warnings.push(context + "." + path.join(".") + " Found an empty image")
+ context.warn(context + "." + path.join(".") + " Found an empty image")
}
if (this._sharedTagRenderings?.has(foundImage)) {
@@ -135,17 +133,15 @@ export class ExtractImages extends Conversion<
if (allRenderedValuesAreImages && isRendered) {
// What we found is an image
if (img.leaf === "" || img.leaf["path"] == "") {
- warnings.push(
- context +
- [...path, ...img.path].join(".") +
- ": Found an empty image at "
- )
+ context
+ .enter(path)
+ .enter(img.path)
+ .warn("Found an emtpy image")
} else if (typeof img.leaf !== "string") {
- ;(this._isOfficial ? errors : warnings).push(
- context +
- "." +
- img.path.join(".") +
- ": found an image path that is not a string: " +
+ const c = context.enter(img.path)
+ const w = this._isOfficial ? c.err : c.warn
+ w(
+ "found an image path that is not a string: " +
JSON.stringify(img.leaf)
)
} else {
@@ -176,9 +172,8 @@ export class ExtractImages extends Conversion<
} else {
for (const foundElement of found) {
if (foundElement.leaf === "") {
- warnings.push(
- context + "." + foundElement.path.join(".") + " Found an empty image"
- )
+ context.enter(foundElement.path).warn("Found an empty image")
+
continue
}
if (typeof foundElement.leaf !== "string") {
@@ -215,7 +210,7 @@ export class ExtractImages extends Conversion<
}
}
- return { result: cleanedImages, errors, warnings }
+ return cleanedImages
}
}
@@ -265,26 +260,22 @@ export class FixImages extends DesugaringStep {
* fixed.layers[0]["mapRendering"][0].icon // => "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg"
* fixed.layers[0]["mapRendering"][0].iconBadges[0].then.mappings[0].then // => "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/Something.svg"
*/
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; warnings?: string[] } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
let url: URL
try {
url = new URL(json.id)
} catch (e) {
// Not a URL, we don't rewrite
- return { result: json }
+ return json
}
- const warnings: string[] = []
const absolute = url.protocol + "//" + url.host
let relative = url.protocol + "//" + url.host + url.pathname
relative = relative.substring(0, relative.lastIndexOf("/"))
const self = this
if (relative.endsWith("assets/generated/themes")) {
- warnings.push(
+ context.warn(
"Detected 'assets/generated/themes' as relative URL. I'm assuming that you are loading your file for the MC-repository, so I'm rewriting all image links as if they were absolute instead of relative"
)
relative = absolute
@@ -296,7 +287,7 @@ export class FixImages extends DesugaringStep {
}
if (typeof leaf !== "string") {
- warnings.push(
+ context.warn(
"Found a non-string object while replacing images: " + JSON.stringify(leaf)
)
return leaf
@@ -318,7 +309,7 @@ export class FixImages extends DesugaringStep {
continue
}
const mightBeTr = ExtractImages.mightBeTagRendering(metapath)
- Utils.WalkPath(metapath.path, json, (leaf, path) => {
+ Utils.WalkPath(metapath.path, json, (leaf) => {
if (typeof leaf === "string") {
return replaceString(leaf)
}
@@ -340,9 +331,6 @@ export class FixImages extends DesugaringStep {
})
}
- return {
- warnings,
- result: json,
- }
+ return json
}
}
diff --git a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts
index 6ddf8c754c..44836c0850 100644
--- a/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts
+++ b/src/Models/ThemeConfig/Conversion/LegacyJsonConvert.ts
@@ -2,7 +2,7 @@ import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import { Utils } from "../../../Utils"
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import { LayerConfigJson } from "../Json/LayerConfigJson"
-import { DesugaringStep, Each, Fuse, On } from "./Conversion"
+import { ConversionContext, DesugaringStep, Each, Fuse, On } from "./Conversion"
import PointRenderingConfigJson from "../Json/PointRenderingConfigJson"
export class UpdateLegacyLayer extends DesugaringStep<
@@ -16,15 +16,12 @@ export class UpdateLegacyLayer extends DesugaringStep<
)
}
- convert(
- json: LayerConfigJson,
- context: string
- ): { result: LayerConfigJson; errors: string[]; warnings: string[] } {
- const warnings = []
+ convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (typeof json === "string" || json["builtin"] !== undefined) {
// Reuse of an already existing layer; return as-is
- return { result: json, errors: [], warnings: [] }
+ return json
}
+ context = context.enter(json.id)
let config = { ...json }
if (config["overpassTags"]) {
@@ -141,7 +138,7 @@ export class UpdateLegacyLayer extends DesugaringStep<
}
for (const overlay of mapRenderingElement["iconBadges"] ?? []) {
if (overlay["badge"] !== true) {
- warnings.push("Warning: non-overlay element for ", config.id)
+ context.enters("iconBadges", "badge").warn("Non-overlay element")
}
delete overlay["badge"]
}
@@ -229,11 +226,7 @@ export class UpdateLegacyLayer extends DesugaringStep<
}
}
- return {
- result: config,
- errors: [],
- warnings,
- }
+ return config
}
}
@@ -242,10 +235,7 @@ class UpdateLegacyTheme extends DesugaringStep {
super("Small fixes in the theme config", ["roamingRenderings"], "UpdateLegacyTheme")
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const oldThemeConfig = { ...json }
if (oldThemeConfig.socialImage === "") {
@@ -260,14 +250,8 @@ class UpdateLegacyTheme extends DesugaringStep {
if (oldThemeConfig["roamingRenderings"].length == 0) {
delete oldThemeConfig["roamingRenderings"]
} else {
- return {
- result: null,
- errors: [
- context +
- ": The theme contains roamingRenderings. These are not supported anymore",
- ],
- warnings: [],
- }
+ context.err("The theme contains roamingRenderings. These are not supported anymore")
+ return null
}
}
@@ -292,11 +276,7 @@ class UpdateLegacyTheme extends DesugaringStep {
}
}
- return {
- errors: [],
- warnings: [],
- result: oldThemeConfig,
- }
+ return oldThemeConfig
}
}
diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts
index 9dd8fb4e58..82e65548db 100644
--- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts
+++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts
@@ -1,6 +1,7 @@
import {
Concat,
Conversion,
+ ConversionContext,
DesugaringContext,
DesugaringStep,
Each,
@@ -48,20 +49,16 @@ class ExpandFilter extends DesugaringStep {
return filters
}
- convert(
- json: LayerConfigJson,
- context: string
- ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
- if (json.filter === undefined || json.filter === null) {
- return { result: json } // Nothing to change here
+ convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
+ if (json?.filter === undefined || json?.filter === null) {
+ return json // Nothing to change here
}
if (json.filter["sameAs"] !== undefined) {
- return { result: json } // Nothing to change here
+ return json // Nothing to change here
}
const newFilters: FilterConfigJson[] = []
- const errors: string[] = []
for (const filter of <(FilterConfigJson | string)[]>json.filter) {
if (typeof filter !== "string") {
newFilters.push(filter)
@@ -71,16 +68,13 @@ class ExpandFilter extends DesugaringStep {
if (this._state.sharedLayers.size > 0) {
const split = filter.split(".")
if (split.length > 2) {
- errors.push(
- context +
- ": invalid filter name: " +
- filter +
- ", expected `layername.filterid`"
+ context.err(
+ "invalid filter name: " + filter + ", expected `layername.filterid`"
)
}
const layer = this._state.sharedLayers.get(split[0])
if (layer === undefined) {
- errors.push(context + ": layer '" + split[0] + "' not found")
+ context.err("Layer '" + split[0] + "' not found")
}
const expectedId = split[1]
const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find(
@@ -100,28 +94,28 @@ class ExpandFilter extends DesugaringStep {
Array.from(ExpandFilter.predefinedFilters.keys()),
(t) => t
)
- const err =
- context +
- ".filter: while searching for predifined filter " +
- filter +
- ": this filter is not found. Perhaps you meant one of: " +
- suggestions
- errors.push(err)
+ context
+ .enter(filter)
+ .err(
+ "While searching for predefined filter " +
+ filter +
+ ": this filter is not found. Perhaps you meant one of: " +
+ suggestions
+ )
}
newFilters.push(found)
}
- return {
- result: {
- ...json,
- filter: newFilters,
- },
- errors,
- }
+ return { ...json, filter: newFilters }
}
}
class ExpandTagRendering extends Conversion<
- string | TagRenderingConfigJson | { builtin: string | string[]; override: any },
+ | string
+ | TagRenderingConfigJson
+ | {
+ builtin: string | string[]
+ override: any
+ },
TagRenderingConfigJson[]
> {
private readonly _state: DesugaringContext
@@ -137,7 +131,10 @@ class ExpandTagRendering extends Conversion<
constructor(
state: DesugaringContext,
self: LayerConfigJson,
- options?: { applyCondition?: true | boolean; noHardcodedStrings?: false | boolean }
+ options?: {
+ applyCondition?: true | boolean
+ noHardcodedStrings?: false | boolean
+ }
) {
super(
"Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question",
@@ -160,23 +157,6 @@ class ExpandTagRendering extends Conversion<
}
}
- convert(
- json:
- | string
- | QuestionableTagRenderingConfigJson
- | { builtin: string | string[]; override: any },
- context: string
- ): { result: QuestionableTagRenderingConfigJson[]; errors: string[]; warnings: string[] } {
- const errors = []
- const warnings = []
-
- return {
- result: this.convertUntilStable(json, warnings, errors, context),
- errors,
- warnings,
- }
- }
-
private lookup(name: string): TagRenderingConfigJson[] | undefined {
const direct = this.directLookup(name)
@@ -261,7 +241,13 @@ class ExpandTagRendering extends Conversion<
}
}
- found = contextWriter.convertStrict(found, layer.id + ".tagRenderings." + found["id"])
+ found = contextWriter.convertStrict(
+ found,
+ ConversionContext.construct(
+ [layer.id, "tagRenderings", found["id"]],
+ ["AddContextToTranslations"]
+ )
+ )
matchingTrs[i] = found
}
@@ -271,12 +257,7 @@ class ExpandTagRendering extends Conversion<
return undefined
}
- private convertOnce(
- tr: string | any,
- warnings: string[],
- errors: string[],
- ctx: string
- ): TagRenderingConfigJson[] {
+ private convertOnce(tr: string | any, ctx: ConversionContext): TagRenderingConfigJson[] {
const state = this._state
if (typeof tr === "string") {
@@ -285,19 +266,17 @@ class ExpandTagRendering extends Conversion<
lookup = this.lookup(tr)
}
if (lookup === undefined) {
- const isTagRendering = ctx.indexOf("On(mapRendering") < 0
- if (isTagRendering && this._state.sharedLayers?.size > 0) {
- warnings.push(
- `${ctx}: A literal rendering was detected: ${tr}
- Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
+ if (this._state.sharedLayers?.size > 0) {
+ ctx.warn(
+ `A literal rendering was detected: ${tr}
+ Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
Array.from(state.sharedLayers.keys()).join(", ")
)
}
if (this._options?.noHardcodedStrings && this._state?.sharedLayers?.size > 0) {
- errors.push(
- ctx +
- "Detected an invocation to a builtin tagRendering, but this tagrendering was not found: " +
+ ctx.err(
+ "Detected an invocation to a builtin tagRendering, but this tagrendering was not found: " +
tr +
" \n Did you perhaps forget to add the layer as prefix, such as `icons." +
tr +
@@ -334,10 +313,8 @@ class ExpandTagRendering extends Conversion<
) {
continue
}
- errors.push(
- "At " +
- ctx +
- ": an object calling a builtin can only have keys `builtin` or `override`, but a key with name `" +
+ ctx.err(
+ "An object calling a builtin can only have keys `builtin` or `override`, but a key with name `" +
key +
"` was found. This won't be picked up! The full object is: " +
JSON.stringify(tr)
@@ -362,18 +339,16 @@ class ExpandTagRendering extends Conversion<
(s) => s
)
if (state.sharedLayers.size === 0) {
- warnings.push(
- ctx +
- ": BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " +
+ ctx.warn(
+ "BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " +
name +
": layer " +
layerName +
" not found for now, but ignoring as this is a bootstrapping run. "
)
} else {
- errors.push(
- ctx +
- ": While reusing tagrendering: " +
+ ctx.err(
+ ": While reusing tagrendering: " +
name +
": layer " +
layerName +
@@ -388,9 +363,8 @@ class ExpandTagRendering extends Conversion<
)
}
candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i)
- errors.push(
- ctx +
- ": The tagRendering with identifier " +
+ ctx.err(
+ "The tagRendering with identifier " +
name +
" was not found.\n\tDid you mean one of " +
candidates.join(", ") +
@@ -413,23 +387,16 @@ class ExpandTagRendering extends Conversion<
return [tr]
}
- private convertUntilStable(
+ public convert(
spec: string | any,
- warnings: string[],
- errors: string[],
- ctx: string
+ ctx: ConversionContext
): QuestionableTagRenderingConfigJson[] {
- const trs = this.convertOnce(spec, warnings, errors, ctx)
+ const trs = this.convertOnce(spec, ctx)
const result = []
for (const tr of trs) {
if (typeof tr === "string" || tr["builtin"] !== undefined) {
- const stable = this.convertUntilStable(
- tr,
- warnings,
- errors,
- ctx + "(RECURSIVE RESOLVE)"
- )
+ const stable = this.convert(tr, ctx.inOperation("recursive_resolve"))
result.push(...stable)
} else {
result.push(tr)
@@ -451,15 +418,10 @@ class DetectInline extends DesugaringStep {
convert(
json: QuestionableTagRenderingConfigJson,
- context: string
- ): {
- result: QuestionableTagRenderingConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ context: ConversionContext
+ ): QuestionableTagRenderingConfigJson {
if (json.freeform === undefined) {
- return { result: json }
+ return json
}
let spec: Record
if (typeof json.render === "string") {
@@ -467,40 +429,33 @@ class DetectInline extends DesugaringStep {
} else {
spec = >json.render
}
- const errors: string[] = []
for (const key in spec) {
if (spec[key].indexOf("= 0) {
// We have a link element, it probably contains something that needs to be substituted...
// Let's play this safe and not inline it
- return { result: json }
+ return json
}
const fullSpecification = SpecialVisualizations.constructSpecification(spec[key])
if (fullSpecification.length > 1) {
// We found a special rendering!
if (json.freeform.inline === true) {
- errors.push(
- "At " +
- context +
- ": 'inline' is set, but the rendering contains a special visualisation...\n " +
+ context.err(
+ "'inline' is set, but the rendering contains a special visualisation...\n " +
spec[key]
)
}
json = JSON.parse(JSON.stringify(json))
json.freeform.inline = false
- return { result: json, errors }
+ return json
}
}
json = JSON.parse(JSON.stringify(json))
if (typeof json.freeform === "string") {
- errors.push("At " + context + ": 'freeform' is a string, but should be an object")
- return { result: json, errors }
+ context.err("'freeform' is a string, but should be an object")
+ return json
}
- try {
- json.freeform.inline ??= true
- } catch (e) {
- errors.push("At " + context + ": " + e.message)
- }
- return { result: json, errors }
+ json.freeform.inline ??= true
+ return json
}
}
@@ -513,15 +468,12 @@ export class AddQuestionBox extends DesugaringStep {
)
}
- convert(
- json: LayerConfigJson,
- context: string
- ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
+ convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (
json.tagRenderings === undefined ||
json.tagRenderings.some((tr) => tr["id"] === "leftover-questions")
) {
- return { result: json }
+ return json
}
json = JSON.parse(JSON.stringify(json))
const allSpecials: Exclude[] = []
@@ -537,13 +489,9 @@ export class AddQuestionBox extends DesugaringStep {
(sp) => sp.args.length === 0 || sp.args[0].trim() === ""
)
- const errors: string[] = []
- const warnings: string[] = []
if (noLabels.length > 1) {
- errors.push(
- "At " +
- context +
- ": multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
+ context.err(
+ "Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this"
)
}
@@ -569,10 +517,8 @@ export class AddQuestionBox extends DesugaringStep {
?.map((a) => a.trim())
?.filter((s) => s != "")
if (blacklisted?.length > 0 && used?.length > 0) {
- errors.push(
- "At " +
- context +
- ": the {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
+ context.err(
+ "The {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
"\n Whitelisted: " +
used.join(", ") +
"\n Blacklisted: " +
@@ -581,10 +527,8 @@ export class AddQuestionBox extends DesugaringStep {
}
for (const usedLabel of used) {
if (!allLabels.has(usedLabel)) {
- errors.push(
- "At " +
- context +
- ": this layers specifies a special question element for label `" +
+ context.err(
+ "This layers specifies a special question element for label `" +
usedLabel +
"`, but this label doesn't exist.\n" +
" Available labels are " +
@@ -607,11 +551,7 @@ export class AddQuestionBox extends DesugaringStep {
}
json.tagRenderings.push(question)
}
- return {
- result: json,
- errors,
- warnings,
- }
+ return json
}
}
@@ -627,12 +567,9 @@ export class AddEditingElements extends DesugaringStep {
this._desugaring = desugaring
}
- convert(
- json: LayerConfigJson,
- context: string
- ): { result: LayerConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
+ convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (this._desugaring.tagRenderings === null) {
- return { result: json }
+ return json
}
json = JSON.parse(JSON.stringify(json))
@@ -693,7 +630,7 @@ export class AddEditingElements extends DesugaringStep {
json.tagRenderings?.push(trc)
}
- return { result: json }
+ return json
}
}
@@ -798,21 +735,16 @@ export class ExpandRewrite extends Conversion, T[
* ]
* new ExpandRewrite().convertStrict(spec, "test") // => expected
*/
- convert(
- json: T | RewritableConfigJson,
- context: string
- ): { result: T[]; errors?: string[]; warnings?: string[]; information?: string[] } {
+ convert(json: T | RewritableConfigJson, context: ConversionContext): T[] {
if (json === null || json === undefined) {
- return { result: [] }
+ return []
}
if (json["rewrite"] === undefined) {
// not a rewrite
- return { result: [json] }
+ return [json]
}
- console.log("Rewriting at", context)
-
const rewrite = >json
const keysToRewrite = rewrite.rewrite
const ts: T[] = []
@@ -824,7 +756,9 @@ export class ExpandRewrite extends Conversion, T[
for (let j = i + 1; j < keysToRewrite.sourceString.length; j++) {
const toRewrite = keysToRewrite.sourceString[j]
if (toRewrite.indexOf(guard) >= 0) {
- throw `${context} Error in rewrite: sourcestring[${i}] is a substring of sourcestring[${j}]: ${guard} will be substituted away before ${toRewrite} is reached.`
+ context.err(
+ `sourcestring[${i}] is a substring of sourcestring[${j}]: ${guard} will be substituted away before ${toRewrite} is reached.`
+ )
}
}
}
@@ -835,7 +769,11 @@ export class ExpandRewrite extends Conversion, T[
for (let i = 0; i < rewrite.rewrite.into.length; i++) {
const into = keysToRewrite.into[i]
if (into.length !== rewrite.rewrite.sourceString.length) {
- throw `${context}.into.${i} Error in rewrite: there are ${rewrite.rewrite.sourceString.length} keys to rewrite, but entry ${i} has only ${into.length} values`
+ context
+ .enters("into", i)
+ .err(
+ `Error in rewrite: there are ${rewrite.rewrite.sourceString.length} keys to rewrite, but entry ${i} has only ${into.length} values`
+ )
}
}
}
@@ -850,7 +788,7 @@ export class ExpandRewrite extends Conversion, T[
ts.push(t)
}
- return { result: ts }
+ return ts
}
}
@@ -925,7 +863,13 @@ export class RewriteSpecial extends DesugaringStep {
* errors // => []
*/
private static convertIfNeeded(
- input: (object & { special: { type: string } }) | any,
+ input:
+ | (object & {
+ special: {
+ type: string
+ }
+ })
+ | any,
errors: string[],
context: string
): any {
@@ -1090,15 +1034,7 @@ export class RewriteSpecial extends DesugaringStep {
* const expected = {render: {'en': "{image_carousel(image)}Some footer"}}
* result // => expected
*/
- convert(
- json: TagRenderingConfigJson,
- context: string
- ): {
- result: TagRenderingConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
const errors = []
json = Utils.Clone(json)
const paths: ConfigMeta[] = tagrenderingconfigmeta
@@ -1111,10 +1047,7 @@ export class RewriteSpecial extends DesugaringStep {
)
}
- return {
- result: json,
- errors,
- }
+ return json
}
}
@@ -1126,51 +1059,42 @@ class ExpandIconBadges extends DesugaringStep {
this._expand = new ExpandTagRendering(state, layer)
}
- convert(
- json: PointRenderingConfigJson,
- context: string
- ): {
- result: PointRenderingConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: PointRenderingConfigJson, context: ConversionContext): PointRenderingConfigJson {
if (!json["iconBadges"]) {
- return { result: json }
+ return json
}
const badgesJson = json.iconBadges
- const iconBadges: { if: TagConfigJson; then: string | TagRenderingConfigJson }[] = []
+ const iconBadges: {
+ if: TagConfigJson
+ then: string | TagRenderingConfigJson
+ }[] = []
const errs: string[] = []
const warns: string[] = []
for (let i = 0; i < badgesJson.length; i++) {
- const iconBadge: { if: TagConfigJson; then: string | TagRenderingConfigJson } =
- badgesJson[i]
- const { errors, result, warnings } = this._expand.convert(
+ const iconBadge: {
+ if: TagConfigJson
+ then: string | TagRenderingConfigJson
+ } = badgesJson[i]
+ const expanded = this._expand.convert(
iconBadge.then,
- context + ".iconBadges[" + i + "]"
+ context.enters("iconBadges", i)
)
- errs.push(...errors)
- warns.push(...warnings)
- if (result === undefined) {
+ if (expanded === undefined) {
iconBadges.push(iconBadge)
continue
}
iconBadges.push(
- ...result.map((resolved) => ({
+ ...expanded.map((resolved) => ({
if: iconBadge.if,
then: resolved,
}))
)
}
- return {
- result: { ...json, iconBadges },
- errors: errs,
- warnings: warns,
- }
+ return { ...json, iconBadges }
}
}
@@ -1196,15 +1120,7 @@ class SetFullNodeDatabase extends DesugaringStep {
)
}
- convert(
- json: LayerConfigJson,
- context: string
- ): {
- result: LayerConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
const needsSpecial =
json.tagRenderings?.some((tr) => {
if (typeof tr === "string") {
@@ -1214,12 +1130,10 @@ class SetFullNodeDatabase extends DesugaringStep {
return specs?.some((sp) => sp.needsNodeDatabase)
}) ?? false
if (!needsSpecial) {
- return { result: json }
- }
- return {
- result: { ...json, fullNodeDatabase: true },
- information: ["Layer " + json.id + " needs the fullNodeDatabase"],
+ return json
}
+ context.info("Layer " + json.id + " needs the fullNodeDatabase")
+ return { ...json, fullNodeDatabase: true }
}
}
@@ -1235,9 +1149,9 @@ export class AddMiniMap extends DesugaringStep {
this._state = state
}
- convert(layerConfig: LayerConfigJson, context: string): { result: LayerConfigJson } {
+ convert(layerConfig: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (!layerConfig.tagRenderings || layerConfig.source === "special") {
- return { result: layerConfig }
+ return layerConfig
}
const state = this._state
const hasMinimap = ValidationUtils.hasSpecialVisualisation(layerConfig, "minimap")
@@ -1254,9 +1168,7 @@ export class AddMiniMap extends DesugaringStep {
}
}
- return {
- result: layerConfig,
- }
+ return layerConfig
}
}
@@ -1274,30 +1186,22 @@ class ExpandMarkerRenderings extends DesugaringStep {
this._state = state
}
- convert(
- json: IconConfigJson,
- context: string
- ): {
- result: IconConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: IconConfigJson, context: ConversionContext): IconConfigJson {
const expander = new ExpandTagRendering(this._state, this._layer)
const result: IconConfigJson = { icon: undefined, color: undefined }
const errors: string[] = []
const warnings: string[] = []
if (json.icon && json.icon["builtin"]) {
- result.icon = expander.convertJoin(json.icon, context, errors, warnings)[0]
+ result.icon = expander.convert(json.icon, context.enter("icon"))[0]
} else {
result.icon = json.icon
}
if (json.color && json.color["builtin"]) {
- result.color = expander.convertJoin(json.color, context, errors, warnings)[0]
+ result.color = expander.convert(json.color, context.enter("color"))[0]
} else {
result.color = json.color
}
- return { result, errors, warnings }
+ return result
}
}
diff --git a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts
index 33246cd550..2fe1527be1 100644
--- a/src/Models/ThemeConfig/Conversion/PrepareTheme.ts
+++ b/src/Models/ThemeConfig/Conversion/PrepareTheme.ts
@@ -1,6 +1,7 @@
import {
Concat,
Conversion,
+ ConversionContext,
DesugaringContext,
DesugaringStep,
Each,
@@ -33,12 +34,7 @@ class SubstituteLayer extends Conversion a[1] - b[1])
const ids = withDistance.map((n) => n[0])
// Known builtin layers are "+.join(",")+"\n For more information, see "
- errors.push(`${context}: The layer with name ${name} was not found as a builtin layer. Perhaps you meant ${ids[0]}, ${ids[1]} or ${ids[2]}?
+ context.err(`The layer with name ${name} was not found as a builtin layer. Perhaps you meant ${ids[0]}, ${ids[1]} or ${ids[2]}?
For an overview of all available layers, refer to https://github.com/pietervdvn/MapComplete/blob/develop/Docs/BuiltinLayers.md`)
}
@@ -58,119 +54,101 @@ class SubstituteLayer extends Conversion 0
+ ) {
+ context.err(
+ `When overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
+ )
+ }
+ try {
+ Utils.Merge(json["override"], found)
+ layers.push(found)
+ } catch (e) {
+ context.err(
+ `Could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
+ json["override"]
+ )}`
+ )
}
- const layers = []
- for (const name of names) {
- const found = Utils.Clone(state.sharedLayers.get(name))
- if (found === undefined) {
- reportNotFound(name)
- continue
- }
- if (
- json["override"]["tagRenderings"] !== undefined &&
- (found["tagRenderings"] ?? []).length > 0
- ) {
- errors.push(
- `At ${context}: when overriding a layer, an override is not allowed to override into tagRenderings. Use "+tagRenderings" or "tagRenderings+" instead to prepend or append some questions.`
- )
- }
- try {
- Utils.Merge(json["override"], found)
- layers.push(found)
- } catch (e) {
- errors.push(
- `At ${context}: could not apply an override due to: ${e}.\nThe override is: ${JSON.stringify(
- json["override"]
- )}`
- )
- }
-
- if (json["hideTagRenderingsWithLabels"]) {
- const hideLabels: Set = new Set(json["hideTagRenderingsWithLabels"])
- // These labels caused at least one deletion
- const usedLabels: Set = new Set()
- const filtered = []
- for (const tr of found.tagRenderings) {
- const labels = tr["labels"]
- if (labels !== undefined) {
- const forbiddenLabel = labels.findIndex((l) => hideLabels.has(l))
- if (forbiddenLabel >= 0) {
- usedLabels.add(labels[forbiddenLabel])
- information.push(
- context +
- ": Dropping tagRendering " +
- tr["id"] +
- " as it has a forbidden label: " +
- labels[forbiddenLabel]
- )
- continue
- }
- }
-
- if (hideLabels.has(tr["id"])) {
- usedLabels.add(tr["id"])
- information.push(
- context +
- ": Dropping tagRendering " +
+ if (json["hideTagRenderingsWithLabels"]) {
+ const hideLabels: Set = new Set(json["hideTagRenderingsWithLabels"])
+ // These labels caused at least one deletion
+ const usedLabels: Set = new Set()
+ const filtered = []
+ for (const tr of found.tagRenderings) {
+ const labels = tr["labels"]
+ if (labels !== undefined) {
+ const forbiddenLabel = labels.findIndex((l) => hideLabels.has(l))
+ if (forbiddenLabel >= 0) {
+ usedLabels.add(labels[forbiddenLabel])
+ context.info(
+ "Dropping tagRendering " +
tr["id"] +
- " as its id is a forbidden label"
+ " as it has a forbidden label: " +
+ labels[forbiddenLabel]
)
continue
}
-
- if (hideLabels.has(tr["group"])) {
- usedLabels.add(tr["group"])
- information.push(
- context +
- ": Dropping tagRendering " +
- tr["id"] +
- " as its group `" +
- tr["group"] +
- "` is a forbidden label"
- )
- continue
- }
-
- filtered.push(tr)
}
- const unused = Array.from(hideLabels).filter((l) => !usedLabels.has(l))
- if (unused.length > 0) {
- errors.push(
- "This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
- unused.join(", ") +
- "\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
+
+ if (hideLabels.has(tr["id"])) {
+ usedLabels.add(tr["id"])
+ context.info(
+ "Dropping tagRendering " + tr["id"] + " as its id is a forbidden label"
)
+ continue
}
- found.tagRenderings = filtered
- }
- }
- return {
- result: layers,
- errors,
- information,
- }
- }
- return {
- result: [json],
- errors,
+ if (hideLabels.has(tr["group"])) {
+ usedLabels.add(tr["group"])
+ context.info(
+ "Dropping tagRendering " +
+ tr["id"] +
+ " as its group `" +
+ tr["group"] +
+ "` is a forbidden label"
+ )
+ continue
+ }
+
+ filtered.push(tr)
+ }
+ const unused = Array.from(hideLabels).filter((l) => !usedLabels.has(l))
+ if (unused.length > 0) {
+ context.err(
+ "This theme specifies that certain tagrenderings have to be removed based on forbidden layers. One or more of these layers did not match any tagRenderings and caused no deletions: " +
+ unused.join(", ") +
+ "\n This means that this label can be removed or that the original tagRendering that should be deleted does not have this label anymore"
+ )
+ }
+ found.tagRenderings = filtered
+ }
}
+ return layers
}
}
@@ -186,12 +164,7 @@ class AddDefaultLayers extends DesugaringStep {
this._state = state
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
- const errors = []
- const warnings = []
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const state = this._state
json.layers = [...json.layers]
const alreadyLoaded = new Set(json.layers.map((l) => l["id"]))
@@ -199,11 +172,11 @@ class AddDefaultLayers extends DesugaringStep {
for (const layerName of Constants.added_by_default) {
const v = state.sharedLayers.get(layerName)
if (v === undefined) {
- errors.push("Default layer " + layerName + " not found")
+ context.err("Default layer " + layerName + " not found")
continue
}
if (alreadyLoaded.has(v.id)) {
- warnings.push(
+ context.warn(
"Layout " +
context +
" already has a layer with name " +
@@ -215,11 +188,7 @@ class AddDefaultLayers extends DesugaringStep {
json.layers.push(v)
}
- return {
- result: json,
- errors,
- warnings,
- }
+ return json
}
}
@@ -232,21 +201,13 @@ class AddImportLayers extends DesugaringStep {
)
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
if (!(json.enableNoteImports ?? true)) {
- return {
- warnings: [
- "Not creating a note import layers for theme " +
- json.id +
- " as they are disabled",
- ],
- result: json,
- }
+ context.info(
+ "Not creating a note import layers for theme " + json.id + " as they are disabled"
+ )
+ return json
}
- const errors = []
json = { ...json }
const allLayers: LayerConfigJson[] = json.layers
@@ -278,20 +239,17 @@ class AddImportLayers extends DesugaringStep {
try {
const importLayerResult = creator.convert(
layer,
- context + ".(noteimportlayer)[" + i1 + "]"
+ context.inOperation(this.name).enter(i1)
)
- if (importLayerResult.result !== undefined) {
- json.layers.push(importLayerResult.result)
+ if (importLayerResult !== undefined) {
+ json.layers.push(importLayerResult)
}
} catch (e) {
- errors.push("Could not generate an import-layer for " + layer.id + " due to " + e)
+ context.err("Could not generate an import-layer for " + layer.id + " due to " + e)
}
}
- return {
- errors,
- result: json,
- }
+ return json
}
}
@@ -304,17 +262,9 @@ class AddContextToTranslationsInLayout extends DesugaringStep
)
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): {
- result: LayoutConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const conversion = new AddContextToTranslations("themes:")
- return conversion.convert(json, json.id)
+ return conversion.convert(json, context)
}
}
@@ -327,13 +277,10 @@ class ApplyOverrideAll extends DesugaringStep {
)
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const overrideAll = json.overrideAll
if (overrideAll === undefined) {
- return { result: json, warnings: [], errors: [] }
+ return json
}
json = { ...json }
@@ -346,8 +293,7 @@ class ApplyOverrideAll extends DesugaringStep {
newLayers.push(layer)
}
json.layers = newLayers
-
- return { result: json, warnings: [], errors: [] }
+ return json
}
}
@@ -458,18 +404,14 @@ class AddDependencyLayersToTheme extends DesugaringStep {
return dependenciesToAdd
}
- convert(
- theme: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; information: string[] } {
+ convert(theme: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const state = this._state
const allKnownLayers: Map = state.sharedLayers
const knownTagRenderings: Map = state.tagRenderings
- const information = []
const layers: LayerConfigJson[] = theme.layers // Layers should be expanded at this point
knownTagRenderings.forEach((value, key) => {
- value.id = key
+ value["id"] = key
})
const dependencies = AddDependencyLayersToTheme.CalculateDependencies(
@@ -481,23 +423,16 @@ class AddDependencyLayersToTheme extends DesugaringStep {
}
if (dependencies.length > 0) {
for (const dependency of dependencies) {
- information.push(
- context +
- ": added " +
- dependency.config.id +
- " to the theme. " +
- dependency.reason
+ context.info(
+ "Added " + dependency.config.id + " to the theme. " + dependency.reason
)
}
}
layers.unshift(...dependencies.map((l) => l.config))
return {
- result: {
- ...theme,
- layers: layers,
- },
- information,
+ ...theme,
+ layers: layers,
}
}
}
@@ -510,17 +445,9 @@ class PreparePersonalTheme extends DesugaringStep {
this._state = state
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): {
- result: LayoutConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
if (json.id !== "personal") {
- return { result: json }
+ return json
}
// The only thing this _really_ does, is adding the layer-ids into 'layers'
@@ -529,10 +456,8 @@ class PreparePersonalTheme extends DesugaringStep {
json.layers = Array.from(this._state.sharedLayers.keys())
.filter((l) => this._state.sharedLayers.get(l).source !== null)
.filter((l) => this._state.publicLayers.has(l))
- return {
- result: json,
- information: ["The personal theme has " + json.layers.length + " public layers"],
- }
+ context.info("The personal theme has " + json.layers.length + " public layers")
+ return json
}
}
@@ -545,19 +470,10 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep
)
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): {
- result: LayoutConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
if (json.hideFromOverview === true) {
- return { result: json }
+ return json
}
- const warnings = []
for (const layer of json.layers) {
if (typeof layer === "string") {
continue
@@ -570,18 +486,15 @@ class WarnForUnsubstitutedLayersInTheme extends DesugaringStep
continue
}
- const wrn =
+ context.warn(
"The theme " +
- json.id +
- " has an inline layer: " +
- layer["id"] +
- ". This is discouraged."
- warnings.push(wrn)
- }
- return {
- result: json,
- warnings,
+ json.id +
+ " has an inline layer: " +
+ layer["id"] +
+ ". This is discouraged."
+ )
}
+ return json
}
}
@@ -616,29 +529,25 @@ export class PrepareTheme extends Fuse {
this.state = state
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const result = super.convert(json, context)
if (this.state.publicLayers.size === 0) {
// THis is a bootstrapping run, no need to already set this flag
return result
}
- const needsNodeDatabase = result.result.layers?.some((l: LayerConfigJson) =>
- l.tagRenderings?.some((tr: TagRenderingConfigJson) =>
- ValidationUtils.getSpecialVisualisations(tr)?.some(
+ const needsNodeDatabase = result.layers?.some((l: LayerConfigJson) =>
+ l.tagRenderings?.some((tr) =>
+ ValidationUtils.getSpecialVisualisations(tr)?.some(
(special) => special.needsNodeDatabase
)
)
)
if (needsNodeDatabase) {
- result.information.push(
- context +
- ": setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
+ context.info(
+ "Setting 'enableNodeDatabase' as this theme uses a special visualisation which needs to keep track of _all_ nodes"
)
- result.result.enableNodeDatabase = true
+ result.enableNodeDatabase = true
}
return result
diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts
index a174716090..bb6c7856f6 100644
--- a/src/Models/ThemeConfig/Conversion/Validation.ts
+++ b/src/Models/ThemeConfig/Conversion/Validation.ts
@@ -1,4 +1,4 @@
-import { DesugaringStep, Each, Fuse, On } from "./Conversion"
+import { ConversionContext, DesugaringStep, Each, Fuse, On } from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import LayerConfig from "../LayerConfig"
import { Utils } from "../../../Utils"
@@ -33,12 +33,7 @@ class ValidateLanguageCompleteness extends DesugaringStep {
this._languages = languages ?? ["en"]
}
- convert(
- obj: any,
- context: string
- ): { result: LayerConfig; errors: string[]; warnings: string[] } {
- const errors = []
- const warnings: string[] = []
+ convert(obj: any, context: ConversionContext): LayerConfig {
const translations = Translation.ExtractAllTranslationsFrom(obj)
for (const neededLanguage of this._languages) {
translations
@@ -48,23 +43,20 @@ class ValidateLanguageCompleteness extends DesugaringStep {
t.tr.translations["*"] === undefined
)
.forEach((missing) => {
- errors.push(
- context +
- "A theme should be translation-complete for " +
- neededLanguage +
- ", but it lacks a translation for " +
- missing.context +
- ".\n\tThe known translation is " +
- missing.tr.textFor("en")
- )
+ 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")
+ )
})
}
- return {
- result: obj,
- errors,
- warnings,
- }
+ return obj
}
}
@@ -84,58 +76,47 @@ export class DoesImageExist extends DesugaringStep {
this.doesPathExist = checkExistsSync
}
- convert(
- image: string,
- context: string
- ): { result: string; errors?: string[]; warnings?: string[]; information?: string[] } {
+ convert(image: string, context: ConversionContext): string {
if (this._ignore?.has(image)) {
- return { result: image }
+ return image
}
- const errors = []
- const warnings = []
- const information = []
if (image.indexOf("{") >= 0) {
- information.push("Ignoring image with { in the path: " + image)
- return { result: image }
+ context.info("Ignoring image with { in the path: " + image)
+ return image
}
if (image === "assets/SocialImage.png") {
- return { result: image }
+ return image
}
if (image.match(/[a-z]*/)) {
if (Svg.All[image + ".svg"] !== undefined) {
// This is a builtin img, e.g. 'checkmark' or 'crosshair'
- return { result: image }
+ return image
}
}
if (image.startsWith("<") && image.endsWith(">")) {
// This is probably HTML, you're on your own here
- return { result: image }
+ return image
}
if (!this._knownImagePaths.has(image)) {
if (this.doesPathExist === undefined) {
- errors.push(
+ context.err(
`Image with path ${image} not found or not attributed; it is used in ${context}`
)
} else if (!this.doesPathExist(image)) {
- errors.push(
+ context.err(
`Image with path ${image} does not exist; it is used in ${context}.\n Check for typo's and missing directories in the path.`
)
} else {
- errors.push(
+ 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`
)
}
}
- return {
- result: image,
- errors,
- warnings,
- information,
- }
+ return image
}
}
@@ -165,28 +146,20 @@ class ValidateTheme extends DesugaringStep {
}
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): { result: LayoutConfigJson; errors: string[]; warnings: string[]; information: string[] } {
- const errors: string[] = []
- const warnings: string[] = []
- const information: string[] = []
-
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const theme = new LayoutConfig(json, this._isBuiltin)
-
{
// Legacy format checks
if (this._isBuiltin) {
if (json["units"] !== undefined) {
- errors.push(
+ context.err(
"The theme " +
json.id +
" has units defined - these should be defined on the layer instead. (Hint: use overrideAll: { '+units': ... }) "
)
}
if (json["roamingRenderings"] !== undefined) {
- errors.push(
+ context.err(
"Theme " +
json.id +
" contains an old 'roamingRenderings'. Use an 'overrideAll' instead"
@@ -196,10 +169,10 @@ class ValidateTheme extends DesugaringStep {
}
if (this._isBuiltin && this._extractImages !== undefined) {
// Check images: are they local, are the licenses there, is the theme icon square, ...
- const images = this._extractImages.convertStrict(json, "validation")
+ const images = this._extractImages.convert(json, context.inOperation("ValidateTheme"))
const remoteImages = images.filter((img) => img.path.indexOf("http") == 0)
for (const remoteImage of remoteImages) {
- errors.push(
+ context.err(
"Found a remote image: " +
remoteImage +
" in theme " +
@@ -208,20 +181,14 @@ class ValidateTheme extends DesugaringStep {
)
}
for (const image of images) {
- this._validateImage.convertJoin(
- image.path,
- context === undefined ? "" : ` in the theme ${context} at ${image.context}`,
- errors,
- warnings,
- information
- )
+ this._validateImage.convert(image.path, context.enters(image.context))
}
}
try {
if (this._isBuiltin) {
if (theme.id !== theme.id.toLowerCase()) {
- errors.push("Theme ids should be in lowercase, but it is " + theme.id)
+ context.err("Theme ids should be in lowercase, but it is " + theme.id)
}
const filename = this._path.substring(
@@ -229,7 +196,7 @@ class ValidateTheme extends DesugaringStep {
this._path.length - 5
)
if (theme.id !== filename) {
- errors.push(
+ context.err(
"Theme ids should be the same as the name.json, but we got id: " +
theme.id +
" and filename " +
@@ -239,54 +206,41 @@ class ValidateTheme extends DesugaringStep {
")"
)
}
- this._validateImage.convertJoin(
- theme.icon,
- context + ".icon",
- errors,
- warnings,
- information
- )
+ this._validateImage.convert(theme.icon, context.enter("icon"))
}
const dups = Utils.Duplicates(json.layers.map((layer) => layer["id"]))
if (dups.length > 0) {
- errors.push(
+ context.err(
`The theme ${json.id} defines multiple layers with id ${dups.join(", ")}`
)
}
if (json["mustHaveLanguage"] !== undefined) {
- const checked = new ValidateLanguageCompleteness(
- ...json["mustHaveLanguage"]
- ).convert(theme, theme.id)
-
- errors.push(...checked.errors)
+ new ValidateLanguageCompleteness(...json["mustHaveLanguage"]).convert(
+ theme,
+ context
+ )
}
if (!json.hideFromOverview && theme.id !== "personal" && this._isBuiltin) {
// The first key in the the title-field must be english, otherwise the title in the loading page will be the different language
const targetLanguage = theme.title.SupportedLanguages()[0]
if (targetLanguage !== "en") {
- warnings.push(
+ 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`
)
}
// Official, public themes must have a full english translation
- const checked = new ValidateLanguageCompleteness("en").convert(theme, theme.id)
- errors.push(...checked.errors)
+ new ValidateLanguageCompleteness("en").convert(theme, context)
}
} catch (e) {
- errors.push(e)
+ context.err(e)
}
if (theme.id !== "personal") {
- new DetectDuplicatePresets().convertJoin(theme, context, errors, warnings, information)
+ new DetectDuplicatePresets().convert(theme, context)
}
- return {
- result: json,
- errors,
- warnings,
- information,
- }
+ return json
}
}
@@ -314,16 +268,12 @@ class OverrideShadowingCheck extends DesugaringStep {
)
}
- convert(
- json: LayoutConfigJson,
- _: string
- ): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
const overrideAll = json.overrideAll
if (overrideAll === undefined) {
- return { result: json }
+ return json
}
- const errors = []
const withOverride = json.layers.filter((l) => l["override"] !== undefined)
for (const layer of withOverride) {
@@ -342,12 +292,12 @@ class OverrideShadowingCheck extends DesugaringStep {
" has a shadowed property: " +
key +
" is overriden by overrideAll of the theme"
- errors.push(w)
+ context.err(w)
}
}
}
- return { result: json, errors }
+ return json
}
}
@@ -356,28 +306,14 @@ class MiscThemeChecks extends DesugaringStep {
super("Miscelleanous checks on the theme", [], "MiscThemesChecks")
}
- convert(
- json: LayoutConfigJson,
- context: string
- ): {
- result: LayoutConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
- const warnings = []
- const errors = []
+ convert(json: LayoutConfigJson, context: ConversionContext): LayoutConfigJson {
if (json.id !== "personal" && (json.layers === undefined || json.layers.length === 0)) {
- errors.push("The theme " + json.id + " has no 'layers' defined (" + context + ")")
+ context.err("The theme " + json.id + " has no 'layers' defined")
}
if (json.socialImage === "") {
- warnings.push("Social image for theme " + json.id + " is the emtpy string")
- }
- return {
- result: json,
- warnings,
- errors,
+ context.warn("Social image for theme " + json.id + " is the emtpy string")
}
+ return json
}
}
@@ -400,17 +336,9 @@ export class DetectConflictingAddExtraTags extends DesugaringStep 0)) {
- return { result: json }
+ return json
}
const tagRendering = new TagRenderingConfig(json)
@@ -438,10 +366,7 @@ export class DetectConflictingAddExtraTags extends DesugaringStep 1
* r.errors[0].indexOf("The mapping key=value&x=y is fully matched by a previous mapping (namely 0)") >= 0 // => true
*/
- convert(
- json: TagRenderingConfigJson,
- context: string
- ): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
- const errors = []
- const warnings = []
+ convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
if (json.mappings === undefined || json.mappings.length === 0) {
- return { result: json }
+ return json
}
const defaultProperties = {}
for (const calculatedTagName of this._calculatedTagNames) {
@@ -547,12 +467,12 @@ export class DetectShadowedMappings extends DesugaringStep 0 // => true
* r.errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0) // => true
*/
- convert(
- json: TagRenderingConfigJson,
- context: string
- ): {
- result: TagRenderingConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
- const errors: string[] = []
- const warnings: string[] = []
- const information: string[] = []
+ convert(json: TagRenderingConfigJson, context: ConversionContext): TagRenderingConfigJson {
if (json.mappings === undefined || json.mappings.length === 0) {
- return { result: json }
+ return json
}
const ignoreToken = "ignore-image-in-then"
for (let i = 0; i < json.mappings.length; i++) {
const mapping = json.mappings[i]
const ignore = mapping["#"]?.indexOf(ignoreToken) >= 0
const images = Utils.Dedup(Translations.T(mapping.then)?.ExtractImages() ?? [])
- const ctx = `${context}.mappings[${i}]`
+ const ctx = context.enters("mappings", i)
if (images.length > 0) {
if (!ignore) {
- errors.push(
- `${ctx}: A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": \` instead. The images found are ${images.join(
+ ctx.err(
+ `A mapping has an image in the 'then'-clause. Remove the image there and use \`"icon": \` instead. The images found are ${images.join(
", "
)}. (This check can be turned of by adding "#": "${ignoreToken}" in the mapping, but this is discouraged`
)
} else {
- information.push(
- `${ctx}: Ignored image ${images.join(
+ ctx.info(
+ `Ignored image ${images.join(
", "
)} in 'then'-clause of a mapping as this check has been disabled`
)
for (const image of images) {
- this._doesImageExist.convertJoin(image, ctx, errors, warnings, information)
+ this._doesImageExist.convert(image, ctx)
}
}
} else if (ignore) {
- warnings.push(`${ctx}: unused '${ignoreToken}' - please remove this`)
+ ctx.warn(`Unused '${ignoreToken}' - please remove this`)
}
}
- return {
- errors,
- warnings,
- information,
- result: json,
- }
+ return json
}
}
@@ -701,20 +601,12 @@ class ValidatePossibleLinks extends DesugaringStep,
- context: string
- ): {
- result: string | Record
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
- const errors = []
+ context: ConversionContext
+ ): string | Record {
if (typeof json === "string") {
if (this.isTabnabbingProne(json)) {
- errors.push(
- "At " +
- context +
- ": the string " +
+ context.err(
+ "The string " +
json +
" has a link targeting `_blank`, but it doesn't have `rel='noopener'` set. This gives rise to reverse tabnapping"
)
@@ -722,16 +614,13 @@ class ValidatePossibleLinks extends DesugaringStep {
convert(
json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson,
- context: string
- ): {
- result: TagRenderingConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
- const warnings = []
- const errors = []
+ context: ConversionContext
+ ): TagRenderingConfigJson {
if (json["special"] !== undefined) {
- errors.push(
- "At " +
- context +
- ': detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
+ context.err(
+ 'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`'
)
}
if (json["group"]) {
- errors.push(
- "At " +
- context +
- ': groups are deprecated, use `"label": ["' +
- json["group"] +
- '"]` instead'
- )
+ context.err('Groups are deprecated, use `"label": ["' + json["group"] + '"]` instead')
}
const freeformType = json["freeform"]?.["type"]
if (freeformType) {
if (Validators.availableTypes.indexOf(freeformType) < 0) {
- throw (
- "At " +
- context +
- ".freeform.type is an unknown type: " +
- freeformType +
- "; try one of " +
- Validators.availableTypes.join(", ")
- )
+ context
+ .enters("freeform", "type")
+ .err(
+ "Unknown type: " +
+ freeformType +
+ "; try one of " +
+ Validators.availableTypes.join(", ")
+ )
}
}
- return {
- result: json,
- errors,
- warnings,
- }
+ return json
}
}
@@ -828,24 +698,21 @@ export class ValidateLayer extends DesugaringStep {
this._doesImageExist = doesImageExist
}
- convert(
- json: LayerConfigJson,
- context: string
- ): { result: LayerConfigJson; errors: string[]; warnings?: string[]; information?: string[] } {
- const errors = []
- const warnings = []
- const information = []
- context = "While validating a layer: " + context
+ convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
+ context = context.inOperation(this.name)
if (typeof json === "string") {
- errors.push(context + ": This layer hasn't been expanded: " + json)
- return {
- result: null,
- errors,
- }
+ context.err("This layer hasn't been expanded: " + json)
+ return null
}
- const layerConfig = new LayerConfig(json, "validation", true)
- for (const [attribute, code, isStrict] of layerConfig.calculatedTags ?? []) {
+ let layerConfig: LayerConfig
+ try {
+ layerConfig = new LayerConfig(json, "validation", true)
+ } catch (e) {
+ context.err(e)
+ return undefined
+ }
+ for (const [_, code, __] of layerConfig.calculatedTags ?? []) {
try {
new Function("feat", "return " + code + ";")
} catch (e) {
@@ -855,9 +722,8 @@ export class ValidateLayer extends DesugaringStep {
if (json.source === "special") {
if (!Constants.priviliged_layers.find((x) => x == json.id)) {
- errors.push(
- context +
- ": layer " +
+ context.err(
+ "Layer " +
json.id +
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer"
)
@@ -866,30 +732,27 @@ export class ValidateLayer extends DesugaringStep {
if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) {
if (json.title === undefined && json.source !== "special:library") {
- errors.push(
- 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."
+ context.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."
)
}
if (json.title === null) {
- information.push(
- context +
- ": title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
+ context.info(
+ "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set."
)
}
}
if (json["builtin"] !== undefined) {
- errors.push(context + ": This layer hasn't been expanded: " + json)
- return {
- result: null,
- errors,
- }
+ context.err("This layer hasn't been expanded: " + json)
+ return null
}
if (json.minzoom > Constants.minZoomLevelToAddNewPoint) {
- ;(json.presets?.length > 0 ? errors : warnings).push(
- `At ${context}: minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates`
+ const c = context.enter("minzoom")
+ const w = json.presets?.length > 0 ? c.err : c.warn
+ w(
+ `Minzoom is ${json.minzoom}, this should be at most ${Constants.minZoomLevelToAddNewPoint} as a preset is set. Why? Selecting the pin for a new item will zoom in to level before adding the point. Having a greater minzoom will hide the points, resulting in possible duplicates`
)
}
{
@@ -898,19 +761,17 @@ export class ValidateLayer extends DesugaringStep {
Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"])))
)
if (duplicates.length > 0) {
- console.log(json.tagRenderings)
- errors.push(
- "At " +
- context +
- ": some tagrenderings have a duplicate id: " +
- duplicates.join(", ")
- )
+ context
+ .enter("tagRenderings")
+ .err("Some tagrenderings have a duplicate id: " + duplicates.join(", "))
}
}
if (json.deletion !== undefined && json.deletion instanceof DeleteConfig) {
if (json.deletion.softDeletionTags === undefined) {
- warnings.push("No soft-deletion tags in deletion block for layer " + json.id)
+ context
+ .enter("deletion")
+ .warn("No soft-deletion tags in deletion block for layer " + json.id)
}
}
@@ -919,7 +780,7 @@ export class ValidateLayer extends DesugaringStep {
// Some checks for legacy elements
if (json["overpassTags"] !== undefined) {
- errors.push(
+ context.err(
"Layer " +
json.id +
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": }\' instead of "overpassTags": (note: this isn\'t your fault, the custom theme generator still spits out the old format)'
@@ -938,18 +799,13 @@ export class ValidateLayer extends DesugaringStep {
]
for (const forbiddenKey of forbiddenTopLevel) {
if (json[forbiddenKey] !== undefined)
- errors.push(
- context +
- ": layer " +
- json.id +
- " still has a forbidden key " +
- forbiddenKey
+ context.err(
+ "Layer " + json.id + " still has a forbidden key " + forbiddenKey
)
}
if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
- errors.push(
- context +
- ": layer " +
+ context.err(
+ "Layer " +
json.id +
" contains an old 'hideUnderlayingFeaturesMinPercentage'"
)
@@ -959,14 +815,14 @@ export class ValidateLayer extends DesugaringStep {
json.isShown !== undefined &&
(json.isShown["render"] !== undefined || json.isShown["mappings"] !== undefined)
) {
- warnings.push(context + " has a tagRendering as `isShown`")
+ context.warn("Has a tagRendering as `isShown`")
}
}
if (this._isBuiltin) {
// Check location of layer file
const expected: string = `assets/layers/${json.id}/${json.id}.json`
if (this._path != undefined && this._path.indexOf(expected) < 0) {
- errors.push(
+ context.err(
"Layer is in an incorrect place. The path is " +
this._path +
", but expected " +
@@ -984,11 +840,13 @@ export class ValidateLayer extends DesugaringStep {
emptyIndexes.push(i)
}
}
- errors.push(
- `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${context}.tagRenderings.[${emptyIndexes.join(
- ","
- )}])`
- )
+ context
+ .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(
@@ -997,29 +855,26 @@ export class ValidateLayer extends DesugaringStep {
.filter((id) => id !== "questions")
)
if (duplicateIds.length > 0 && !Utils.runningFromConsole) {
- errors.push(
- `Some tagRenderings have a duplicate id: ${duplicateIds} (at ${context}.tagRenderings)`
- )
+ context
+ .enter("tagRenderings")
+ .err(`Some tagRenderings have a duplicate id: ${duplicateIds}`)
}
if (json.description === undefined) {
if (typeof json.source === null) {
- errors.push(context + ": A priviliged layer must have a description")
+ context.err("A priviliged layer must have a description")
} else {
- warnings.push(context + ": A builtin layer should have a description")
+ context.warn("A builtin layer should have a description")
}
}
}
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 ?? []))
+ new On("filter", new Each(new ValidateFilter())).convert(json, context)
}
if (json.tagRenderings !== undefined) {
- const r = new On(
+ new On(
"tagRenderings",
new Each(
new ValidateTagRenderings(json, this._doesImageExist, {
@@ -1027,9 +882,6 @@ export class ValidateLayer extends DesugaringStep {
})
)
).convert(json, context)
- warnings.push(...(r.warnings ?? []))
- errors.push(...(r.errors ?? []))
- information.push(...(r.information ?? []))
}
{
@@ -1037,10 +889,8 @@ export class ValidateLayer extends DesugaringStep {
(mr) => mr["icon"] !== undefined && mr["icon"]["condition"] !== undefined
)
if (hasCondition?.length > 0) {
- errors.push(
- "At " +
- 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" +
+ context.err(
+ "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, " ")
)
}
@@ -1048,7 +898,7 @@ export class ValidateLayer extends DesugaringStep {
if (json.presets !== undefined) {
if (typeof json.source === "string") {
- throw "A special layer cannot have presets"
+ context.err("A special layer cannot have presets")
}
// Check that a preset will be picked up by the layer itself
const baseTags = TagUtils.Tag(json.source["osmTags"])
@@ -1063,28 +913,22 @@ export class ValidateLayer extends DesugaringStep {
}
const doMatch = baseTags.matchesProperties(properties)
if (!doMatch) {
- errors.push(
- context +
- ".presets[" +
- 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: " +
- JSON.stringify(properties) +
- "\n The required tags are: " +
- baseTags.asHumanString(false, false, {})
- )
+ context
+ .enters("presets", i)
+ .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: " +
+ JSON.stringify(properties) +
+ "\n The required tags are: " +
+ baseTags.asHumanString(false, false, {})
+ )
}
}
}
} catch (e) {
- errors.push(e)
+ context.err(e)
}
- return {
- result: json,
- errors,
- warnings,
- information,
- }
+ return json
}
}
@@ -1093,33 +937,27 @@ export class ValidateFilter extends DesugaringStep {
super("Detect common errors in the filters", [], "ValidateFilter")
}
- convert(
- filter: FilterConfigJson,
- context: string
- ): {
- result: FilterConfigJson
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+ convert(filter: FilterConfigJson, context: ConversionContext): FilterConfigJson {
if (typeof filter === "string") {
// Calling another filter, we skip
- return { result: filter }
+ return filter
}
- const errors = []
for (const option of filter.options) {
for (let i = 0; i < option.fields?.length ?? 0; i++) {
const field = option.fields[i]
const type = field.type ?? "string"
if (Validators.availableTypes.find((t) => t === type) === undefined) {
- const err = `Invalid filter: ${type} is not a valid textfield type (at ${context}.fields[${i}])\n\tTry one of ${Array.from(
- Validators.availableTypes
- ).join(",")}`
- errors.push(err)
+ context
+ .enters("fields", i)
+ .err(
+ `Invalid filter: ${type} is not a valid textfield type.\n\tTry one of ${Array.from(
+ Validators.availableTypes
+ ).join(",")}`
+ )
}
}
}
- return { result: filter, errors }
+ return filter
}
}
@@ -1137,17 +975,8 @@ export class DetectDuplicateFilters extends DesugaringStep<{
convert(
json: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] },
- __: string
- ): {
- result: { layers: LayerConfigJson[]; themes: LayoutConfigJson[] }
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
- const errors: string[] = []
- const warnings: string[] = []
- const information: string[] = []
-
+ context: ConversionContext
+ ): { layers: LayerConfigJson[]; themes: LayoutConfigJson[] } {
const { layers, themes } = json
const perOsmTag = new Map<
string,
@@ -1191,15 +1020,10 @@ export class DetectDuplicateFilters extends DesugaringStep<{
}
msg += `\n - ${id}${layer.id}.${filter.id}`
}
- warnings.push(msg)
+ context.warn(msg)
})
- return {
- result: json,
- errors,
- warnings,
- information,
- }
+ return json
}
/**
@@ -1258,18 +1082,10 @@ export class DetectDuplicatePresets extends DesugaringStep {
"DetectDuplicatePresets"
)
}
- convert(
- json: LayoutConfig,
- context: string
- ): {
- result: LayoutConfig
- errors?: string[]
- warnings?: string[]
- information?: string[]
- } {
+
+ convert(json: LayoutConfig, context: ConversionContext): LayoutConfig {
const presets: PresetConfig[] = [].concat(...json.layers.map((l) => l.presets))
- const errors = []
const enNames = presets.map((p) => p.title.textFor("en"))
if (new Set(enNames).size != enNames.length) {
const dups = Utils.Duplicates(enNames)
@@ -1277,8 +1093,8 @@ export class DetectDuplicatePresets extends DesugaringStep {
l.presets.some((p) => dups.indexOf(p.title.textFor("en")) >= 0)
)
const layerIds = layersWithDup.map((l) => l.id)
- errors.push(
- `At ${context}: this themes has multiple presets which are named:${dups}, namely layers ${layerIds.join(
+ context.err(
+ `This themes 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`
)
@@ -1298,8 +1114,8 @@ export class DetectDuplicatePresets extends DesugaringStep {
presetB.preciseInput.snapToLayers
)
) {
- errors.push(
- `At ${context}: this themes has multiple presets with the same tags: ${presetATags.asHumanString(
+ context.err(
+ `This themes has multiple presets with the same tags: ${presetATags.asHumanString(
false,
false,
{}
@@ -1311,6 +1127,6 @@ export class DetectDuplicatePresets extends DesugaringStep {
}
}
- return { errors, result: json }
+ return json
}
}
diff --git a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts
index 92e322a82a..0f924e808d 100644
--- a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts
+++ b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts
@@ -197,7 +197,7 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
*/
freeform?: {
/**
- * question What is the name of the attribute that should be written to?
+ * question: What is the name of the attribute that should be written to?
* ifunset: do not offer a freeform textfield as answer option
*/
key: string
@@ -206,11 +206,14 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
* question: What is the input type?
* The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...
* See Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values
+ * ifunset: use an unconstrained string as input (default)
* suggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: "value="+type.name, then: ""+type.name+" "+type.explanation.split("\n")[0]}))
*/
type?: string
/**
+ * question: What placeholder text should be shown in the input-element if there is no input?
* A (translated) text that is shown (as gray text) within the textfield
+ * type: translation
*/
placeholder?: string | any
@@ -236,8 +239,9 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
inline?: boolean
/**
- * default value to enter if no previous tagging is present.
- * Normally undefined (aka do not enter anything)
+ * question: What value should be entered in the text field if no value is set?
+ * This can help people to quickly enter the most common option
+ * ifunset: do not prefill the textfield
*/
default?: string
}
diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts
index 053c3e4bc5..7898e24126 100644
--- a/src/Models/ThemeConfig/LayerConfig.ts
+++ b/src/Models/ThemeConfig/LayerConfig.ts
@@ -239,7 +239,9 @@ export default class LayerConfig extends WithContextLoader {
throw (
"Layer " +
this.id +
- " defines a maxSnapDistance, but does not include a `snapToLayer`"
+ " defines a maxSnapDistance, but does not include a `snapToLayer` (at " +
+ context +
+ ")"
)
}
diff --git a/src/Models/ThemeConfig/LayoutConfig.ts b/src/Models/ThemeConfig/LayoutConfig.ts
index 9781734dd8..f9b3c2d5f3 100644
--- a/src/Models/ThemeConfig/LayoutConfig.ts
+++ b/src/Models/ThemeConfig/LayoutConfig.ts
@@ -9,6 +9,7 @@ import { Utils } from "../../Utils"
import LanguageUtils from "../../Utils/LanguageUtils"
import { RasterLayerProperties } from "../RasterLayerProperties"
+import { ConversionContext } from "./Conversion/Conversion"
/**
* Minimal information about a theme
@@ -97,10 +98,7 @@ export default class LayoutConfig implements LayoutInformation {
this.language = json.mustHaveLanguage ?? Object.keys(json.title)
this.usedImages = Array.from(
new ExtractImages(official, undefined)
- .convertStrict(
- json,
- "while extracting the images of " + json.id + " " + context ?? ""
- )
+ .convertStrict(json, ConversionContext.construct([json.id], ["ExtractImages"]))
.map((i) => i.path)
).sort()
{
diff --git a/src/Models/ThemeConfig/PointRenderingConfig.ts b/src/Models/ThemeConfig/PointRenderingConfig.ts
index 389548b89d..885fe6d93f 100644
--- a/src/Models/ThemeConfig/PointRenderingConfig.ts
+++ b/src/Models/ThemeConfig/PointRenderingConfig.ts
@@ -14,6 +14,7 @@ import { VariableUiElement } from "../../UI/Base/VariableUIElement"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import SvelteUIElement from "../../UI/Base/SvelteUIElement"
import Marker from "../../UI/Map/Marker.svelte"
+import DynamicMarker from "../../UI/Map/DynamicMarker.svelte"
export class IconConfig extends WithContextLoader {
public readonly icon: TagRenderingConfig
@@ -45,8 +46,7 @@ export default class PointRenderingConfig extends WithContextLoader {
"point" | "centroid" | "start" | "end" | "projected_centerpoint" | string
>
- // public readonly icon?: TagRenderingConfig
- private readonly marker: IconConfig[]
+ public readonly marker: IconConfig[]
public readonly iconBadges: { if: TagsFilter; then: TagRenderingConfig }[]
public readonly iconSize: TagRenderingConfig
public readonly anchor: TagRenderingConfig
@@ -192,7 +192,7 @@ export default class PointRenderingConfig extends WithContextLoader {
}
public GetBaseIcon(tags?: Record): BaseUIElement {
- return new SvelteUIElement(Marker, { config: this, tags: new ImmutableStore(tags) })
+ return new SvelteUIElement(DynamicMarker, { config: this, tags: new ImmutableStore(tags) })
}
public RenderIcon(
tags: Store>,
@@ -244,7 +244,9 @@ export default class PointRenderingConfig extends WithContextLoader {
anchorH = -iconH / 2
}
- const icon = new SvelteUIElement(Marker, { config: this, tags }).SetClass("w-full h-full")
+ const icon = new SvelteUIElement(DynamicMarker, { config: this, tags }).SetClass(
+ "w-full h-full"
+ )
let badges = undefined
if (options?.includeBadges ?? true) {
badges = this.GetBadges(tags)
diff --git a/src/UI/Map/DynamicIcon.svelte b/src/UI/Map/DynamicIcon.svelte
new file mode 100644
index 0000000000..52bbefba3f
--- /dev/null
+++ b/src/UI/Map/DynamicIcon.svelte
@@ -0,0 +1,41 @@
+
+
+
diff --git a/src/UI/Map/DynamicMarker.svelte b/src/UI/Map/DynamicMarker.svelte
new file mode 100644
index 0000000000..77259967e2
--- /dev/null
+++ b/src/UI/Map/DynamicMarker.svelte
@@ -0,0 +1,21 @@
+
+{#if config !== undefined}
+
+ {#each icons as icon}
+
+ {/each}
+
+{/if}
diff --git a/src/UI/Map/Icon.svelte b/src/UI/Map/Icon.svelte
index 9586e5c9cb..237dedfb33 100644
--- a/src/UI/Map/Icon.svelte
+++ b/src/UI/Map/Icon.svelte
@@ -1,6 +1,4 @@
-{#if iconItem}
+{#if icon}
- {#if iconItem === "pin"}
+ {#if icon === "pin"}
- {:else if iconItem === "square"}
+ {:else if icon === "square"}
- {:else if iconItem === "circle"}
+ {:else if icon === "circle"}
- {:else if iconItem === "checkmark"}
+ {:else if icon === "checkmark"}
- {:else if iconItem === "clock"}
+ {:else if icon === "clock"}
- {:else if iconItem === "close"}
+ {:else if icon === "close"}
- {:else if iconItem === "crosshair"}
+ {:else if icon === "crosshair"}
- {:else if iconItem === "help"}
+ {:else if icon === "help"}
- {:else if iconItem === "home"}
+ {:else if icon === "home"}
- {:else if iconItem === "invalid"}
+ {:else if icon === "invalid"}
- {:else if iconItem === "location"}
+ {:else if icon === "location"}
- {:else if iconItem === "location_empty"}
+ {:else if icon === "location_empty"}
- {:else if iconItem === "location_locked"}
+ {:else if icon === "location_locked"}
- {:else if iconItem === "note"}
+ {:else if icon === "note"}
- {:else if iconItem === "resolved"}
+ {:else if icon === "resolved"}
- {:else if iconItem === "ring"}
+ {:else if icon === "ring"}
- {:else if iconItem === "scissors"}
+ {:else if icon === "scissors"}
- {:else if iconItem === "teardrop"}
+ {:else if icon === "teardrop"}
- {:else if iconItem === "teardrop_with_hole_green"}
+ {:else if icon === "teardrop_with_hole_green"}
- {:else if iconItem === "triangle"}
+ {:else if icon === "triangle"}
{:else}
-

+

{/if}
{/if}
diff --git a/src/UI/Map/Marker.svelte b/src/UI/Map/Marker.svelte
index 26864a3ba1..656f2b6deb 100644
--- a/src/UI/Map/Marker.svelte
+++ b/src/UI/Map/Marker.svelte
@@ -1,21 +1,17 @@
-{#if config !== undefined}
+{#if icons !== undefined && icons.length > 0}
{#each icons as icon}
-
+
{/each}
{/if}
diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte
index 10431d2165..7fb20d181d 100644
--- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte
+++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte
@@ -235,6 +235,7 @@
bind:group={selectedMapping}
name={"mappings-radio-" + config.id}
value={i}
+ on:keypress={e => {console.log(e) ; if(e.key === "Enter") onSave()}}
/>
{/each}
diff --git a/src/UI/Studio/EditLayer.svelte b/src/UI/Studio/EditLayer.svelte
index 078360bc93..c267b2f3eb 100644
--- a/src/UI/Studio/EditLayer.svelte
+++ b/src/UI/Studio/EditLayer.svelte
@@ -19,7 +19,7 @@
* Blacklist of regions for the general area tab
* These are regions which are handled by a different tab
*/
- const regionBlacklist = ["hidden", undefined, "infobox", "tagrenderings", "maprendering", "editing", "title"];
+ const regionBlacklist = ["hidden", undefined, "infobox", "tagrenderings", "maprendering", "editing", "title","linerendering","pointrendering"];
const allNames = Utils.Dedup(layerSchema.map(meta => meta.hints.group));
const perRegion: Record = {};
@@ -27,7 +27,7 @@
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region);
}
- const baselayerRegions: string[] = ["Basic", "presets", "filters", "advanced", "expert"];
+ const baselayerRegions: string[] = ["Basic", "presets", "filters"];
for (const baselayerRegion of baselayerRegions) {
if (perRegion[baselayerRegion] === undefined) {
console.error("BaseLayerRegions in editLayer: no items have group '" + baselayerRegion + "\"");
@@ -38,8 +38,6 @@
Editing layer {$title}
-Leftover regions
-{leftoverRegions.join("; ")}
General properties
@@ -47,9 +45,6 @@
{#each baselayerRegions as region}
{/each}
- {#each leftoverRegions as region}
-
- {/each}
Information panel (questions and answers)
@@ -63,8 +58,14 @@
- Configuration file
+
+ Advanced functionality
+
+
+
+ Configuration file
+
Below, you'll find the raw configuration file in `.json`-format.
This is mostly for debugging purposes
diff --git a/src/UI/Studio/SchemaBasedArray.svelte b/src/UI/Studio/SchemaBasedArray.svelte
index 9a8418f330..0675ccd0a8 100644
--- a/src/UI/Studio/SchemaBasedArray.svelte
+++ b/src/UI/Studio/SchemaBasedArray.svelte
@@ -65,8 +65,13 @@ console.log("For ", schema.path, "got subparts", subparts)
}
function del(value) {
- values.data.splice(values.data.indexOf(value));
+ const index = values.data.indexOf(value)
+ console.log("Deleting",value, index)
+ values.data.splice(index, 1);
+ const store =
>state.getStoreFor(path);
+ store.data.splice(index, 1)
values.ping();
+ store.ping()
}
diff --git a/src/UI/Studio/SchemaBasedField.svelte b/src/UI/Studio/SchemaBasedField.svelte
index 3cd1996547..7354e1c4f0 100644
--- a/src/UI/Studio/SchemaBasedField.svelte
+++ b/src/UI/Studio/SchemaBasedField.svelte
@@ -96,13 +96,13 @@
err = path.join(".") + " " + e
}
let startValue = state.getCurrentValueFor(path)
- if (typeof startValue !== "string") {
- startValue = JSON.stringify(startValue)
- }
const tags = new UIEventSource>({value: startValue ?? ""})
try {
onDestroy(state.register(path, tags.map(tgs => {
const v = tgs["value"];
+ if(typeof v !== "string"){
+ return v
+ }
if (schema.type === "boolan") {
return v === "true" || v === "yes" || v === "1"
}
@@ -135,7 +135,6 @@
{err}
{:else}
- {path.join(".")}
{/if}
diff --git a/src/UI/Studio/SchemaBasedMultiType.svelte b/src/UI/Studio/SchemaBasedMultiType.svelte
index 75f4e75029..7c53ba74b7 100644
--- a/src/UI/Studio/SchemaBasedMultiType.svelte
+++ b/src/UI/Studio/SchemaBasedMultiType.svelte
@@ -214,5 +214,4 @@
path={[...subpath, (subschema?.path?.at(-1) ?? "???")]}>
{/each}
{/if}
- {chosenOption}
diff --git a/src/UI/Studio/StudioServer.ts b/src/UI/Studio/StudioServer.ts
new file mode 100644
index 0000000000..fe783b7ebb
--- /dev/null
+++ b/src/UI/Studio/StudioServer.ts
@@ -0,0 +1,38 @@
+import { Utils } from "../../Utils"
+import Constants from "../../Models/Constants"
+import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
+
+export default class StudioServer {
+ private _url: string
+
+ constructor(url: string) {
+ this._url = url
+ }
+
+ public async fetchLayerOverview(): Promise
> {
+ const { allFiles } = <{ allFiles: string[] }>(
+ await Utils.downloadJson(this._url + "/overview")
+ )
+ const layers = allFiles
+ .filter((f) => f.startsWith("layers/"))
+ .map((l) => l.substring(l.lastIndexOf("/") + 1, l.length - ".json".length))
+ .filter((layerId) => Constants.priviliged_layers.indexOf(layerId) < 0)
+ return new Set(layers)
+ }
+
+ async fetchLayer(layerId: string, checkNew: boolean = false): Promise {
+ try {
+ return await Utils.downloadJson(
+ this._url +
+ "/layers/" +
+ layerId +
+ "/" +
+ layerId +
+ ".json" +
+ (checkNew ? ".new.json" : "")
+ )
+ } catch (e) {
+ return undefined
+ }
+ }
+}
diff --git a/src/UI/StudioGUI.svelte b/src/UI/StudioGUI.svelte
index 254a216053..6ae29f54d6 100644
--- a/src/UI/StudioGUI.svelte
+++ b/src/UI/StudioGUI.svelte
@@ -2,35 +2,32 @@
import NextButton from "./Base/NextButton.svelte";
- import { Utils } from "../Utils";
import { UIEventSource } from "../Logic/UIEventSource";
- import Constants from "../Models/Constants";
import ValidatedInput from "./InputElement/ValidatedInput.svelte";
import EditLayerState from "./Studio/EditLayerState";
import EditLayer from "./Studio/EditLayer.svelte";
import Loading from "../assets/svg/Loading.svelte";
+ import Marker from "./Map/Marker.svelte";
+ import { AllSharedLayers } from "../Customizations/AllSharedLayers";
+ import StudioServer from "./Studio/StudioServer";
+ import LoginToggle from "./Base/LoginToggle.svelte";
+ import { OsmConnection } from "../Logic/Osm/OsmConnection";
+ import { QueryParameters } from "../Logic/Web/QueryParameters";
export let studioUrl = "http://127.0.0.1:1235";
- let overview = UIEventSource.FromPromise<{ allFiles: string[] }>(Utils.downloadJson(studioUrl + "/overview"));
- let layers = overview.map(overview => {
- if (!overview) {
- return [];
- }
- return overview.allFiles.filter(f => f.startsWith("layers/")
- ).map(l => l.substring(l.lastIndexOf("/") + 1, l.length - ".json".length))
- .filter(layerId => Constants.priviliged_layers.indexOf(layerId) < 0);
- });
+ const studio = new StudioServer(studioUrl);
+ let layers = UIEventSource.FromPromise(studio.fetchLayerOverview());
let state: undefined | "edit_layer" | "new_layer" | "edit_theme" | "new_theme" | "editing_layer" | "loading" = undefined;
- let initialLayerConfig: undefined;
+ let initialLayerConfig: { id: string };
let newLayerId = new UIEventSource("");
let layerIdFeedback = new UIEventSource(undefined);
newLayerId.addCallbackD(layerId => {
if (layerId === "") {
return;
}
- if (layers.data.indexOf(layerId) >= 0) {
+ if (layers.data.has(layerId)) {
layerIdFeedback.setData("This id is already used");
}
}, [layers]);
@@ -38,7 +35,28 @@
let editLayerState = new EditLayerState();
+ function fetchIconDescription(layerId): any {
+ const icon = AllSharedLayers.getSharedLayersConfigs().get(layerId)?._layerIcon;
+ console.log(icon);
+ return icon;
+ }
+
+ let osmConnection = new OsmConnection( new OsmConnection({
+ oauth_token: QueryParameters.GetQueryParameter(
+ "oauth_token",
+ undefined,
+ "Used to complete the login"
+ ),
+ }))
+
+
+
+
+
+ Please log in to use MapComplete Studio
+
+
{#if state === undefined}
MapComplete Studio
@@ -58,13 +76,16 @@
{:else if state === "edit_layer"}
- {#each $layers as layerId}
+ {#each Array.from($layers) as layerId}
{
console.log("Editing layer",layerId)
state = "loading"
- initialLayerConfig = await Utils.downloadJson(studioUrl+"/layers/"+layerId+"/"+layerId+".json")
+ initialLayerConfig = await studio.fetchLayer(layerId)
state = "editing_layer"
}}>
+
+
+
{layerId}
{/each}
@@ -76,12 +97,22 @@
{$layerIdFeedback}
{:else }
- {initialLayerConfig = ({id: newLayerId.data}); state = "editing_layer"}}>
+ {
+ state = "loading"
+ const id = newLayerId.data
+ const createdBy = osmConnection.userDetails.data.name
+
+ const loaded = await studio.fetchLayer(id, true)
+ initialLayerConfig = loaded ?? {id, credits: createdBy};
+ state = "editing_layer"}}>
Create this layer
{/if}
{:else if state === "loading"}
-
+
+
+
{:else if state === "editing_layer"}
{/if}
+
diff --git a/src/assets/schemas/layerconfigmeta.json b/src/assets/schemas/layerconfigmeta.json
index 892b42fec4..a3135ae9ab 100644
--- a/src/assets/schemas/layerconfigmeta.json
+++ b/src/assets/schemas/layerconfigmeta.json
@@ -11745,6 +11745,10 @@
"if": "value=gps_track",
"then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track."
},
+ {
+ "if": "value=guidepost",
+ "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
+ },
{
"if": "value=hackerspace",
"then": "hackerspace - Hackerspace"
@@ -12102,15 +12106,15 @@
"type": "object",
"properties": {
"key": {
- "description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
+ "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
"type": "string"
},
"type": {
- "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
+ "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained string as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
"type": "string"
},
"placeholder": {
- "description": "A (translated) text that is shown (as gray text) within the textfield"
+ "description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
@@ -12129,7 +12133,7 @@
"type": "boolean"
},
"default": {
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
+ "description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
"type": "string"
}
},
@@ -12816,10 +12820,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -12830,6 +12835,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -12902,6 +12908,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -12915,7 +12925,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -12962,9 +12975,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -13837,10 +13853,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -13852,6 +13869,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -13924,6 +13942,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -13938,7 +13960,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -13989,9 +14014,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -14888,10 +14916,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -14903,6 +14932,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -14975,6 +15005,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -14989,7 +15023,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -15040,9 +15077,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -15951,10 +15991,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -15967,6 +16008,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -16039,6 +16081,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -16054,7 +16100,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -16109,9 +16158,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
diff --git a/src/assets/schemas/layoutconfigmeta.json b/src/assets/schemas/layoutconfigmeta.json
index 643d66b5f1..feed8423ef 100644
--- a/src/assets/schemas/layoutconfigmeta.json
+++ b/src/assets/schemas/layoutconfigmeta.json
@@ -663,15 +663,15 @@
"type": "object",
"properties": {
"key": {
- "description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
+ "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
"type": "string"
},
"type": {
- "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
+ "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained string as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
"type": "string"
},
"placeholder": {
- "description": "A (translated) text that is shown (as gray text) within the textfield"
+ "description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
@@ -690,7 +690,7 @@
"type": "boolean"
},
"default": {
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
+ "description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
"type": "string"
}
},
@@ -13206,6 +13206,10 @@
"if": "value=gps_track",
"then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track."
},
+ {
+ "if": "value=guidepost",
+ "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
+ },
{
"if": "value=hackerspace",
"then": "hackerspace - Hackerspace"
@@ -13565,15 +13569,15 @@
"type": "object",
"properties": {
"key": {
- "description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
+ "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
"type": "string"
},
"type": {
- "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
+ "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained string as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
"type": "string"
},
"placeholder": {
- "description": "A (translated) text that is shown (as gray text) within the textfield"
+ "description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
@@ -13592,7 +13596,7 @@
"type": "boolean"
},
"default": {
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
+ "description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
"type": "string"
}
},
@@ -14300,10 +14304,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -14315,6 +14320,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -14387,6 +14393,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -14401,7 +14411,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -14452,9 +14465,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -15363,10 +15379,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -15379,6 +15396,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -15451,6 +15469,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -15466,7 +15488,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -15521,9 +15546,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -16457,10 +16485,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -16473,6 +16502,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -16545,6 +16575,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -16560,7 +16594,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -16615,9 +16652,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -17562,10 +17602,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -17579,6 +17620,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -17651,6 +17693,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -17667,7 +17713,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -17726,9 +17775,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -31420,6 +31472,10 @@
"if": "value=gps_track",
"then": "gps_track - Meta layer showing the previous locations of the user as single line with controls, e.g. to erase, upload or download this track. Add this to your theme and override the maprendering to change the appearance of the travelled track."
},
+ {
+ "if": "value=guidepost",
+ "then": "guidepost - Guideposts (also known as fingerposts or finger posts) are often found along official hiking/cycling/riding/skiing routes to indicate the directions to different destinations"
+ },
{
"if": "value=hackerspace",
"then": "hackerspace - Hackerspace"
@@ -31781,15 +31837,15 @@
"type": "object",
"properties": {
"key": {
- "description": "question What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
+ "description": "question: What is the name of the attribute that should be written to?\nifunset: do not offer a freeform textfield as answer option",
"type": "string"
},
"type": {
- "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
+ "description": "question: What is the input type?\nThe type of the text-field, e.g. 'string', 'nat', 'float', 'date',...\nSee Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values\nifunset: use an unconstrained string as input (default)\nsuggestions: return validators.AllValidators.filter(type => !type.isMeta).map((type) => ({if: \"value=\"+type.name, then: \"\"+type.name+\" \"+type.explanation.split(\"\\n\")[0]}))",
"type": "string"
},
"placeholder": {
- "description": "A (translated) text that is shown (as gray text) within the textfield"
+ "description": "question: What placeholder text should be shown in the input-element if there is no input?\nA (translated) text that is shown (as gray text) within the textfield\ntype: translation"
},
"helperArgs": {
"description": "Extra parameters to initialize the input helper arguments.\nFor semantics, see the 'SpecialInputElements.md'",
@@ -31808,7 +31864,7 @@
"type": "boolean"
},
"default": {
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)",
+ "description": "question: What value should be entered in the text field if no value is set?\nThis can help people to quickly enter the most common option\nifunset: do not prefill the textfield",
"type": "string"
}
},
@@ -32537,10 +32593,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -32553,6 +32610,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -32625,6 +32683,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -32640,7 +32702,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -32695,9 +32760,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -33642,10 +33710,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -33659,6 +33728,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -33731,6 +33801,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -33747,7 +33821,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -33806,9 +33883,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -34779,10 +34859,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -34796,6 +34877,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -34868,6 +34950,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -34884,7 +34970,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -34943,9 +35032,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
@@ -35926,10 +36018,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -35944,6 +36037,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -36016,6 +36110,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -36033,7 +36131,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -36096,9 +36197,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [
diff --git a/src/assets/schemas/questionabletagrenderingconfigmeta.json b/src/assets/schemas/questionabletagrenderingconfigmeta.json
index 503b39956c..f4a4eab477 100644
--- a/src/assets/schemas/questionabletagrenderingconfigmeta.json
+++ b/src/assets/schemas/questionabletagrenderingconfigmeta.json
@@ -473,10 +473,11 @@
],
"required": true,
"hints": {
+ "question": "What is the name of the attribute that should be written to?",
"ifunset": "do not offer a freeform textfield as answer option"
},
"type": "string",
- "description": "question What is the name of the attribute that should be written to?"
+ "description": ""
},
{
"path": [
@@ -486,6 +487,7 @@
"required": false,
"hints": {
"question": "What is the input type?",
+ "ifunset": "use an unconstrained string as input (default)",
"suggestions": [
{
"if": "value=string",
@@ -558,6 +560,10 @@
{
"if": "value=fediverse",
"then": "fediverse Validates fediverse addresses and normalizes them into `@username@server`-format"
+ },
+ {
+ "if": "value=id",
+ "then": "id Checks for valid identifiers for layers, will automatically replace spaces and uppercase"
}
]
},
@@ -570,7 +576,10 @@
"placeholder"
],
"required": false,
- "hints": {},
+ "hints": {
+ "typehint": "translation",
+ "question": "What placeholder text should be shown in the input-element if there is no input?"
+ },
"description": "A (translated) text that is shown (as gray text) within the textfield"
},
{
@@ -613,9 +622,12 @@
"default"
],
"required": false,
- "hints": {},
+ "hints": {
+ "question": "What value should be entered in the text field if no value is set?",
+ "ifunset": "do not prefill the textfield"
+ },
"type": "string",
- "description": "default value to enter if no previous tagging is present.\nNormally undefined (aka do not enter anything)"
+ "description": "This can help people to quickly enter the most common option"
},
{
"path": [