diff --git a/src/Logic/State/LayerState.ts b/src/Logic/State/LayerState.ts index d611f91322..c1e9578ce6 100644 --- a/src/Logic/State/LayerState.ts +++ b/src/Logic/State/LayerState.ts @@ -184,9 +184,7 @@ export default class LayerState { ", but this layer was not loaded" ) } - console.warn( - "Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs - ) + // We make a shallow copy, reusing the same underlying stores const copy = new FilteredLayer(layer, toReuse.appliedFilters, toReuse.isDisplayed) filteredLayers.set(layer.id, copy) } diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index 8504b4fff9..9039b94ee3 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -78,7 +78,11 @@ export default class LayerConfig extends WithContextLoader { */ private readonly _basedOn: string | undefined - constructor(json: LayerConfigJson, context?: string, official: boolean = true) { + constructor(json: LayerConfigJson, + context?: string, + official: boolean = true, + allLayers?: LayerConfigJson[], + ) { context = context + "." + json?.id const translationContext = "layers:" + json.id super(json, context) @@ -109,7 +113,7 @@ export default class LayerConfig extends WithContextLoader { mercatorCrs: json.source["mercatorCrs"], idKey: json.source["idKey"], }, - json.id + json.id, ) } @@ -129,7 +133,7 @@ export default class LayerConfig extends WithContextLoader { if (json.calculatedTags !== undefined) { if (!official) { 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 = [] @@ -199,7 +203,7 @@ export default class LayerConfig extends WithContextLoader { tags: pr.tags.map((t) => TagUtils.SimpleTag(t)), description: Translations.T( pr.description, - `${translationContext}.presets.${i}.description` + `${translationContext}.presets.${i}.description`, ), preciseInput: preciseInput, exampleImages: pr.exampleImages, @@ -213,7 +217,7 @@ export default class LayerConfig extends WithContextLoader { if (json.lineRendering) { this.lineRendering = Utils.NoNull(json.lineRendering).map( - (r, i) => new LineRenderingConfig(r, `${context}[${i}]`) + (r, i) => new LineRenderingConfig(r, `${context}[${i}]`), ) } else { this.lineRendering = [] @@ -221,7 +225,7 @@ export default class LayerConfig extends WithContextLoader { if (json.pointRendering) { 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 { this.mapRendering = [] @@ -233,7 +237,7 @@ export default class LayerConfig extends WithContextLoader { r.location.has("centroid") || r.location.has("projected_centerpoint") || r.location.has("start") || - r.location.has("end") + r.location.has("end"), ) if ( @@ -255,7 +259,7 @@ export default class LayerConfig extends WithContextLoader { Constants.priviliged_layers.indexOf(this.id) < 0 && this.source !== null /*library layer*/ && !this.source?.geojsonSource?.startsWith( - "https://api.openstreetmap.org/api/0.6/notes.json" + "https://api.openstreetmap.org/api/0.6/notes.json", ) ) { throw ( @@ -274,7 +278,7 @@ export default class LayerConfig extends WithContextLoader { typeof tr !== "string" && tr["builtin"] === undefined && tr["id"] === undefined && - tr["rewrite"] === undefined + tr["rewrite"] === undefined, ) ?? [] if (missingIds?.length > 0 && official) { console.error("Some tagRenderings of", this.id, "are missing an id:", missingIds) @@ -285,8 +289,8 @@ export default class LayerConfig extends WithContextLoader { (tr, i) => new TagRenderingConfig( tr, - this.id + ".tagRenderings[" + i + "]" - ) + this.id + ".tagRenderings[" + i + "]", + ), ) if (json.units !== undefined && !Array.isArray(json.units)) { throw ( @@ -296,22 +300,33 @@ export default class LayerConfig extends WithContextLoader { ) } this.units = (json.units ?? []).flatMap((unitJson, i) => - Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`) + Unit.fromJson(unitJson, this.tagRenderings, `${context}.unit[${i}]`), ) + { + let filter = json.filter + + while ( + filter !== undefined && + filter !== null && + filter["sameAs"] !== undefined + ) { + const targetLayerName = filter["sameAs"] + this.filterIsSameAs = targetLayerName + const targetLayer = allLayers.find(l => l.id === targetLayerName) + if(!targetLayer){ + throw "Target layer "+targetLayerName+" not found in this theme" + } + filter = targetLayer?.filter + } - if ( - json.filter !== undefined && - json.filter !== null && - json.filter["sameAs"] !== undefined - ) { - this.filterIsSameAs = json.filter["sameAs"] this.filters = [] - } else { - this.filters = (json.filter ?? []) - .filter((f) => typeof f !== "string") - .map((option, i) => { - return new FilterConfig(option, `layers:${this.id}.filter.${i}`) - }) + { + this.filters = (filter ?? []) + .filter((f) => typeof f !== "string") + .map((option, i) => { + return new FilterConfig(option, `layers:${this.id}.filter.${i}`) + }) + } } { @@ -358,7 +373,7 @@ export default class LayerConfig extends WithContextLoader { } this.popupInFloatover = json.popupInFloatover ?? false this.baseTags = 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,6 +383,7 @@ export default class LayerConfig extends WithContextLoader { } return this.mapRendering.some((r) => r.location.has("point")) } + public GenerateDocumentation( usedInThemes: string[], layerIsNeededBy?: Map, @@ -377,7 +393,7 @@ export default class LayerConfig extends WithContextLoader { neededLayer: string }[] = [], addedByDefault = false, - canBeIncluded = true + canBeIncluded = true, ): string { const extraProps: string[] = [] extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher") @@ -385,32 +401,32 @@ export default class LayerConfig extends WithContextLoader { if (canBeIncluded) { if (addedByDefault) { 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) { 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) { 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) { 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-=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-=true", ) } if (this.name === undefined) { 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) { 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`", ) } @@ -420,12 +436,12 @@ export default class LayerConfig extends WithContextLoader { "", "This layer is loaded from an external source, namely ", "`" + this.source.geojsonSource + "`", - ].join("\n\n") + ].join("\n\n"), ) } } else { 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.", ) } @@ -435,7 +451,7 @@ export default class LayerConfig extends WithContextLoader { usingLayer = [ "## Themes using this layer", MarkdownUtils.list( - (usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`) + (usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`), ), ] } else if (this.source !== null) { @@ -451,7 +467,7 @@ export default class LayerConfig extends WithContextLoader { " into the layout as it depends on it: ", dep.reason, "(" + dep.context + ")", - ].join(" ") + ].join(" "), ) } @@ -478,7 +494,7 @@ export default class LayerConfig extends WithContextLoader { new And(preset.tags).asHumanString(true) + snaps ) - }) + }), ), ] } @@ -486,8 +502,8 @@ export default class LayerConfig extends WithContextLoader { for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) { extraProps.push( ["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join( - " " - ) + " ", + ), ) } @@ -498,10 +514,10 @@ export default class LayerConfig extends WithContextLoader { .filter((values) => values.key !== "id") .map((values) => { 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_"] const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent( - values.key + values.key, )}/` const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values` return [ @@ -516,7 +532,7 @@ export default class LayerConfig extends WithContextLoader { : `[${values.type}](../SpecialInputElements.md#${values.type})`, embedded.join(" "), ] - }) + }), ) let quickOverview: string[] = [] @@ -526,7 +542,7 @@ export default class LayerConfig extends WithContextLoader { "this quick overview is incomplete", MarkdownUtils.table( ["attribute", "type", "values which are supported by this layer"], - tableRows + tableRows, ), ] } @@ -560,19 +576,19 @@ export default class LayerConfig extends WithContextLoader { const parts = neededTags["and"] tagsDescription.push( "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"]) { const parts = neededTags["or"] tagsDescription.push( "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 { tagsDescription.push( "Elements must match the expression **" + - neededTags.asHumanString(true, false, {}) + - "**" + neededTags.asHumanString(true, false, {}) + + "**", ) } diff --git a/src/Models/ThemeConfig/ThemeConfig.ts b/src/Models/ThemeConfig/ThemeConfig.ts index 7db9787a79..5ef592c7fa 100644 --- a/src/Models/ThemeConfig/ThemeConfig.ts +++ b/src/Models/ThemeConfig/ThemeConfig.ts @@ -99,7 +99,7 @@ export default class ThemeConfig implements ThemeInformation { options?: { definedAtUrl?: string definitionRaw?: string - } + }, ) { if (json === undefined) { throw "Cannot construct a layout config, the parameter 'json' is undefined" @@ -130,7 +130,7 @@ export default class ThemeConfig implements ThemeInformation { throw `The title of a theme should always be a translation, as it sets the corresponding languages (${context}.title). The themenID is ${ this.id }; the offending object is ${JSON.stringify( - json.title + json.title, )} which is a ${typeof json.title})` } if (this.language.length == 0) { @@ -183,8 +183,9 @@ export default class ThemeConfig implements ThemeInformation { new LayerConfig( lyrJson, json.id + ".layers." + lyrJson["id"], - official - ) + official, + json.layers, + ), ) this.extraLink = new ExtraLinkConfig( @@ -194,7 +195,7 @@ export default class ThemeConfig implements ThemeInformation { newTab: true, requirements: ["iframe", "no-welcome-message"], }, - context + ".extraLink" + context + ".extraLink", ) this.hideFromOverview = json.hideFromOverview ?? false @@ -300,7 +301,7 @@ export default class ThemeConfig implements ThemeInformation { return false } return o instanceof Translation - } + }, ) return { untranslated, total } @@ -308,7 +309,7 @@ export default class ThemeConfig implements ThemeInformation { public getMatchingLayer( tags: Record, - blacklistLayers?: Set + blacklistLayers?: Set, ): LayerConfig | undefined { if (tags === undefined) { return undefined @@ -337,7 +338,7 @@ export default class ThemeConfig implements ThemeInformation { "Fallthrough: could not find the appropriate layer for an object with tags", tags, "within layout", - this + this, ) return undefined }