Themes: automatically derive source from presets if no source is given

This commit is contained in:
Pieter Vander Vennet 2024-08-16 02:09:54 +02:00
parent 257a35d219
commit 8e18864daa
6 changed files with 243 additions and 184 deletions

View file

@ -81,15 +81,16 @@ class ParseLayer extends Conversion<
} }
const fixed = this._prepareLayer.convert(parsed, context.inOperation("PrepareLayer")) const fixed = this._prepareLayer.convert(parsed, context.inOperation("PrepareLayer"))
if (!fixed.source) { if (!fixed.source && fixed.presets?.length < 1) {
context.enter("source").err("No source is configured") context.enter("source").err("No source is configured. (Tags might be automatically derived if presets are given)")
return undefined return undefined
} }
if ( if (
fixed.source &&
typeof fixed.source !== "string" && typeof fixed.source !== "string" &&
fixed.source["osmTags"] && fixed.source?.["osmTags"] &&
fixed.source["osmTags"]["and"] === undefined fixed.source?.["osmTags"]["and"] === undefined
) { ) {
fixed.source["osmTags"] = { and: [fixed.source["osmTags"]] } fixed.source["osmTags"] = { and: [fixed.source["osmTags"]] }
} }

View file

@ -25,6 +25,10 @@ export class UpdateLegacyLayer extends DesugaringStep<
context = context.enter(json.id) context = context.enter(json.id)
let config = { ...json } let config = { ...json }
if(config["credits"] === "Not logged in"){
delete config["credits"]
}
if (config["overpassTags"]) { if (config["overpassTags"]) {
config.source = config.source ?? { config.source = config.source ?? {
osmTags: config["overpassTags"], osmTags: config["overpassTags"],
@ -142,9 +146,11 @@ export class UpdateLegacyLayer extends DesugaringStep<
delete config["wayHandling"] delete config["wayHandling"]
delete config["hideUnderlayingFeaturesMinPercentage"] delete config["hideUnderlayingFeaturesMinPercentage"]
const src = config.source const src = config.source
if(src){
delete src["isOsmCache"] delete src["isOsmCache"]
delete src["maxCacheAge"] delete src["maxCacheAge"]
delete src["widenFactor"] delete src["widenFactor"]
}
for (const mapRenderingElement of config["mapRendering"] ?? []) { for (const mapRenderingElement of config["mapRendering"] ?? []) {
if (mapRenderingElement["iconOverlays"] !== undefined) { if (mapRenderingElement["iconOverlays"] !== undefined) {

View file

@ -32,6 +32,7 @@ import { ConfigMeta } from "../../../UI/Studio/configMeta"
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import LineRenderingConfigJson from "../Json/LineRenderingConfigJson"
import { ConversionContext } from "./ConversionContext" import { ConversionContext } from "./ConversionContext"
import { ExpandRewrite } from "./ExpandRewrite" import { ExpandRewrite } from "./ExpandRewrite"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
class ExpandFilter extends DesugaringStep<LayerConfigJson> { class ExpandFilter extends DesugaringStep<LayerConfigJson> {
private static readonly predefinedFilters = ExpandFilter.load_filters() private static readonly predefinedFilters = ExpandFilter.load_filters()
@ -41,7 +42,7 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
super( super(
"Expands filters: replaces a shorthand by the value found in 'filters.json'. If the string is formatted 'layername.filtername, it will be looked up into that layer instead", "Expands filters: replaces a shorthand by the value found in 'filters.json'. If the string is formatted 'layername.filtername, it will be looked up into that layer instead",
["filter"], ["filter"],
"ExpandFilter" "ExpandFilter",
) )
this._state = state this._state = state
} }
@ -111,7 +112,7 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
context context
.enters("filter", i) .enters("filter", i)
.err( .err(
"Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings" "Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings",
) )
} }
const options = matchingTr.mappings.map((mapping) => ({ const options = matchingTr.mappings.map((mapping) => ({
@ -136,7 +137,7 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
const split = filter.split(".") const split = filter.split(".")
if (split.length > 2) { if (split.length > 2) {
context.err( context.err(
"invalid filter name: " + filter + ", expected `layername.filterid`" "invalid filter name: " + filter + ", expected `layername.filterid`",
) )
} }
const layer = this._state.sharedLayers.get(split[0]) const layer = this._state.sharedLayers.get(split[0])
@ -145,7 +146,7 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
} }
const expectedId = split[1] const expectedId = split[1]
const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find( const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find(
(f) => typeof f !== "string" && f.id === expectedId (f) => typeof f !== "string" && f.id === expectedId,
) )
if (expandedFilter === undefined) { if (expandedFilter === undefined) {
context.err("Did not find filter with name " + filter) context.err("Did not find filter with name " + filter)
@ -163,7 +164,7 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
const suggestions = Utils.sortedByLevenshteinDistance( const suggestions = Utils.sortedByLevenshteinDistance(
filter, filter,
Array.from(ExpandFilter.predefinedFilters.keys()), Array.from(ExpandFilter.predefinedFilters.keys()),
(t) => t (t) => t,
) )
context context
.enter(filter) .enter(filter)
@ -171,7 +172,7 @@ class ExpandFilter extends DesugaringStep<LayerConfigJson> {
"While searching for predefined filter " + "While searching for predefined filter " +
filter + filter +
": this filter is not found. Perhaps you meant one of: " + ": this filter is not found. Perhaps you meant one of: " +
suggestions suggestions,
) )
} }
newFilters.push(found) newFilters.push(found)
@ -186,7 +187,7 @@ class ExpandTagRendering extends Conversion<
| { | {
builtin: string | string[] builtin: string | string[]
override: any override: any
}, },
TagRenderingConfigJson[] TagRenderingConfigJson[]
> { > {
private readonly _state: DesugaringContext private readonly _state: DesugaringContext
@ -208,12 +209,12 @@ class ExpandTagRendering extends Conversion<
noHardcodedStrings?: false | boolean noHardcodedStrings?: false | boolean
// If set, a question will be added to the 'sharedTagRenderings'. Should only be used for 'questions.json' // If set, a question will be added to the 'sharedTagRenderings'. Should only be used for 'questions.json'
addToContext?: false | boolean addToContext?: false | boolean
} },
) { ) {
super( super(
"Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question and reusing the builtins", "Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question and reusing the builtins",
[], [],
"ExpandTagRendering" "ExpandTagRendering",
) )
this._state = state this._state = state
this._self = self this._self = self
@ -233,7 +234,7 @@ class ExpandTagRendering extends Conversion<
public convert( public convert(
spec: string | any, spec: string | any,
ctx: ConversionContext ctx: ConversionContext,
): QuestionableTagRenderingConfigJson[] { ): QuestionableTagRenderingConfigJson[] {
const trs = this.convertOnce(spec, ctx) const trs = this.convertOnce(spec, ctx)
@ -346,8 +347,8 @@ class ExpandTagRendering extends Conversion<
found, found,
ConversionContext.construct( ConversionContext.construct(
[layer.id, "tagRenderings", found["id"]], [layer.id, "tagRenderings", found["id"]],
["AddContextToTranslations"] ["AddContextToTranslations"],
) ),
) )
matchingTrs[i] = found matchingTrs[i] = found
} }
@ -375,7 +376,7 @@ class ExpandTagRendering extends Conversion<
ctx.warn( ctx.warn(
`A literal rendering was detected: ${tr} `A literal rendering was detected: ${tr}
Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` + Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` +
Array.from(state.sharedLayers.keys()).join(", ") Array.from(state.sharedLayers.keys()).join(", "),
) )
} }
@ -385,7 +386,7 @@ class ExpandTagRendering extends Conversion<
tr + tr +
" \n Did you perhaps forget to add the layer as prefix, such as `icons." + " \n Did you perhaps forget to add the layer as prefix, such as `icons." +
tr + tr +
"`? " "`? ",
) )
} }
@ -422,7 +423,7 @@ class ExpandTagRendering extends Conversion<
"An object calling a builtin can only have keys `builtin` or `override`, but a key with name `" + "An object calling a builtin can only have keys `builtin` or `override`, but a key with name `" +
key + key +
"` was found. This won't be picked up! The full object is: " + "` was found. This won't be picked up! The full object is: " +
JSON.stringify(tr) JSON.stringify(tr),
) )
} }
@ -441,7 +442,7 @@ class ExpandTagRendering extends Conversion<
const candidates = Utils.sortedByLevenshteinDistance( const candidates = Utils.sortedByLevenshteinDistance(
layerName, layerName,
Array.from(state.sharedLayers.keys()), Array.from(state.sharedLayers.keys()),
(s) => s (s) => s,
) )
if (state.sharedLayers.size === 0) { if (state.sharedLayers.size === 0) {
ctx.warn( ctx.warn(
@ -449,7 +450,7 @@ class ExpandTagRendering extends Conversion<
name + name +
": layer " + ": layer " +
layerName + layerName +
" not found for now, but ignoring as this is a bootstrapping run. " " not found for now, but ignoring as this is a bootstrapping run. ",
) )
} else { } else {
ctx.err( ctx.err(
@ -458,13 +459,13 @@ class ExpandTagRendering extends Conversion<
": layer " + ": layer " +
layerName + layerName +
" not found. Maybe you meant one of " + " not found. Maybe you meant one of " +
candidates.slice(0, 3).join(", ") candidates.slice(0, 3).join(", "),
) )
} }
continue continue
} }
candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map( candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map(
(id) => layerName + "." + id (id) => layerName + "." + id,
) )
} }
candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i) candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i)
@ -473,7 +474,7 @@ class ExpandTagRendering extends Conversion<
name + name +
" was not found.\n\tDid you mean one of " + " was not found.\n\tDid you mean one of " +
candidates.join(", ") + candidates.join(", ") +
"?\n(Hint: did you add a new label and are you trying to use this label at the same time? Run 'reset:layeroverview' first" "?\n(Hint: did you add a new label and are you trying to use this label at the same time? Run 'reset:layeroverview' first",
) )
continue continue
} }
@ -498,13 +499,13 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
super( super(
"If no 'inline' is set on the freeform key, it will be automatically added. If no special renderings are used, it'll be set to true", "If no 'inline' is set on the freeform key, it will be automatically added. If no special renderings are used, it'll be set to true",
["freeform.inline"], ["freeform.inline"],
"DetectInline" "DetectInline",
) )
} }
convert( convert(
json: QuestionableTagRenderingConfigJson, json: QuestionableTagRenderingConfigJson,
context: ConversionContext context: ConversionContext,
): QuestionableTagRenderingConfigJson { ): QuestionableTagRenderingConfigJson {
if (json.freeform === undefined) { if (json.freeform === undefined) {
return json return json
@ -527,7 +528,7 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
if (json.freeform.inline === true) { if (json.freeform.inline === true) {
context.err( context.err(
"'inline' is set, but the rendering contains a special visualisation...\n " + "'inline' is set, but the rendering contains a special visualisation...\n " +
spec[key] spec[key],
) )
} }
json = JSON.parse(JSON.stringify(json)) json = JSON.parse(JSON.stringify(json))
@ -550,7 +551,7 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
super( super(
"Adds a 'questions'-object if no question element is added yet", "Adds a 'questions'-object if no question element is added yet",
["tagRenderings"], ["tagRenderings"],
"AddQuestionBox" "AddQuestionBox",
) )
} }
@ -574,18 +575,18 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
json.tagRenderings = [...json.tagRenderings] json.tagRenderings = [...json.tagRenderings]
const allSpecials: Exclude<RenderingSpecification, string>[] = <any>( const allSpecials: Exclude<RenderingSpecification, string>[] = <any>(
ValidationUtils.getAllSpecialVisualisations( ValidationUtils.getAllSpecialVisualisations(
<QuestionableTagRenderingConfigJson[]>json.tagRenderings <QuestionableTagRenderingConfigJson[]>json.tagRenderings,
).filter((spec) => typeof spec !== "string") ).filter((spec) => typeof spec !== "string")
) )
const questionSpecials = allSpecials.filter((sp) => sp.func.funcName === "questions") const questionSpecials = allSpecials.filter((sp) => sp.func.funcName === "questions")
const noLabels = questionSpecials.filter( const noLabels = questionSpecials.filter(
(sp) => sp.args.length === 0 || sp.args[0].trim() === "" (sp) => sp.args.length === 0 || sp.args[0].trim() === "",
) )
if (noLabels.length > 1) { if (noLabels.length > 1) {
context.err( context.err(
"Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this" "Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this",
) )
} }
@ -593,9 +594,9 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
const allLabels = new Set( const allLabels = new Set(
[].concat( [].concat(
...json.tagRenderings.map( ...json.tagRenderings.map(
(tr) => (<QuestionableTagRenderingConfigJson>tr).labels ?? [] (tr) => (<QuestionableTagRenderingConfigJson>tr).labels ?? [],
) ),
) ),
) )
const seen: Set<string> = new Set() const seen: Set<string> = new Set()
for (const questionSpecial of questionSpecials) { for (const questionSpecial of questionSpecials) {
@ -616,7 +617,7 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
"\n Whitelisted: " + "\n Whitelisted: " +
used.join(", ") + used.join(", ") +
"\n Blacklisted: " + "\n Blacklisted: " +
blacklisted.join(", ") blacklisted.join(", "),
) )
} }
for (const usedLabel of used) { for (const usedLabel of used) {
@ -626,7 +627,7 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
usedLabel + usedLabel +
"`, but this label doesn't exist.\n" + "`, but this label doesn't exist.\n" +
" Available labels are " + " Available labels are " +
Array.from(allLabels).join(", ") Array.from(allLabels).join(", "),
) )
} }
seen.add(usedLabel) seen.add(usedLabel)
@ -654,11 +655,12 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
private readonly _addedByDefaultAtTop: QuestionableTagRenderingConfigJson[] private readonly _addedByDefaultAtTop: QuestionableTagRenderingConfigJson[]
private readonly _addedByDefault: QuestionableTagRenderingConfigJson[] private readonly _addedByDefault: QuestionableTagRenderingConfigJson[]
private readonly builtinQuestions: QuestionableTagRenderingConfigJson[] private readonly builtinQuestions: QuestionableTagRenderingConfigJson[]
constructor(desugaring: DesugaringContext) { constructor(desugaring: DesugaringContext) {
super( super(
"Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements", "Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements",
[], [],
"AddEditingElements" "AddEditingElements",
) )
this._desugaring = desugaring this._desugaring = desugaring
this.builtinQuestions = Array.from(this._desugaring.tagRenderings?.values() ?? []) this.builtinQuestions = Array.from(this._desugaring.tagRenderings?.values() ?? [])
@ -688,13 +690,13 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
json.tagRenderings = [...(json.tagRenderings ?? [])] json.tagRenderings = [...(json.tagRenderings ?? [])]
const allIds = new Set<string>(json.tagRenderings.map((tr) => tr["id"])) const allIds = new Set<string>(json.tagRenderings.map((tr) => tr["id"]))
const specialVisualisations = ValidationUtils.getAllSpecialVisualisations( const specialVisualisations = ValidationUtils.getAllSpecialVisualisations(
<any>json.tagRenderings <any>json.tagRenderings,
) )
const usedSpecialFunctions = new Set( const usedSpecialFunctions = new Set(
specialVisualisations.map((sv) => specialVisualisations.map((sv) =>
typeof sv === "string" ? undefined : sv.func.funcName typeof sv === "string" ? undefined : sv.func.funcName,
) ),
) )
/***** ADD TO TOP ****/ /***** ADD TO TOP ****/
@ -762,7 +764,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
super( super(
"Converts a 'special' translation into a regular translation which uses parameters", "Converts a 'special' translation into a regular translation which uses parameters",
["special"], ["special"],
"RewriteSpecial" "RewriteSpecial",
) )
} }
@ -858,7 +860,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
} }
}) })
| any, | any,
context: ConversionContext context: ConversionContext,
): any { ): any {
const special = input["special"] const special = input["special"]
if (special === undefined) { if (special === undefined) {
@ -868,7 +870,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
const type = special["type"] const type = special["type"]
if (type === undefined) { if (type === undefined) {
context.err( context.err(
"A 'special'-block should define 'type' to indicate which visualisation should be used" "A 'special'-block should define 'type' to indicate which visualisation should be used",
) )
return undefined return undefined
} }
@ -878,10 +880,10 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
const options = Utils.sortedByLevenshteinDistance( const options = Utils.sortedByLevenshteinDistance(
type, type,
SpecialVisualizations.specialVisualizations, SpecialVisualizations.specialVisualizations,
(sp) => sp.funcName (sp) => sp.funcName,
) )
context.err( context.err(
`Special visualisation '${type}' not found. Did you perhaps mean ${options[0].funcName}, ${options[1].funcName} or ${options[2].funcName}?\n\tFor all known special visualisations, please see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialRenderings.md` `Special visualisation '${type}' not found. Did you perhaps mean ${options[0].funcName}, ${options[1].funcName} or ${options[2].funcName}?\n\tFor all known special visualisations, please see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialRenderings.md`,
) )
return undefined return undefined
} }
@ -902,7 +904,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
const byDistance = Utils.sortedByLevenshteinDistance( const byDistance = Utils.sortedByLevenshteinDistance(
wrongArg, wrongArg,
argNamesList, argNamesList,
(x) => x (x) => x,
) )
return `Unexpected argument in special block at ${context} with name '${wrongArg}'. Did you mean ${ return `Unexpected argument in special block at ${context} with name '${wrongArg}'. Did you mean ${
byDistance[0] byDistance[0]
@ -921,8 +923,8 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
`Obligated parameter '${arg.name}' in special rendering of type ${ `Obligated parameter '${arg.name}' in special rendering of type ${
vis.funcName vis.funcName
} not found.\n The full special rendering specification is: '${JSON.stringify( } not found.\n The full special rendering specification is: '${JSON.stringify(
input input,
)}'\n ${arg.name}: ${arg.doc}` )}'\n ${arg.name}: ${arg.doc}`,
) )
} }
} }
@ -1024,7 +1026,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
continue continue
} }
Utils.WalkPath(path.path, json, (leaf, travelled) => Utils.WalkPath(path.path, json, (leaf, travelled) =>
RewriteSpecial.convertIfNeeded(leaf, context.enter(travelled)) RewriteSpecial.convertIfNeeded(leaf, context.enter(travelled)),
) )
} }
@ -1058,7 +1060,7 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
} = badgesJson[i] } = badgesJson[i]
const expanded = this._expand.convert( const expanded = this._expand.convert(
<QuestionableTagRenderingConfigJson>iconBadge.then, <QuestionableTagRenderingConfigJson>iconBadge.then,
context.enters("iconBadges", i) context.enters("iconBadges", i),
) )
if (expanded === undefined) { if (expanded === undefined) {
iconBadges.push(iconBadge) iconBadges.push(iconBadge)
@ -1069,7 +1071,7 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
...expanded.map((resolved) => ({ ...expanded.map((resolved) => ({
if: iconBadge.if, if: iconBadge.if,
then: <MinimalTagRenderingConfigJson>resolved, then: <MinimalTagRenderingConfigJson>resolved,
})) })),
) )
} }
@ -1086,11 +1088,11 @@ class PreparePointRendering extends Fuse<PointRenderingConfigJson> {
new Each( new Each(
new On( new On(
"icon", "icon",
new FirstOf(new ExpandTagRendering(state, layer, { applyCondition: false })) new FirstOf(new ExpandTagRendering(state, layer, { applyCondition: false })),
)
)
), ),
new ExpandIconBadges(state, layer) ),
),
new ExpandIconBadges(state, layer),
) )
} }
} }
@ -1100,7 +1102,7 @@ class SetFullNodeDatabase extends DesugaringStep<LayerConfigJson> {
super( super(
"sets the fullNodeDatabase-bit if needed", "sets the fullNodeDatabase-bit if needed",
["fullNodeDatabase"], ["fullNodeDatabase"],
"SetFullNodeDatabase" "SetFullNodeDatabase",
) )
} }
@ -1129,7 +1131,7 @@ class ExpandMarkerRenderings extends DesugaringStep<IconConfigJson> {
super( super(
"Expands tagRenderings in the icons, if needed", "Expands tagRenderings in the icons, if needed",
["icon", "color"], ["icon", "color"],
"ExpandMarkerRenderings" "ExpandMarkerRenderings",
) )
this._layer = layer this._layer = layer
this._state = state this._state = state
@ -1161,7 +1163,7 @@ class AddFavouriteBadges extends DesugaringStep<LayerConfigJson> {
super( super(
"Adds the favourite heart to the title and the rendering badges", "Adds the favourite heart to the title and the rendering badges",
[], [],
"AddFavouriteBadges" "AddFavouriteBadges",
) )
} }
@ -1186,7 +1188,7 @@ export class AddRatingBadge extends DesugaringStep<LayerConfigJson> {
super( super(
"Adds the 'rating'-element if a reviews-element is used in the tagRenderings", "Adds the 'rating'-element if a reviews-element is used in the tagRenderings",
["titleIcons"], ["titleIcons"],
"AddRatingBadge" "AddRatingBadge",
) )
} }
@ -1206,7 +1208,7 @@ export class AddRatingBadge extends DesugaringStep<LayerConfigJson> {
const specialVis: Exclude<RenderingSpecification, string>[] = < const specialVis: Exclude<RenderingSpecification, string>[] = <
Exclude<RenderingSpecification, string>[] Exclude<RenderingSpecification, string>[]
>ValidationUtils.getAllSpecialVisualisations(<any>json.tagRenderings).filter( >ValidationUtils.getAllSpecialVisualisations(<any>json.tagRenderings).filter(
(rs) => typeof rs !== "string" (rs) => typeof rs !== "string",
) )
const funcs = new Set<string>(specialVis.map((rs) => rs.func.funcName)) const funcs = new Set<string>(specialVis.map((rs) => rs.func.funcName))
@ -1222,12 +1224,12 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
super( super(
"The auto-icon creates a (non-clickable) title icon based on a tagRendering which has icons", "The auto-icon creates a (non-clickable) title icon based on a tagRendering which has icons",
["titleIcons"], ["titleIcons"],
"AutoTitleIcon" "AutoTitleIcon",
) )
} }
private createTitleIconsBasedOn( private createTitleIconsBasedOn(
tr: QuestionableTagRenderingConfigJson tr: QuestionableTagRenderingConfigJson,
): TagRenderingConfigJson | undefined { ): TagRenderingConfigJson | undefined {
const mappings: { if: TagConfigJson; then: string }[] = tr.mappings const mappings: { if: TagConfigJson; then: string }[] = tr.mappings
?.filter((m) => m.icon !== undefined) ?.filter((m) => m.icon !== undefined)
@ -1257,7 +1259,7 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
return undefined return undefined
} }
return this.createTitleIconsBasedOn(<any>tr) return this.createTitleIconsBasedOn(<any>tr)
}) }),
) )
json.titleIcons.splice(allAutoIndex, 1, ...generated) json.titleIcons.splice(allAutoIndex, 1, ...generated)
return json return json
@ -1287,7 +1289,7 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
.warn( .warn(
"TagRendering with id " + "TagRendering with id " +
trId + trId +
" does not have any icons, not generating an icon for this" " does not have any icons, not generating an icon for this",
) )
continue continue
} }
@ -1297,13 +1299,48 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
} }
} }
class DeriveSource extends DesugaringStep<LayerConfigJson> {
constructor() {
super("If no source is given, automatically derives the osmTags by 'or'-ing all the preset tags", ["source"], "DeriveSource")
}
public convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (json.source) {
return json
}
if (!json.presets) {
context.err("No source tags given. Trying to derive the source-tags based on the presets, but no presets are given")
return json
}
json = { ...json }
const raw = { or: json.presets.map(pr => ({ and: pr.tags })) }
const osmTags = TagUtils.optimzeJson(raw)
if (osmTags === false) {
context.err("The preset-tags optimize to 'false' " + JSON.stringify(raw))
return json
}
if (osmTags === true) {
context.err("The preset-tags optimize to 'true' " + JSON.stringify(raw))
return json
}
json.source = { osmTags }
return json
}
}
export class PrepareLayer extends Fuse<LayerConfigJson> { export class PrepareLayer extends Fuse<LayerConfigJson> {
constructor( constructor(
state: DesugaringContext, state: DesugaringContext,
options?: { addTagRenderingsToContext?: false | boolean } options?: { addTagRenderingsToContext?: false | boolean },
) { ) {
super( super(
"Fully prepares and expands a layer for the LayerConfig.", "Fully prepares and expands a layer for the LayerConfig.",
new DeriveSource(),
new On("tagRenderings", new Each(new RewriteSpecial())), new On("tagRenderings", new Each(new RewriteSpecial())),
new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)), new On("tagRenderings", new Concat(new ExpandRewrite()).andThenF(Utils.Flatten)),
new On( new On(
@ -1312,8 +1349,8 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
new Concat( new Concat(
new ExpandTagRendering(state, layer, { new ExpandTagRendering(state, layer, {
addToContext: options?.addTagRenderingsToContext ?? false, addToContext: options?.addTagRenderingsToContext ?? false,
}) }),
) ),
), ),
new On("tagRenderings", new Each(new DetectInline())), new On("tagRenderings", new Each(new DetectInline())),
new AddQuestionBox(), new AddQuestionBox(),
@ -1326,11 +1363,11 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
new On<PointRenderingConfigJson[], LayerConfigJson>( new On<PointRenderingConfigJson[], LayerConfigJson>(
"pointRendering", "pointRendering",
(layer) => (layer) =>
new Each(new On("marker", new Each(new ExpandMarkerRenderings(state, layer)))) new Each(new On("marker", new Each(new ExpandMarkerRenderings(state, layer)))),
), ),
new On<PointRenderingConfigJson[], LayerConfigJson>( new On<PointRenderingConfigJson[], LayerConfigJson>(
"pointRendering", "pointRendering",
(layer) => new Each(new PreparePointRendering(state, layer)) (layer) => new Each(new PreparePointRendering(state, layer)),
), ),
new SetDefault("titleIcons", ["icons.defaults"]), new SetDefault("titleIcons", ["icons.defaults"]),
new AddRatingBadge(), new AddRatingBadge(),
@ -1339,9 +1376,9 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
new On( new On(
"titleIcons", "titleIcons",
(layer) => (layer) =>
new Concat(new ExpandTagRendering(state, layer, { noHardcodedStrings: true })) new Concat(new ExpandTagRendering(state, layer, { noHardcodedStrings: true })),
), ),
new ExpandFilter(state) new ExpandFilter(state),
) )
} }
} }

View file

@ -9,6 +9,7 @@ import DeleteConfig from "../DeleteConfig"
import { And } from "../../../Logic/Tags/And" import { And } from "../../../Logic/Tags/And"
import { DoesImageExist, ValidateFilter, ValidatePointRendering } from "./Validation" import { DoesImageExist, ValidateFilter, ValidatePointRendering } from "./Validation"
import { ValidateTagRenderings } from "./ValidateTagRenderings" import { ValidateTagRenderings } from "./ValidateTagRenderings"
import { TagsFilterClosed } from "../../../Logic/Tags/TagTypes"
export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> { export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
private readonly _isBuiltin: boolean private readonly _isBuiltin: boolean
@ -24,7 +25,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
path: string, path: string,
isBuiltin: boolean, isBuiltin: boolean,
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
studioValidations: boolean studioValidations: boolean,
) { ) {
super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer") super("Runs various checks against common mistakes for a layer", [], "PrevalidateLayer")
this._path = path this._path = path
@ -47,18 +48,21 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
} }
if (json.source === undefined) { if (json.source === undefined) {
if (json.presets?.length < 1) {
context context
.enter("source") .enter("source")
.err( .err(
"No source section is defined; please define one as data is not loaded otherwise" "No source section is defined; please define one as data is not loaded otherwise",
) )
}
} else { } else {
if (json.source === "special" || json.source === "special:library") { if (json.source === "special" || json.source === "special:library") {
} else if (json.source && json.source["osmTags"] === undefined) { } else if (json.source && json.source["osmTags"] === undefined) {
context context
.enters("source", "osmTags") .enters("source", "osmTags")
.err( .err(
"No osmTags defined in the source section - these should always be present, even for geojson layer" "No osmTags defined in the source section - these should always be present, even for geojson layer",
) )
} else { } else {
const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags") const osmTags = TagUtils.Tag(json.source["osmTags"], context + "source.osmTags")
@ -67,7 +71,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
.enters("source", "osmTags") .enters("source", "osmTags")
.err( .err(
"The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" + "The source states tags which give a very wide selection: it only uses negative expressions, which will result in too much and unexpected data. Add at least one required tag. The tags are:\n\t" +
osmTags.asHumanString(false, false, {}) osmTags.asHumanString(false, false, {}),
) )
} }
} }
@ -96,7 +100,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") + LayerConfig.syncSelectionAllowed.map((v) => `'${v}'`).join(", ") +
" but got '" + " but got '" +
json.syncSelection + json.syncSelection +
"'" "'",
) )
} }
if (json["pointRenderings"]?.length > 0) { if (json["pointRenderings"]?.length > 0) {
@ -115,7 +119,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
} }
json.pointRendering?.forEach((pr, i) => json.pointRendering?.forEach((pr, i) =>
this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)) this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)),
) )
if (json["mapRendering"]) { if (json["mapRendering"]) {
@ -133,7 +137,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
context.err( context.err(
"Layer " + "Layer " +
json.id + json.id +
" uses 'special' as source.osmTags. However, this layer is not a priviliged layer" " uses 'special' as source.osmTags. However, this layer is not a privileged layer",
) )
} }
} }
@ -148,19 +152,19 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
context context
.enter("title") .enter("title")
.err( .err(
"This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error." "This layer does not have a title defined but it does have tagRenderings. Not having a title will disable the popups, resulting in an unclickable element. Please add a title. If not having a popup is intended and the tagrenderings need to be kept (e.g. in a library layer), set `title: null` to disable this error.",
) )
} }
if (json.title === null) { if (json.title === null) {
context.info( context.info(
"Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set." "Title is `null`. This results in an element that cannot be clicked - even though tagRenderings is set.",
) )
} }
{ {
// Check for multiple, identical builtin questions - usability for studio users // Check for multiple, identical builtin questions - usability for studio users
const duplicates = Utils.Duplicates( const duplicates = Utils.Duplicates(
<string[]>json.tagRenderings.filter((tr) => typeof tr === "string") <string[]>json.tagRenderings.filter((tr) => typeof tr === "string"),
) )
for (let i = 0; i < json.tagRenderings.length; i++) { for (let i = 0; i < json.tagRenderings.length; i++) {
const tagRendering = json.tagRenderings[i] const tagRendering = json.tagRenderings[i]
@ -190,7 +194,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
{ {
// duplicate ids in tagrenderings check // duplicate ids in tagrenderings check
const duplicates = Utils.NoNull( const duplicates = Utils.NoNull(
Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))) Utils.Duplicates(Utils.NoNull((json.tagRenderings ?? []).map((tr) => tr["id"]))),
) )
if (duplicates.length > 0) { if (duplicates.length > 0) {
// It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list // It is tempting to add an index to this warning; however, due to labels the indices here might be different from the index in the tagRendering list
@ -201,8 +205,8 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
duplicates.join(", ") + duplicates.join(", ") +
"\n" + "\n" +
JSON.stringify( JSON.stringify(
json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0) json.tagRenderings.filter((tr) => duplicates.indexOf(tr["id"]) >= 0),
) ),
) )
} }
} }
@ -236,7 +240,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
context.err( context.err(
"Layer " + "Layer " +
json.id + json.id +
'still uses the old \'overpassTags\'-format. Please use "source": {"osmTags": <tags>}\' instead of "overpassTags": <tags> (note: this isn\'t your fault, the custom theme generator still spits out the old format)' "still uses the old 'overpassTags'-format. Please use \"source\": {\"osmTags\": <tags>}' instead of \"overpassTags\": <tags> (note: this isn't your fault, the custom theme generator still spits out the old format)",
) )
} }
const forbiddenTopLevel = [ const forbiddenTopLevel = [
@ -256,7 +260,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
} }
if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) { if (json["hideUnderlayingFeaturesMinPercentage"] !== undefined) {
context.err( context.err(
"Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'" "Layer " + json.id + " contains an old 'hideUnderlayingFeaturesMinPercentage'",
) )
} }
@ -275,7 +279,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
"Layer is in an incorrect place. The path is " + "Layer is in an incorrect place. The path is " +
this._path + this._path +
", but expected " + ", but expected " +
expected expected,
) )
} }
} }
@ -293,13 +297,13 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
.enter(["tagRenderings", ...emptyIndexes]) .enter(["tagRenderings", ...emptyIndexes])
.err( .err(
`Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join( `Some tagrendering-ids are empty or have an emtpy string; this is not allowed (at ${emptyIndexes.join(
"," ",",
)}])` )}])`,
) )
} }
const duplicateIds = Utils.Duplicates( const duplicateIds = Utils.Duplicates(
(json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions") (json.tagRenderings ?? [])?.map((f) => f["id"]).filter((id) => id !== "questions"),
) )
if (duplicateIds.length > 0 && !Utils.runningFromConsole) { if (duplicateIds.length > 0 && !Utils.runningFromConsole) {
context context
@ -323,7 +327,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
if (json.tagRenderings !== undefined) { if (json.tagRenderings !== undefined) {
new On( new On(
"tagRenderings", "tagRenderings",
new Each(new ValidateTagRenderings(json, this._doesImageExist)) new Each(new ValidateTagRenderings(json, this._doesImageExist)),
).convert(json, context) ).convert(json, context)
} }
@ -350,7 +354,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
context context
.enters("pointRendering", i, "marker", indexM, "icon", "condition") .enters("pointRendering", i, "marker", indexM, "icon", "condition")
.err( .err(
"Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead." "Don't set a condition in a marker as this will result in an invisible but clickable element. Use extra filters in the source instead.",
) )
} }
} }
@ -361,8 +365,11 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
if (typeof json.source === "string") { if (typeof json.source === "string") {
context.enter("presets").err("A special layer cannot have presets") context.enter("presets").err("A special layer cannot have presets")
} }
let baseTags: TagsFilterClosed
if (json.source) {
// Check that a preset will be picked up by the layer itself // Check that a preset will be picked up by the layer itself
const baseTags = TagUtils.Tag(json.source["osmTags"]) baseTags = TagUtils.Tag(json.source["osmTags"])
}
for (let i = 0; i < json.presets.length; i++) { for (let i = 0; i < json.presets.length; i++) {
const preset = json.presets[i] const preset = json.presets[i]
if (!preset) { if (!preset) {
@ -382,6 +389,7 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
for (const tag of tags.asChange({ id: "node/-1" })) { for (const tag of tags.asChange({ id: "node/-1" })) {
properties[tag.k] = tag.v properties[tag.k] = tag.v
} }
if(baseTags) {
const doMatch = baseTags.matchesProperties(properties) const doMatch = baseTags.matchesProperties(properties)
if (!doMatch) { if (!doMatch) {
context context
@ -390,11 +398,12 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
"This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + "This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " +
tags.asHumanString(false, false, {}) + tags.asHumanString(false, false, {}) +
"\n The required tags are: " + "\n The required tags are: " +
baseTags.asHumanString(false, false, {}) baseTags.asHumanString(false, false, {}),
) )
} }
} }
} }
}
return json return json
} }
} }

View file

@ -63,9 +63,11 @@ export interface LayerConfigJson {
* *
* types: Load data with specific tags from OpenStreetMap ; Load data from an external geojson source ; * types: Load data with specific tags from OpenStreetMap ; Load data from an external geojson source ;
* typesdefault: 0 * typesdefault: 0
* ifunset: Determine the tags automatically based on the presets
* group: Basic * group: Basic
*/ */
source: source:
| undefined
| "special" | "special"
| "special:library" | "special:library"
| { | {

View file

@ -81,7 +81,11 @@ export default class LayerConfig extends WithContextLoader {
} }
this.syncSelection = json.syncSelection ?? "no" this.syncSelection = json.syncSelection ?? "no"
if (typeof json.source !== "string") { if(!json.source){
this.source = new SourceConfig({
osmTags: TagUtils.Tag({or: json.presets.map(pr => ({and:pr.tags}))})
})
}else if (typeof json.source !== "string") {
this.maxAgeOfCache = json.source["maxCacheAge"] ?? 24 * 60 * 60 * 30 this.maxAgeOfCache = json.source["maxCacheAge"] ?? 24 * 60 * 60 * 30
this.source = new SourceConfig( this.source = new SourceConfig(
{ {
@ -93,7 +97,7 @@ export default class LayerConfig extends WithContextLoader {
mercatorCrs: json.source["mercatorCrs"], mercatorCrs: json.source["mercatorCrs"],
idKey: json.source["idKey"], idKey: json.source["idKey"],
}, },
json.id json.id,
) )
} }
@ -112,7 +116,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.calculatedTags !== undefined) { if (json.calculatedTags !== undefined) {
if (!official) { if (!official) {
console.warn( console.warn(
`Unofficial theme ${this.id} with custom javascript! This is a security risk` `Unofficial theme ${this.id} with custom javascript! This is a security risk`,
) )
} }
this.calculatedTags = [] this.calculatedTags = []
@ -182,7 +186,7 @@ export default class LayerConfig extends WithContextLoader {
tags: pr.tags.map((t) => TagUtils.SimpleTag(t)), tags: pr.tags.map((t) => TagUtils.SimpleTag(t)),
description: Translations.T( description: Translations.T(
pr.description, pr.description,
`${translationContext}.presets.${i}.description` `${translationContext}.presets.${i}.description`,
), ),
preciseInput: preciseInput, preciseInput: preciseInput,
exampleImages: pr.exampleImages, exampleImages: pr.exampleImages,
@ -196,7 +200,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.lineRendering) { if (json.lineRendering) {
this.lineRendering = Utils.NoNull(json.lineRendering).map( this.lineRendering = Utils.NoNull(json.lineRendering).map(
(r, i) => new LineRenderingConfig(r, `${context}[${i}]`) (r, i) => new LineRenderingConfig(r, `${context}[${i}]`),
) )
} else { } else {
this.lineRendering = [] this.lineRendering = []
@ -204,7 +208,7 @@ export default class LayerConfig extends WithContextLoader {
if (json.pointRendering) { if (json.pointRendering) {
this.mapRendering = Utils.NoNull(json.pointRendering).map( this.mapRendering = Utils.NoNull(json.pointRendering).map(
(r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`) (r, i) => new PointRenderingConfig(r, `${context}[${i}](${this.id})`),
) )
} else { } else {
this.mapRendering = [] this.mapRendering = []
@ -216,7 +220,7 @@ export default class LayerConfig extends WithContextLoader {
r.location.has("centroid") || r.location.has("centroid") ||
r.location.has("projected_centerpoint") || r.location.has("projected_centerpoint") ||
r.location.has("start") || r.location.has("start") ||
r.location.has("end") r.location.has("end"),
) )
if ( if (
@ -238,7 +242,7 @@ export default class LayerConfig extends WithContextLoader {
Constants.priviliged_layers.indexOf(<any>this.id) < 0 && Constants.priviliged_layers.indexOf(<any>this.id) < 0 &&
this.source !== null /*library layer*/ && this.source !== null /*library layer*/ &&
!this.source?.geojsonSource?.startsWith( !this.source?.geojsonSource?.startsWith(
"https://api.openstreetmap.org/api/0.6/notes.json" "https://api.openstreetmap.org/api/0.6/notes.json",
) )
) { ) {
throw ( throw (
@ -257,7 +261,7 @@ export default class LayerConfig extends WithContextLoader {
typeof tr !== "string" && typeof tr !== "string" &&
tr["builtin"] === undefined && tr["builtin"] === undefined &&
tr["id"] === undefined && tr["id"] === undefined &&
tr["rewrite"] === undefined tr["rewrite"] === undefined,
) ?? [] ) ?? []
if (missingIds?.length > 0 && official) { if (missingIds?.length > 0 && official) {
console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds) console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds)
@ -268,8 +272,8 @@ export default class LayerConfig extends WithContextLoader {
(tr, i) => (tr, i) =>
new TagRenderingConfig( new TagRenderingConfig(
<QuestionableTagRenderingConfigJson>tr, <QuestionableTagRenderingConfigJson>tr,
this.id + ".tagRenderings[" + i + "]" this.id + ".tagRenderings[" + i + "]",
) ),
) )
if (json.units !== undefined && !Array.isArray(json.units)) { if (json.units !== undefined && !Array.isArray(json.units)) {
throw ( throw (
@ -279,7 +283,7 @@ export default class LayerConfig extends WithContextLoader {
) )
} }
this.units = (json.units ?? []).flatMap((unitJson, i) => this.units = (json.units ?? []).flatMap((unitJson, i) =>
Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`) Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`),
) )
if ( if (
@ -355,7 +359,7 @@ export default class LayerConfig extends WithContextLoader {
public GetBaseTags(): Record<string, string> { public GetBaseTags(): Record<string, string> {
return TagUtils.changeAsProperties( return TagUtils.changeAsProperties(
this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }] this.source?.osmTags?.asChange({ id: "node/-1" }) ?? [{ k: "id", v: "node/-1" }],
) )
} }
@ -368,7 +372,7 @@ export default class LayerConfig extends WithContextLoader {
neededLayer: string neededLayer: string
}[] = [], }[] = [],
addedByDefault = false, addedByDefault = false,
canBeIncluded = true canBeIncluded = true,
): string { ): string {
const extraProps: string[] = [] const extraProps: string[] = []
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher") extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
@ -376,32 +380,32 @@ export default class LayerConfig extends WithContextLoader {
if (canBeIncluded) { if (canBeIncluded) {
if (addedByDefault) { if (addedByDefault) {
extraProps.push( extraProps.push(
"**This layer is included automatically in every theme. This layer might contain no points**" "**This layer is included automatically in every theme. This layer might contain no points**",
) )
} }
if (this.shownByDefault === false) { if (this.shownByDefault === false) {
extraProps.push( extraProps.push(
"This layer is not visible by default and must be enabled in the filter by the user. " "This layer is not visible by default and must be enabled in the filter by the user. ",
) )
} }
if (this.title === undefined) { if (this.title === undefined) {
extraProps.push( extraProps.push(
"Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable." "Elements don't have a title set and cannot be toggled nor will they show up in the dashboard. If you import this layer in your theme, override `title` to make this toggleable.",
) )
} }
if (this.name === undefined && this.shownByDefault === false) { if (this.name === undefined && this.shownByDefault === false) {
extraProps.push( extraProps.push(
"This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true" "This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by setting the URL-parameter layer-<id>=true",
) )
} }
if (this.name === undefined) { if (this.name === undefined) {
extraProps.push( extraProps.push(
"Not visible in the layer selection by default. If you want to make this layer toggable, override `name`" "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`",
) )
} }
if (this.mapRendering.length === 0) { if (this.mapRendering.length === 0) {
extraProps.push( extraProps.push(
"Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`" "Not rendered on the map by default. If you want to rendering this on the map, override `mapRenderings`",
) )
} }
@ -411,12 +415,12 @@ export default class LayerConfig extends WithContextLoader {
"<img src='../warning.svg' height='1rem'/>", "<img src='../warning.svg' height='1rem'/>",
"This layer is loaded from an external source, namely ", "This layer is loaded from an external source, namely ",
"`" + this.source.geojsonSource + "`", "`" + this.source.geojsonSource + "`",
].join("\n\n") ].join("\n\n"),
) )
} }
} else { } else {
extraProps.push( extraProps.push(
"This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data." "This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.",
) )
} }
@ -426,7 +430,7 @@ export default class LayerConfig extends WithContextLoader {
usingLayer = [ usingLayer = [
"## Themes using this layer", "## Themes using this layer",
MarkdownUtils.list( MarkdownUtils.list(
(usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`) (usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`),
), ),
] ]
} else if (this.source !== null) { } else if (this.source !== null) {
@ -442,15 +446,15 @@ export default class LayerConfig extends WithContextLoader {
" into the layout as it depends on it: ", " into the layout as it depends on it: ",
dep.reason, dep.reason,
"(" + dep.context + ")", "(" + dep.context + ")",
].join(" ") ].join(" "),
) )
} }
for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) { for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) {
extraProps.push( extraProps.push(
["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join( ["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join(
" " " ",
) ),
) )
} }
@ -461,10 +465,10 @@ export default class LayerConfig extends WithContextLoader {
.filter((values) => values.key !== "id") .filter((values) => values.key !== "id")
.map((values) => { .map((values) => {
const embedded: string[] = values.values?.map((v) => const embedded: string[] = values.values?.map((v) =>
Link.OsmWiki(values.key, v, true).SetClass("mr-2").AsMarkdown() Link.OsmWiki(values.key, v, true).SetClass("mr-2").AsMarkdown(),
) ?? ["_no preset options defined, or no values in them_"] ) ?? ["_no preset options defined, or no values in them_"]
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent( const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
values.key values.key,
)}/` )}/`
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values` const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
return [ return [
@ -479,7 +483,7 @@ export default class LayerConfig extends WithContextLoader {
: `[${values.type}](../SpecialInputElements.md#${values.type})`, : `[${values.type}](../SpecialInputElements.md#${values.type})`,
embedded.join(" "), embedded.join(" "),
] ]
}) }),
) )
let quickOverview: string[] = [] let quickOverview: string[] = []
@ -489,7 +493,7 @@ export default class LayerConfig extends WithContextLoader {
"this quick overview is incomplete", "this quick overview is incomplete",
MarkdownUtils.table( MarkdownUtils.table(
["attribute", "type", "values which are supported by this layer"], ["attribute", "type", "values which are supported by this layer"],
tableRows tableRows,
), ),
] ]
} }
@ -523,19 +527,19 @@ export default class LayerConfig extends WithContextLoader {
const parts = neededTags["and"] const parts = neededTags["and"]
tagsDescription.push( tagsDescription.push(
"Elements must match **all** of the following expressions:", "Elements must match **all** of the following expressions:",
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n") parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
) )
} else if (neededTags["or"]) { } else if (neededTags["or"]) {
const parts = neededTags["or"] const parts = neededTags["or"]
tagsDescription.push( tagsDescription.push(
"Elements must match **any** of the following expressions:", "Elements must match **any** of the following expressions:",
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n") parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
) )
} else { } else {
tagsDescription.push( tagsDescription.push(
"Elements must match the expression **" + "Elements must match the expression **" +
neededTags.asHumanString(true, false, {}) + neededTags.asHumanString(true, false, {}) +
"**" "**",
) )
} }