Merge develop

This commit is contained in:
Pieter Vander Vennet 2025-01-21 21:14:11 +01:00
commit 4fdae55c97
206 changed files with 8760 additions and 3718 deletions

View file

@ -13,49 +13,96 @@ import { Or } from "../../../Logic/Tags/Or"
import Translations from "../../../UI/i18n/Translations"
import { FlatTag, OptimizedTag, TagsFilterClosed } from "../../../Logic/Tags/TagTypes"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import { And } from "../../../Logic/Tags/And"
import { Translation } from "../../../UI/i18n/Translation"
export class PruneFilters extends DesugaringStep<LayerConfigJson>{
export class PruneFilters extends DesugaringStep<LayerConfigJson> {
constructor() {
super("Removes all filters which are impossible, e.g. because they conflict with the base tags", ["filter"],"PruneFilters")
super(
"Removes all filters which are impossible, e.g. because they conflict with the base tags",
["filter"],
"PruneFilters"
)
}
private prune(sourceTags:FlatTag, filter: FilterConfigJson, context: ConversionContext): FilterConfigJson{
if(!filter.strict){
/**
* Prunes a filter; returns null/undefined if keeping the filter is useless
*/
private prune(
sourceTags: FlatTag,
filter: FilterConfigJson,
context: ConversionContext
): FilterConfigJson {
if (filter.options.length === 1) {
const option = filter.options[0]
const tags = TagUtils.Tag(option.osmTags)
const optimized = TagUtils.removeKnownParts(tags, sourceTags, true)
if (optimized === true) {
context.warn("Removing filter as always known: ", new Translation(option.question).textFor("en"))
return undefined
}
if (optimized === false) {
context.warn("Removing filter as not possible: ", new Translation(option.question).textFor("en"))
return undefined
}
}
if (!filter.strict) {
return filter
}
const countBefore = filter.options.length
const newOptions: FilterConfigOptionJson[] = filter.options.filter(option => {
if(!option.osmTags){
return true
}
const condition = <OptimizedTag & TagsFilterClosed> TagUtils.Tag(option.osmTags).optimize()
return condition.shadows(sourceTags);
}).map(option => {
if(!option.osmTags){
return option
}
let basetags = TagUtils.Tag(option.osmTags)
return {...option, osmTags: (<TagsFilter>TagUtils.removeKnownParts(basetags ,sourceTags)).asJson()}
})
const newOptions: FilterConfigOptionJson[] = filter.options
.filter((option) => {
if (!option.osmTags) {
return true
}
const condition = <OptimizedTag & TagsFilterClosed>(
TagUtils.Tag(option.osmTags).optimize()
)
return condition.shadows(sourceTags)
})
.map((option) => {
if (!option.osmTags) {
return option
}
const basetags = TagUtils.Tag(option.osmTags)
return {
...option,
osmTags: (<TagsFilter>TagUtils.removeKnownParts(basetags, sourceTags)).asJson(),
}
})
const countAfter = newOptions.length
if(countAfter !== countBefore){
context.enters("filter", filter.id ).info("Pruned "+(countBefore-countAfter)+" options away from filter (out of "+countBefore+")")
if (countAfter !== countBefore) {
context
.enters("filter", filter.id)
.info(
"Pruned " +
(countBefore - countAfter) +
" options away from filter (out of " +
countBefore +
")"
)
}
return {...filter, options: newOptions, strict: undefined}
return { ...filter, options: newOptions, strict: undefined }
}
public convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if(!Array.isArray(json.filter) || typeof json.source === "string"){
if (!Array.isArray(json.filter) || typeof json.source === "string") {
return json
}
if(!json.source["osmTags"]){
if (!json.source["osmTags"]) {
return json
}
const sourceTags = TagUtils.Tag(json.source["osmTags"])
return {...json, filter: json.filter?.map(obj => this.prune(sourceTags, <FilterConfigJson> obj, context))}
return {
...json,
filter: Utils.NoNull(json.filter?.map((obj) =>
this.prune(sourceTags, <FilterConfigJson>obj, context)
)),
}
}
}
export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
@ -69,7 +116,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
"If the string is formatted 'layername.filtername, it will be looked up into that layer instead. Note that pruning should still be done",
].join(" "),
["filter"],
"ExpandFilter",
"ExpandFilter"
)
this._state = state
}
@ -84,11 +131,11 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
public static buildFilterFromTagRendering(
tr: TagRenderingConfigJson,
context: ConversionContext,
context: ConversionContext
): FilterConfigJson {
if (!(tr.mappings?.length >= 1)) {
context.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 qtr = <QuestionableTagRenderingConfigJson>tr
@ -103,7 +150,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
if (qtr.multiAnswer && osmTags instanceof Tag) {
osmTags = new RegexTag(
osmTags.key,
new RegExp("^(.+;)?" + osmTags.value + "(;.+)$", "is"),
new RegExp("^(.+;)?" + osmTags.value + "(;.+)$", "is")
)
}
if (mapping.alsoShowIf) {
@ -161,7 +208,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
if (matchingTr) {
const filter = ExpandFilter.buildFilterFromTagRendering(
matchingTr,
context.enters("filter", i),
context.enters("filter", i)
)
newFilters.push(filter)
continue
@ -175,7 +222,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
const split = filter.split(".")
if (split.length > 2) {
context.err(
"invalid filter name: " + filter + ", expected `layername.filterid`",
"invalid filter name: " + filter + ", expected `layername.filterid`"
)
}
const layer = this._state.sharedLayers.get(split[0])
@ -184,7 +231,7 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
}
const expectedId = split[1]
const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find(
(f) => typeof f !== "string" && f.id === expectedId,
(f) => typeof f !== "string" && f.id === expectedId
)
if (expandedFilter === undefined) {
context.err("Did not find filter with name " + filter)
@ -199,15 +246,15 @@ export class ExpandFilter extends DesugaringStep<LayerConfigJson> {
const suggestions = Utils.sortedByLevenshteinDistance(
filter,
Array.from(ExpandFilter.predefinedFilters.keys()),
(t) => t,
(t) => t
)
context
.enter(filter)
.err(
"While searching for predefined filter " +
filter +
": this filter is not found. Perhaps you meant one of: " +
suggestions,
filter +
": this filter is not found. Perhaps you meant one of: " +
suggestions
)
}
newFilters.push(found)

View file

@ -11,9 +11,9 @@ export class ExpandTagRendering extends Conversion<
| string
| TagRenderingConfigJson
| {
builtin: string | string[]
override: any
},
builtin: string | string[]
override: any
},
TagRenderingConfigJson[]
> {
private readonly _state: DesugaringContext
@ -35,12 +35,12 @@ export class ExpandTagRendering extends Conversion<
noHardcodedStrings?: false | boolean
// If set, a question will be added to the 'sharedTagRenderings'. Should only be used for 'questions.json'
addToContext?: false | boolean
},
}
) {
super(
"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._self = self
@ -59,11 +59,13 @@ export class ExpandTagRendering extends Conversion<
}
public convert(
spec: string | any,
ctx: ConversionContext,
spec: string | { "builtin": string | string[] } | (TagRenderingConfigJson),
ctx: ConversionContext
): QuestionableTagRenderingConfigJson[] {
const trs = this.convertOnce(spec, ctx)
?.map(tr => this.pruneMappings<TagRenderingConfigJson & { id: string }>(tr, ctx))
const trs = this.convertOnce(<any>spec, ctx)?.map((tr) =>
this.pruneMappings<TagRenderingConfigJson & { id: string }>(tr, ctx)
)
if (!Array.isArray(trs)) {
ctx.err("Result of lookup for " + spec + " is not iterable; got " + trs)
return undefined
@ -71,8 +73,9 @@ export class ExpandTagRendering extends Conversion<
const result = []
for (const tr of trs) {
if (typeof tr === "string" || tr["builtin"] !== undefined) {
const stable = this.convert(tr, ctx.inOperation("recursive_resolve"))
.map(tr => this.pruneMappings(tr, ctx))
const stable = this.convert(tr, ctx.inOperation("recursive_resolve")).map((tr) =>
this.pruneMappings(tr, ctx)
)
result.push(...stable)
if (this._options?.addToContext) {
for (const tr of stable) {
@ -90,49 +93,60 @@ export class ExpandTagRendering extends Conversion<
return result
}
private pruneMappings<T extends (TagRenderingConfigJson & {
id: string
})>(tagRendering: T, ctx: ConversionContext): T {
private pruneMappings<
T extends TagRenderingConfigJson & {
id: string
}
>(tagRendering: T, ctx: ConversionContext): T {
if (!tagRendering["strict"]) {
return tagRendering
}
if(!this._self.source["osmTags"]){
if (!this._self.source["osmTags"]) {
return tagRendering
}
ctx.inOperation("expandTagRendering:pruning").enters(tagRendering.id)
.info(`PRUNING! Tagrendering to prune: ${tagRendering.id} in the context of layer ${this._self.id} Sourcetags: ${this._self.source["osmTags"]}`)
ctx.inOperation("expandTagRendering:pruning")
.enters(tagRendering.id)
.info(
`PRUNING! Tagrendering to prune: ${tagRendering.id} in the context of layer ${this._self.id} Sourcetags: ${this._self.source["osmTags"]}`
)
const before = tagRendering.mappings?.length ?? 0
const alwaysTags = TagUtils.Tag(this._self.source["osmTags"])
const newMappings = tagRendering.mappings?.filter(mapping => {
const condition = TagUtils.Tag(mapping.if)
return condition.shadows(alwaysTags);
}).map(mapping => {
const newIf = TagUtils.removeKnownParts(
TagUtils.Tag(mapping.if), alwaysTags)
if (typeof newIf === "boolean") {
throw "Invalid removeKnownParts"
}
return {
...mapping,
if: newIf.asJson(),
}
})
const newMappings = tagRendering.mappings
?.filter((mapping) => {
const condition = TagUtils.Tag(mapping.if)
return condition.shadows(alwaysTags)
})
.map((mapping) => {
const newIf = TagUtils.removeKnownParts(TagUtils.Tag(mapping.if), alwaysTags)
if (typeof newIf === "boolean") {
throw "Invalid removeKnownParts"
}
return {
...mapping,
if: newIf.asJson()
}
})
const after = newMappings?.length ?? 0
if (before - after > 0) {
ctx.info(`Pruned mappings for ${tagRendering.id}, from ${before} to ${after} (removed ${before - after})`)
ctx.info(
`Pruned mappings for ${tagRendering.id}, from ${before} to ${after} (removed ${
before - after
})`
)
}
const tr = {
...tagRendering,
mappings: newMappings,
mappings: newMappings
}
delete tr["strict"]
return tr
}
private lookup(name: string, ctx: ConversionContext): (TagRenderingConfigJson & { id: string })[] | undefined {
private lookup(
name: string,
ctx: ConversionContext
): (TagRenderingConfigJson & { id: string })[] | undefined {
const direct = this.directLookup(name)
if (direct === undefined) {
@ -142,17 +156,17 @@ export class ExpandTagRendering extends Conversion<
for (const tagRenderingConfigJson of direct) {
const nm: string | string[] | undefined = tagRenderingConfigJson["builtin"]
if (nm !== undefined) {
let indirect: TagRenderingConfigJson[]
let indirect: (TagRenderingConfigJson & { id: string })[]
if (typeof nm === "string") {
indirect = this.lookup(nm, ctx)
} else {
indirect = [].concat(...nm.map((n) => this.lookup(n, ctx)))
}
for (let foundTr of indirect) {
foundTr = Utils.Clone<any>(foundTr)
foundTr = Utils.Clone(foundTr)
ctx.MergeObjectsForOverride(tagRenderingConfigJson["override"] ?? {}, foundTr)
foundTr["id"] = tagRenderingConfigJson["id"] ?? foundTr["id"]
result.push(<any>foundTr)
result.push(foundTr)
}
} else {
result.push(tagRenderingConfigJson)
@ -202,9 +216,11 @@ export class ExpandTagRendering extends Conversion<
matchingTrs = layerTrs.filter((tr) => tr["id"] === id || tr["labels"]?.indexOf(id) >= 0)
}
const contextWriter = new AddContextToTranslations<TagRenderingConfigJson & { id: string }>("layers:")
const contextWriter = new AddContextToTranslations<TagRenderingConfigJson & { id: string }>(
"layers:"
)
for (let i = 0; i < matchingTrs.length; i++) {
let found: (TagRenderingConfigJson & { id: string }) = Utils.Clone(matchingTrs[i])
let found: TagRenderingConfigJson & { id: string } = Utils.Clone(matchingTrs[i])
if (this._options?.applyCondition) {
// The matched tagRenderings are 'stolen' from another layer. This means that they must match the layer condition before being shown
if (typeof layer.source !== "string") {
@ -220,8 +236,8 @@ export class ExpandTagRendering extends Conversion<
found,
ConversionContext.construct(
[layer.id, "tagRenderings", found["id"]],
["AddContextToTranslations"],
),
["AddContextToTranslations"]
)
)
matchingTrs[i] = found
}
@ -232,9 +248,16 @@ export class ExpandTagRendering extends Conversion<
return undefined
}
private convertOnce(tr: string | any, ctx: ConversionContext): (TagRenderingConfigJson & { id: string })[] {
private convertOnce(
tr: string | { "builtin": string } | TagRenderingConfigJson,
ctx: ConversionContext
): TagRenderingConfigJson[] {
const state = this._state
if (tr === undefined) {
return []
}
if (typeof tr === "string") {
if (this._state.tagRenderings !== null) {
const lookup = this.lookup(tr, ctx)
@ -250,7 +273,7 @@ export class ExpandTagRendering extends Conversion<
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(", "),
Array.from(state.sharedLayers.keys()).join(", ")
)
}
@ -260,15 +283,15 @@ export class ExpandTagRendering extends Conversion<
tr +
" \n Did you perhaps forget to add the layer as prefix, such as `icons." +
tr +
"`? ",
"`? "
)
}
return [
<any>{
<TagRenderingConfigJson & { id: string }>{
render: tr,
id: tr.replace(/[^a-zA-Z0-9]/g, ""),
},
id: tr.replace(/[^a-zA-Z0-9]/g, "")
}
]
}
@ -295,7 +318,7 @@ export class ExpandTagRendering extends Conversion<
"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),
JSON.stringify(tr)
)
}
@ -317,7 +340,7 @@ export class ExpandTagRendering extends Conversion<
const candidates = Utils.sortedByLevenshteinDistance(
layerName,
Utils.NoNull(Array.from(state.sharedLayers.keys())),
(s) => s,
(s) => s
)
if (state.sharedLayers.size === 0) {
ctx.warn(
@ -325,7 +348,7 @@ export class ExpandTagRendering extends Conversion<
name +
": layer " +
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 {
ctx.err(
@ -334,13 +357,13 @@ export class ExpandTagRendering extends Conversion<
": layer " +
layerName +
" not found. Maybe you meant one of " +
candidates.slice(0, 3).join(", "),
candidates.slice(0, 3).join(", ")
)
}
continue
}
candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map(
(id) => layerName + "." + id,
(id) => layerName + "." + id
)
}
candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i)
@ -349,12 +372,12 @@ export class ExpandTagRendering extends Conversion<
name +
" was not found.\n\tDid you mean one of " +
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
}
for (let foundTr of lookup) {
foundTr = Utils.Clone<any>(foundTr)
foundTr = Utils.Clone(foundTr)
ctx.MergeObjectsForOverride(tr["override"] ?? {}, foundTr)
if (names.length == 1) {
foundTr["id"] = tr["id"] ?? foundTr["id"]
@ -365,6 +388,6 @@ export class ExpandTagRendering extends Conversion<
return trs
}
return [tr]
return [<TagRenderingConfigJson & { id: string }>tr]
}
}

View file

@ -24,7 +24,7 @@ import { ExpandTagRendering } from "./ExpandTagRendering"
class AddFiltersFromTagRenderings extends DesugaringStep<LayerConfigJson> {
constructor() {
super(
'Inspects all the tagRenderings. If some tagRenderings have the `filter` attribute set, introduce those filters. This step might introduce shorthand filter names, thus \'ExpandFilter\' should be run afterwards. Can be disabled with "#filter":"no-auto"',
"Inspects all the tagRenderings. If some tagRenderings have the `filter` attribute set, introduce those filters. This step might introduce shorthand filter names, thus 'ExpandFilter' should be run afterwards. Can be disabled with \"#filter\":\"no-auto\"",
["filter"],
"AddFiltersFromTagRenderings"
)
@ -127,7 +127,7 @@ class DetectInline extends DesugaringStep<QuestionableTagRenderingConfigJson> {
if (json.freeform.inline === true) {
context.err(
"'inline' is set, but the rendering contains a special visualisation...\n " +
spec[key]
spec[key]
)
}
json = JSON.parse(JSON.stringify(json))
@ -226,20 +226,20 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
if (blacklisted?.length > 0 && used?.length > 0) {
context.err(
"The {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." +
"\n Whitelisted: " +
used.join(", ") +
"\n Blacklisted: " +
blacklisted.join(", ")
"\n Whitelisted: " +
used.join(", ") +
"\n Blacklisted: " +
blacklisted.join(", ")
)
}
for (const usedLabel of used) {
if (!allLabels.has(usedLabel)) {
context.err(
"This layers specifies a special question element for label `" +
usedLabel +
"`, but this label doesn't exist.\n" +
" Available labels are " +
Array.from(allLabels).join(", ")
usedLabel +
"`, but this label doesn't exist.\n" +
" Available labels are " +
Array.from(allLabels).join(", ")
)
}
seen.add(usedLabel)
@ -253,8 +253,8 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
const question: QuestionableTagRenderingConfigJson = {
id: "leftover-questions",
render: {
"*": `{questions( ,${Array.from(seen).join(";")})}`,
},
"*": `{questions( ,${Array.from(seen).join(";")})}`
}
}
json.tagRenderings.push(question)
}
@ -336,13 +336,13 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
if (json.allowMove && !usedSpecialFunctions.has("move_button")) {
json.tagRenderings.push({
id: "move-button",
render: { "*": "{move_button()}" },
render: { "*": "{move_button()}" }
})
}
if (json.deletion && !usedSpecialFunctions.has("delete_button")) {
json.tagRenderings.push({
id: "delete-button",
render: { "*": "{delete_button()}" },
render: { "*": "{delete_button()}" }
})
}
@ -357,9 +357,9 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
or: [
"__featureSwitchIsDebugging=true",
"mapcomplete-show_tags=full",
"mapcomplete-show_debug=yes",
],
},
"mapcomplete-show_debug=yes"
]
}
}
json.tagRenderings?.push(trc)
}
@ -467,10 +467,10 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
private static convertIfNeeded(
input:
| (object & {
special: {
type: string
}
})
special: {
type: string
}
})
| any,
context: ConversionContext
): any {
@ -568,7 +568,7 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
.map((nm) => RewriteSpecial.escapeStr(special[nm] ?? "", context))
.join(",")
return {
"*": `{${type}(${args})${clss}}`,
"*": `{${type}(${args})${clss}}`
}
}
@ -666,11 +666,50 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
}[] = []
for (let i = 0; i < badgesJson.length; i++) {
const iconBadge: {
const iconBadge: string | ({
if: TagConfigJson
then: string | MinimalTagRenderingConfigJson
} = badgesJson[i]
const expanded = this._expand.convert(
}) = badgesJson[i]
if (typeof iconBadge === "string") {
const expanded: QuestionableTagRenderingConfigJson[] = this._expand.convert(
iconBadge,
context.enters("iconBadges", i)
)
for (const tr of expanded) {
const condition = tr.condition
for (const trElement of tr.mappings) {
const showIf = TagUtils.optimzeJson({
and: Utils.NoNull([condition,
{
or: Utils.NoNull([
trElement.alsoShowIf, trElement.if
])
}
])
})
if (showIf === true) {
context.warn("Dropping iconBadge that would be _always_ shown: " + (trElement.icon ?? trElement.then))
continue
}
if (showIf === false) {
continue
}
iconBadges.push({
if: showIf,
then: trElement.icon ?? trElement.then
})
}
}
continue
}
const expanded: QuestionableTagRenderingConfigJson[] = this._expand.convert(
<QuestionableTagRenderingConfigJson>iconBadge.then,
context.enters("iconBadges", i)
)
@ -682,7 +721,7 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
iconBadges.push(
...expanded.map((resolved) => ({
if: iconBadge.if,
then: <MinimalTagRenderingConfigJson>resolved,
then: <MinimalTagRenderingConfigJson>resolved
}))
)
}
@ -751,19 +790,21 @@ class ExpandMarkerRenderings extends DesugaringStep<IconConfigJson> {
}
convert(json: IconConfigJson, context: ConversionContext): IconConfigJson {
const expander = new ExpandTagRendering(this._state, this._layer, {applyCondition: false})
const expander = new ExpandTagRendering(this._state, this._layer, { applyCondition: false })
const result: IconConfigJson = { icon: undefined, color: undefined }
if (json.icon && json.icon["builtin"]) {
result.icon = <MinimalTagRenderingConfigJson>(
expander.convert(<any>json.icon, context.enter("icon"))[0]
) ?? json.icon
result.icon =
<MinimalTagRenderingConfigJson>(
expander.convert(<any>json.icon, context.enter("icon"))[0]
) ?? json.icon
} else {
result.icon = json.icon
}
if (json.color && json.color["builtin"]) {
result.color = <MinimalTagRenderingConfigJson>(
expander.convert(<any>json.color, context.enter("color"))[0]
) ?? json.color
result.color =
<MinimalTagRenderingConfigJson>(
expander.convert(<any>json.color, context.enter("color"))[0]
) ?? json.color
} else {
result.color = json.color
}
@ -820,7 +861,7 @@ export class AddRatingBadge extends DesugaringStep<LayerConfigJson> {
const specialVis: Exclude<RenderingSpecification, string>[] = <
Exclude<RenderingSpecification, string>[]
>ValidationUtils.getAllSpecialVisualisations(<any>json.tagRenderings).filter(
>ValidationUtils.getAllSpecialVisualisations(<any>json.tagRenderings).filter(
(rs) => typeof rs !== "string"
)
const funcs = new Set<string>(specialVis.map((rs) => rs.func.funcName))
@ -856,7 +897,7 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
}
return <TagRenderingConfigJson>{
id: "title_icon_auto_" + tr.id,
mappings,
mappings
}
}
@ -901,8 +942,8 @@ export class AutoTitleIcon extends DesugaringStep<LayerConfigJson> {
.enters("titleIcons", i)
.warn(
"TagRendering with id " +
trId +
" does not have any icons, not generating an icon for this"
trId +
" does not have any icons, not generating an icon for this"
)
continue
}
@ -965,7 +1006,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
(layer) =>
new Concat(
new ExpandTagRendering(state, layer, {
addToContext: options?.addTagRenderingsToContext ?? false,
addToContext: options?.addTagRenderingsToContext ?? false
})
)
),
@ -1002,7 +1043,7 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
}
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if(json === undefined || json === null){
if (json === undefined || json === null) {
throw "Error: prepareLayer got null"
}
return super.convert(json, context)

View file

@ -112,7 +112,7 @@ export class DoesImageExist extends DesugaringStep<string> {
if (!this._knownImagePaths.has(image)) {
if (this.doesPathExist === undefined || image.indexOf("nsi/logos/") >= 0) {
// pass
} else if (!this.doesPathExist(image) ) {
} else if (!this.doesPathExist(image)) {
context.err(
`Image with path ${image} does not exist.\n Check for typo's and missing directories in the path. `
)

View file

@ -13,14 +13,15 @@ export interface IconConfigJson {
*
*
* types: <span class="text-lg font-bold">Use a different icon depending on the value of some attributes</span> ; icon
* suggestions: return [ "nsi_brand.icon", "nsi_operator.icon", "id_presets.shop_rendering", ...Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i}))]
* suggestions: return [ {"if":"value=nsi_brand.icon", "then": "Use icons for brand from the Name Suggestion Index"}, {"if":"value=nsi_operator.icon", "then": "Use icons for operator from the Name Suggestion Index"}, {"if":"value=id_presets.shop_rendering", "then": "Use shop preset icons from iD"}, ...Constants.defaultPinIcons.map(i => ({if: "value="+i, then: i, icon: i}))]
*/
icon: string | MinimalTagRenderingConfigJson | { builtin: string; override: any }
/**
* question: What colour should the icon be?
* This will only work for the default icons such as `pin`,`circle`,...
* types: <span class="text-lg font-bold">Use a different color depending on the value of some attributes</span> ; color
*
* This will only work for the default icons such as `pin`,`circle`,...
*
* types: <span class="text-lg font-bold">Use a different color depending on the value of some attributes</span> ; color
*/
color?: string | MinimalTagRenderingConfigJson | { builtin: string; override: any }
}
@ -70,16 +71,18 @@ export default interface PointRenderingConfigJson {
* They will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.
*
* Note: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle
* Alternatively, this can reuse a _tagRendering_ from another layer, e.g. one of the 'icons'-tagrenderings.
* See ExpandIconBadges on how this is handled
* group: hidden
*/
iconBadges?: {
iconBadges?: (string | {
if: TagConfigJson
/**
* Badge to show
* Type: icon
*/
then: string | MinimalTagRenderingConfigJson
}[]
})[]
/**
* question: What size should the marker be on the map?

View file

@ -78,10 +78,11 @@ export default class LayerConfig extends WithContextLoader {
*/
private readonly _basedOn: string | undefined
constructor(json: LayerConfigJson,
context?: string,
official: boolean = true,
allLayers?: LayerConfigJson[],
constructor(
json: LayerConfigJson,
context?: string,
official: boolean = true,
allLayers?: LayerConfigJson[]
) {
context = context + "." + json?.id
const translationContext = "layers:" + json.id
@ -113,7 +114,7 @@ export default class LayerConfig extends WithContextLoader {
mercatorCrs: json.source["mercatorCrs"],
idKey: json.source["idKey"],
},
json.id,
json.id
)
}
@ -133,7 +134,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 = []
@ -203,7 +204,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,
@ -217,7 +218,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 = []
@ -225,7 +226,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 = []
@ -237,7 +238,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 (
@ -259,7 +260,7 @@ export default class LayerConfig extends WithContextLoader {
Constants.priviliged_layers.indexOf(<any>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 (
@ -278,7 +279,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)
@ -289,8 +290,8 @@ export default class LayerConfig extends WithContextLoader {
(tr, i) =>
new TagRenderingConfig(
<QuestionableTagRenderingConfigJson>tr,
this.id + ".tagRenderings[" + i + "]",
),
this.id + ".tagRenderings[" + i + "]"
)
)
if (json.units !== undefined && !Array.isArray(json.units)) {
throw (
@ -300,19 +301,15 @@ 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
) {
while (filter !== undefined && filter !== null && filter["sameAs"] !== undefined) {
const targetLayerName = filter["sameAs"]
this.filterIsSameAs = targetLayerName
const targetLayer = allLayers?.find(l => l.id === targetLayerName)
const targetLayer = allLayers?.find((l) => l.id === targetLayerName)
if (allLayers && !targetLayer) {
throw "Target layer " + targetLayerName + " not found in this theme"
}
@ -373,7 +370,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" }]
)
}
@ -393,7 +390,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")
@ -401,32 +398,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-<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) {
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`"
)
}
@ -436,12 +433,12 @@ export default class LayerConfig extends WithContextLoader {
"<img src='../warning.svg' height='1rem'/>",
"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."
)
}
@ -451,7 +448,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) {
@ -467,7 +464,7 @@ export default class LayerConfig extends WithContextLoader {
" into the layout as it depends on it: ",
dep.reason,
"(" + dep.context + ")",
].join(" "),
].join(" ")
)
}
@ -494,7 +491,7 @@ export default class LayerConfig extends WithContextLoader {
new And(preset.tags).asHumanString(true) +
snaps
)
}),
})
),
]
}
@ -502,8 +499,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(
" ",
),
" "
)
)
}
@ -514,10 +511,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 [
@ -532,7 +529,7 @@ export default class LayerConfig extends WithContextLoader {
: `[${values.type}](../SpecialInputElements.md#${values.type})`,
embedded.join(" "),
]
}),
})
)
let quickOverview: string[] = []
@ -542,7 +539,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
),
]
}
@ -576,19 +573,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, {}) +
"**"
)
}

View file

@ -5,10 +5,7 @@ import { TagUtils } from "../../Logic/Tags/TagUtils"
import { And } from "../../Logic/Tags/And"
import { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag"
import {
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "./Json/QuestionableTagRenderingConfigJson"
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import { RegexTag } from "../../Logic/Tags/RegexTag"
@ -82,6 +79,7 @@ export default class TagRenderingConfig {
public readonly classes: string[] | undefined
public readonly onSoftDelete?: ReadonlyArray<UploadableTag>
public readonly alwaysForceSaveButton: boolean
constructor(
config:
@ -144,6 +142,7 @@ export default class TagRenderingConfig {
this.question = Translations.T(json.question, translationKey + ".question")
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
this.questionHintIsMd = json["questionHintIsMd"] ?? false
this.alwaysForceSaveButton = json["#force-save-button"] === "yes"
this.description = Translations.T(json.description, translationKey + ".description")
if (json.onSoftDelete && !Array.isArray(json.onSoftDelete)) {
throw context + ".onSoftDelete Not an array: " + typeof json.onSoftDelete

View file

@ -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) {
@ -184,8 +184,8 @@ export default class ThemeConfig implements ThemeInformation {
<LayerConfigJson>lyrJson,
json.id + ".layers." + lyrJson["id"],
official,
<LayerConfigJson[]>json.layers,
),
<LayerConfigJson[]>json.layers
)
)
this.extraLink = new ExtraLinkConfig(
@ -195,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
@ -301,7 +301,7 @@ export default class ThemeConfig implements ThemeInformation {
return false
}
return o instanceof Translation
},
}
)
return { untranslated, total }
@ -309,7 +309,7 @@ export default class ThemeConfig implements ThemeInformation {
public getMatchingLayer(
tags: Record<string, string>,
blacklistLayers?: Set<string>,
blacklistLayers?: Set<string>
): LayerConfig | undefined {
if (tags === undefined) {
return undefined
@ -338,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
}