forked from MapComplete/MapComplete
Scripts: rework more of the documentation files, prepere for translated docs
This commit is contained in:
parent
4d9133fed3
commit
84cc512271
7 changed files with 462 additions and 299 deletions
|
@ -1,4 +1,3 @@
|
|||
import BaseUIElement from "../src/UI/BaseUIElement"
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
||||
import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts"
|
||||
import SimpleMetaTaggers from "../src/Logic/SimpleMetaTagger"
|
||||
|
@ -34,6 +33,8 @@ import * as unitUsage from "../Docs/Schemas/UnitConfigJson.schema.json"
|
|||
import { ThemeConfigJson } from "../src/Models/ThemeConfig/Json/ThemeConfigJson"
|
||||
import { ServerSourceInfo, SourceOverview } from "../src/Models/SourceOverview"
|
||||
import { Lists } from "../src/Utils/Lists"
|
||||
import { Translation, TypedTranslation } from "../src/UI/i18n/Translation"
|
||||
import language_translations from "../src/assets/language_translations.json"
|
||||
|
||||
/**
|
||||
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
|
||||
|
@ -177,8 +178,13 @@ export class GenerateDocs extends Script {
|
|||
this.generateBuiltinUnits()
|
||||
await this.generateSourcesOverview()
|
||||
|
||||
if (!existsSync("./Docs/themes_nl")) {
|
||||
mkdirSync("./Docs/themes_nl")
|
||||
}
|
||||
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
||||
this.generateForTheme(theme)
|
||||
// this.generateForTheme(theme, { path: "./Docs/themes_nl", lang: "nl" })
|
||||
|
||||
ScriptUtils.erasableLog("Written docs for theme", theme.id)
|
||||
})
|
||||
|
||||
|
@ -247,7 +253,7 @@ export class GenerateDocs extends Script {
|
|||
|
||||
let md = markdown
|
||||
|
||||
if (options?.noTableOfContents !== false) {
|
||||
if (options?.noTableOfContents !== true) {
|
||||
md = TableOfContents.insertTocIntoMd(md, lang, options?.tocMaxDepth)
|
||||
}
|
||||
|
||||
|
@ -260,15 +266,17 @@ export class GenerateDocs extends Script {
|
|||
const warnAutomated =
|
||||
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n"
|
||||
|
||||
const generatedFrom = [
|
||||
"This document is autogenerated from",
|
||||
autogenSource
|
||||
.map(
|
||||
(s) =>
|
||||
`[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`
|
||||
)
|
||||
.join(", "),
|
||||
].join(" ")
|
||||
|
||||
const sources = autogenSource.map(
|
||||
(s) => `[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`).join(", ")
|
||||
|
||||
const generatedFrom =
|
||||
new TypedTranslation<{ sources }>({
|
||||
en: "This document is autogenerated from {sources}",
|
||||
nl: "Dit document werd gegenereerd op basis van {sources}",
|
||||
}).Subs({ sources }).textFor(lang)
|
||||
|
||||
|
||||
|
||||
writeFileSync(filename, warnAutomated + md + "\n\n" + generatedFrom + "\n")
|
||||
}
|
||||
|
@ -417,7 +425,7 @@ export class GenerateDocs extends Script {
|
|||
if (inlineSource !== undefined) {
|
||||
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
||||
}
|
||||
this.writeMarkdownFile(targetDirectory+ "/" + layer.id + ".md", element, [source])
|
||||
this.writeMarkdownFile(targetDirectory + "/" + layer.id + ".md", element, [source], { lang })
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -509,45 +517,73 @@ export class GenerateDocs extends Script {
|
|||
])
|
||||
}
|
||||
|
||||
private generateForTheme(theme: ThemeConfig): void {
|
||||
private generateForTheme(theme: ThemeConfig, options?: { path?: string, lang?: string }): void {
|
||||
const allLayers = AllSharedLayers.sharedLayers
|
||||
const layersToShow = theme.layers.filter(
|
||||
(l) => l.id !== "favourite" && Constants.added_by_default.indexOf(<any>l.id) < 0
|
||||
)
|
||||
const lang = options?.lang ?? "en"
|
||||
const layersToInline = layersToShow.filter((l) => !allLayers.has(l.id))
|
||||
const el = [
|
||||
[
|
||||
"##",
|
||||
theme.title,
|
||||
theme.title.textFor(lang),
|
||||
"(",
|
||||
`[${theme.id}](https://mapcomplete.org/${theme.id})`,
|
||||
")",
|
||||
].join(" "),
|
||||
|
||||
"_This document details some technical information about this MapComplete theme, mostly about the attributes used in the theme. Various links point toward more information about the attributes, e.g. to the OpenStreetMap-wiki, to TagInfo or tools creating statistics_",
|
||||
"The theme introduction reads:\n",
|
||||
"> " + parse_html(theme.description.textFor("en")).textContent.replace(/\n/g, " "),
|
||||
"",
|
||||
"This theme contains the following layers:",
|
||||
new Translation({
|
||||
en: "This theme contains the following layers:",
|
||||
nl: "Dit kaartthema bevat de volgende lagen",
|
||||
}).textFor(lang),
|
||||
MarkdownUtils.list(
|
||||
layersToShow.map((l) => {
|
||||
if (allLayers.has(l.id)) {
|
||||
return `[${l.id}](../Layers/${l.id}.md)`
|
||||
}
|
||||
return `[${l.id} (defined in this theme)](#${l.id.trim().replace(/ /g, "-")})`
|
||||
return `[${l.id} (${l.name?.textFor(lang)})](#${l.id.trim().replace(/ /g, "-")})`
|
||||
})
|
||||
),
|
||||
"Available languages:",
|
||||
MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context")),
|
||||
"# Layers defined in this theme configuration file",
|
||||
"These layers can not be reused in different themes.",
|
||||
...layersToInline.map((l) => l.generateDocumentation({ usedInThemes: null })),
|
||||
].join("\n")
|
||||
new Translation(
|
||||
{
|
||||
en: "This theme is available in the following languages:",
|
||||
nl: "Deze kaart is beschikbaar in de volgende talen:",
|
||||
},
|
||||
).textFor(lang),
|
||||
MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context").map(ln => {
|
||||
if (language_translations[ln]) {
|
||||
return ln + " (" + new Translation(language_translations[ln]).textFor(lang) + ")"
|
||||
} else {
|
||||
return ln
|
||||
}
|
||||
},
|
||||
)),
|
||||
]
|
||||
|
||||
if (layersToInline.length > 0) {
|
||||
el.push(MarkdownUtils.title(1, new Translation({
|
||||
en: "Layers defined in this theme configuration file",
|
||||
nl: "Lagen gedefinieerd in dit kaartthema-bestand",
|
||||
})).textFor(lang))
|
||||
el.push(MarkdownUtils.list(layersToInline.map(id => `[${id}](#${id})`)))
|
||||
|
||||
el.push(new Translation({
|
||||
en: "These layers can not be reused in different themes.",
|
||||
nl: "Deze lagen kunnen niet in andere kaartthemas hergebruikt worden",
|
||||
}).textFor(lang))
|
||||
el.push(
|
||||
...layersToInline.map((l) => l.generateDocumentation({ usedInThemes: null, lang })),
|
||||
)
|
||||
}
|
||||
|
||||
const path = options?.path ?? "./Docs/Themes"
|
||||
this.writeMarkdownFile(
|
||||
"./Docs/Themes/" + theme.id + ".md",
|
||||
el,
|
||||
path + "/" + theme.id + ".md",
|
||||
el.join("\n"),
|
||||
[`assets/themes/${theme.id}/${theme.id}.json`],
|
||||
{ noTableOfContents: true }
|
||||
{ noTableOfContents: true, lang },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -681,7 +717,7 @@ export class GenerateDocs extends Script {
|
|||
* Generates the documentation for the layers overview page
|
||||
* @constructor
|
||||
*/
|
||||
private generateLayerOverviewText(): BaseUIElement {
|
||||
private generateLayerOverviewText(): void {
|
||||
for (const id of Constants.priviliged_layers) {
|
||||
if (!AllSharedLayers.sharedLayers.has(id)) {
|
||||
console.error("Priviliged layer definition not found: " + id)
|
||||
|
|
|
@ -252,12 +252,24 @@ export class RegexTag extends TagsFilter {
|
|||
return this.invert
|
||||
}
|
||||
|
||||
asHumanString() {
|
||||
asHumanString(linkToWiki: boolean) {
|
||||
if (typeof this.key === "string") {
|
||||
const oper = typeof this.value === "string" ? "=" : "~"
|
||||
return `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
|
||||
if(linkToWiki){
|
||||
return `[${this.key}](https://wiki.osm.org/wiki/Key:${this.key})\`${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}\``
|
||||
}
|
||||
return `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
|
||||
|
||||
const v= `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
|
||||
if(linkToWiki){
|
||||
return `\`${v}\``
|
||||
}
|
||||
return v
|
||||
}
|
||||
const v = `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
|
||||
if(linkToWiki){
|
||||
return `\`${v}\``
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -171,9 +171,7 @@ export class SourceOverview {
|
|||
if (!url) {
|
||||
continue
|
||||
}
|
||||
console.log(">>> SPlitting", url)
|
||||
const split = url.split(",https://")
|
||||
console.log("Multisplit:", split)
|
||||
url = split[0]
|
||||
let urlClipped = url
|
||||
if (url.indexOf("?") > 0) {
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Utils } from "../../Utils"
|
|||
import { RegexTag } from "../../Logic/Tags/RegexTag"
|
||||
import MarkdownUtils from "../../Utils/MarkdownUtils"
|
||||
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
|
||||
import { Lists } from "../../Utils/Lists"
|
||||
|
||||
export type FilterConfigOption = {
|
||||
question: Translation
|
||||
|
@ -223,23 +222,29 @@ export default class FilterConfig {
|
|||
)
|
||||
}
|
||||
|
||||
public GenerateDocs(): string {
|
||||
const hasField = this.options.some((opt) => opt.fields?.length > 0)
|
||||
return MarkdownUtils.table(
|
||||
Lists.noNull(["id", "question", "osmTags", hasField ? "fields" : undefined]),
|
||||
this.options.map((opt, i) => {
|
||||
public generateDocs(options?: { lang?: string }): string {
|
||||
const lang = options?.lang ?? "en"
|
||||
const header = ["id", { en: "Default", nl: "Standaard" }, { en: "Question", nl: "Vraag" }, {
|
||||
en: "Attributes",
|
||||
nl: "Attributen",
|
||||
},
|
||||
{ en: "Fields", nl: "Velden" },
|
||||
].map(x => typeof x === "string" ? new Translation({ "*": x }) : new Translation(x))
|
||||
.map(tr => tr.textFor(lang))
|
||||
|
||||
const content: string[][] = []
|
||||
for (let i = 0; i < this.options.length; i++) {
|
||||
const opt = this.options[i]
|
||||
const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i
|
||||
return <string[]>(
|
||||
Lists.noNull([
|
||||
content.push([
|
||||
this.id + "." + i,
|
||||
isDefault ? `*${opt.question.txt}* (default)` : opt.question,
|
||||
opt.osmTags?.asHumanString() ?? "",
|
||||
opt.fields?.length > 0
|
||||
? opt.fields.map((f) => f.name + " (" + f.type + ")").join(" ")
|
||||
: undefined,
|
||||
isDefault ? "✔️" : "",
|
||||
opt.question.textFor(lang),
|
||||
opt.osmTags?.asHumanString(true) ?? (new Translation({en: "_None - show all_", nl: "_Geen - toon alles_"})).textFor(lang),
|
||||
opt.fields?.map((f) => f.name + " (" + f.type + ")").join(" "),
|
||||
])
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return MarkdownUtils.table(header, content, { dropEmptyColumns: true })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Translation } from "../../UI/i18n/Translation"
|
||||
import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
|
||||
import SourceConfig from "./SourceConfig"
|
||||
import TagRenderingConfig from "./TagRenderingConfig"
|
||||
import PresetConfig, { PreciseInput } from "./PresetConfig"
|
||||
|
@ -393,52 +393,6 @@ export default class LayerConfig extends WithContextLoader {
|
|||
return this.mapRendering.some((r) => r.location.has("point"))
|
||||
}
|
||||
|
||||
/**
|
||||
* A quick overview table of all the elements in the popup-box
|
||||
* @private
|
||||
*/
|
||||
private generateDocumentationQuickTable(): string {
|
||||
return MarkdownUtils.table(
|
||||
["id", "question", "labels", "freeform key"],
|
||||
this.tagRenderings
|
||||
.filter((tr) => tr.labels.indexOf("ignore_docs") < 0)
|
||||
.map((tr) => {
|
||||
let key = "_Multiple choice only_"
|
||||
if (tr.freeform) {
|
||||
const type = `[${tr.freeform.type}](../SpecialInputElements.md#${tr.freeform.type})`
|
||||
|
||||
key = `*[${tr.freeform.key}](https://wiki.osm.org/wiki/Key:${tr.freeform.key})* (${type})`
|
||||
}
|
||||
let origDef = ""
|
||||
if (tr._definedIn) {
|
||||
let [layer, id] = tr._definedIn
|
||||
if (layer == "questions") {
|
||||
layer = "./BuiltinQuestions"
|
||||
} else {
|
||||
layer = "./" + layer
|
||||
}
|
||||
origDef = `<br/> _(Original in [${tr._definedIn[0]}](${layer}.md#${id}))_`
|
||||
}
|
||||
const q = tr.question?.Subs(this.baseTags)?.txt?.trim()
|
||||
let r = tr.render?.txt
|
||||
if (r && r !== "") {
|
||||
r = `_${r}_`
|
||||
}
|
||||
let options: string = undefined
|
||||
if (tr.mappings?.length > 0) {
|
||||
options = `${tr.mappings.length} options`
|
||||
}
|
||||
|
||||
return [
|
||||
`[${tr.id}](#${tr.id}) ${origDef}`,
|
||||
Lists.noNull([q, r, options]).join("<br/>"),
|
||||
tr.labels.join(", "),
|
||||
key,
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
public generateDocumentation({
|
||||
usedInThemes = [],
|
||||
layerIsNeededBy,
|
||||
|
@ -456,234 +410,367 @@ export default class LayerConfig extends WithContextLoader {
|
|||
reusedTagRenderings?: Map<string, { layer: string }[]>
|
||||
lang?: string
|
||||
}): string {
|
||||
const extraProps: string[] = []
|
||||
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
|
||||
|
||||
const paragraphs: (string | Translation)[] = [
|
||||
"# " + this.id,
|
||||
this.description,
|
||||
]
|
||||
|
||||
function add(item: Translation | string | Record<string, string> & { en: string, nl: string }) {
|
||||
if (item instanceof Translation || typeof item === "string") {
|
||||
paragraphs.push(item)
|
||||
} else if (item["en"] !== undefined) {
|
||||
paragraphs.push(new Translation(item))
|
||||
}
|
||||
}
|
||||
|
||||
if (this._basedOn) {
|
||||
add(new TypedTranslation<{ basedOn }>(
|
||||
{
|
||||
en: `This layer is based on [{basedOn}](../Layers/{basedOn}.md)`,
|
||||
nl: `Deze laag is gebaseerd op [{basedOn}](../Layers/{basedOn}.md)`,
|
||||
},
|
||||
).Subs({ basedOn: this._basedOn }))
|
||||
}
|
||||
|
||||
// Various extra properties, added in a list below the description
|
||||
{
|
||||
const extraProps: Translation[] = []
|
||||
extraProps.push(
|
||||
new TypedTranslation<{ minzoom }>({
|
||||
en: "This layer is shown at zoomlevel **{minzoom}** and higher",
|
||||
nl: "Deze laag wordt getoond vanaf zoomlevel **{minzoom}**",
|
||||
}).Subs(this))
|
||||
|
||||
if (canBeIncluded) {
|
||||
if (addedByDefault) {
|
||||
extraProps.push(
|
||||
"**This layer is included automatically in every theme. This layer might contain no points**"
|
||||
new Translation(
|
||||
{
|
||||
en:
|
||||
"**This layer is included automatically in every theme. This layer might contain no points**",
|
||||
nl: "**Deze laag wordt automatisch toegevoegd aan ieder kaartthema. Deze laag bevat mogelijks geen punten",
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
if (this.shownByDefault === false) {
|
||||
extraProps.push(
|
||||
"This layer is not visible by default and must be enabled in the filter by the user. "
|
||||
new Translation({
|
||||
en: "This layer is not visible by default and must be enabled in the filter by the user. ",
|
||||
nl: "Deze laag is standaard niet zichtbaar en moet door de gebruiker aangezet worden in het 'filter'-menu ",
|
||||
}),
|
||||
)
|
||||
}
|
||||
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."
|
||||
new Translation({
|
||||
en:
|
||||
"This layers doesn't have a title set. As such, elements will appear on the map but cannot be clicked. If you import this layer in your theme, override `title` to make sure elements can be opened."
|
||||
,
|
||||
nl: "Deze laag heeft geen 'title' ingesteld. Elementen zullen op de kaart tonen, maar kunnen niet opengeklikt worden door de gebruiker. Hergebruik je deze laag? Voeg een `title` toe zodat element wel opengeklikt kunnen worden.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
if (this.name === undefined && this.shownByDefault === false) {
|
||||
if (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"
|
||||
new TypedTranslation<{ id }>({
|
||||
en: "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 adding the URL-parameter `layer-{id}=true`",
|
||||
nl: "Deze laag is standaard niet zichtbaar én heeft geen `name`. Dit betekent dat de zichtbaarheid niet door de gebruiker ingesteld kan worden, wat in een volledig verborgen laag resulteert. Dit kan nuttig zij, bv. om metatags uit te rekenen. Wil je deze laag toch tonen (bv om te debuggen?)? Voeg de URL-parameter `layer-{id}=true` toe",
|
||||
}).Subs(this),
|
||||
)
|
||||
} else {
|
||||
extraProps.push(
|
||||
new Translation({
|
||||
en: "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`",
|
||||
nl: "Deze laag kan niet onzichtbaar gemaakt worden in het filtermenu. Overschrijf `name` indien je dit wel wilt.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
if (this.name === undefined) {
|
||||
extraProps.push(
|
||||
"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`"
|
||||
new Translation({
|
||||
en: "Not rendered on the map by default.",
|
||||
nl: "Deze laag wordt standaard niet weergegeven op de kaart.",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if (this.source?.geojsonSource !== undefined) {
|
||||
extraProps.push(
|
||||
[
|
||||
"<img src='../warning.svg' height='1rem'/>",
|
||||
"This layer is loaded from an external source, namely ",
|
||||
"`" + this.source.geojsonSource + "`",
|
||||
].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."
|
||||
new Translation({
|
||||
en: "This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.",
|
||||
nl: "Deze laag kan **niet** aan een kaartthema toegevoegd worden. Deze laag wordt enkel en alleen gebruikt om een [speciale rendering](SpecialRenderings.md) te ondersteunen. ",
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
let usingLayer: string[] = []
|
||||
if (!addedByDefault) {
|
||||
if (usedInThemes?.length > 0) {
|
||||
usingLayer = [
|
||||
"## Themes using this layer",
|
||||
MarkdownUtils.list(
|
||||
(usedInThemes ?? []).map((id) => `[${id}](https://mapcomplete.org/${id})`)
|
||||
),
|
||||
]
|
||||
} else if (this.source !== null) {
|
||||
usingLayer = ["No themes use this layer"]
|
||||
}
|
||||
}
|
||||
|
||||
for (const dep of dependencies) {
|
||||
extraProps.push(
|
||||
[
|
||||
"This layer will automatically load ",
|
||||
`[${dep.neededLayer}](./${dep.neededLayer}.md)`,
|
||||
" into the layout as it depends on it: ",
|
||||
dep.reason,
|
||||
"(" + dep.context + ")",
|
||||
].join(" ")
|
||||
new TypedTranslation<{ neededLayer, reason, context? }>({
|
||||
en: "This layer will automatically load [`{neededLayer}`](./{neededLayer}.md) into the theme as it depends on it: {reason} ({context??no context given})",
|
||||
nl: "Deze laag laadt automatisch de laag [`{neededLayer}`](./{neededLayer}.md) in het kaartthema want deze laag steunt hierop: {reason} ({context??geen context gekend})",
|
||||
|
||||
}).Subs(dep),
|
||||
)
|
||||
}
|
||||
|
||||
let presets: string[] = []
|
||||
if (this.presets.length > 0) {
|
||||
presets = [
|
||||
"## Presets",
|
||||
"The following options to create new points are included:",
|
||||
MarkdownUtils.list(
|
||||
this.presets.map((preset) => {
|
||||
let snaps = ""
|
||||
if (preset.preciseInput?.snapToLayers) {
|
||||
snaps =
|
||||
" (snaps to layers " +
|
||||
preset.preciseInput.snapToLayers
|
||||
.map((id) => `\`${id}\``)
|
||||
.join(", ") +
|
||||
")"
|
||||
}
|
||||
return (
|
||||
"**" +
|
||||
preset.title.textFor(lang) +
|
||||
"** which has the following tags:" +
|
||||
new And(preset.tags).asHumanString(true) +
|
||||
snaps
|
||||
)
|
||||
})
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
for (const revDep of Lists.dedup(layerIsNeededBy?.get(this.id) ?? [])) {
|
||||
extraProps.push(
|
||||
["This layer is needed as dependency for layer", `[${revDep}](#${revDep})`].join(
|
||||
" "
|
||||
)
|
||||
new TypedTranslation<{ revDep }>({
|
||||
en: "This layer is needed as dependency for layer [`{revDep}`](#{revDep})",
|
||||
}).Subs({ revDep }),
|
||||
)
|
||||
}
|
||||
|
||||
const tableRows: string[][] = Lists.noNull(
|
||||
this.tagRenderings
|
||||
|
||||
add(MarkdownUtils.list(extraProps.map(tr => tr.textFor(lang))))
|
||||
|
||||
}
|
||||
|
||||
// Overview of what themes use this layer
|
||||
{
|
||||
if (!addedByDefault) {
|
||||
if (usedInThemes?.length > 0) {
|
||||
add(
|
||||
MarkdownUtils.title(3,
|
||||
new Translation({
|
||||
en: "Themes using this layer",
|
||||
nl: "Kaartthemas die deze laag gebruiken",
|
||||
})),
|
||||
)
|
||||
add(
|
||||
MarkdownUtils.list(
|
||||
(usedInThemes ?? []).map((id) => `[\`${id}\`](https://mapcomplete.org/${id})`),
|
||||
),
|
||||
)
|
||||
} else if (this.source !== null) {
|
||||
add({
|
||||
en: "No themes use this layer",
|
||||
nl: "Geen enkel kaartthema gebruikt deze laag",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data source information
|
||||
{
|
||||
paragraphs.push(MarkdownUtils.title(3, new Translation({ en: "Data source", nl: "Databron" })))
|
||||
|
||||
if (this.source?.geojsonSource) {
|
||||
paragraphs.push(
|
||||
new TypedTranslation<{ geojsonSource? }>({
|
||||
en: "⚠️ This layer is loaded from an external source, namely `{geojsonSource}`",
|
||||
nl: "⚠️ Deze laag wordt van een externe bron geladen, namelijk `{geojsonSource}`",
|
||||
},
|
||||
).Subs(this.source),
|
||||
)
|
||||
} else if (!this.source) {
|
||||
add({
|
||||
en: "This is a special layer, probably a library layer or support layer for MapComplete",
|
||||
nl: "Dit is een speciale laag, waarschijnlijk een bibliotheeklaag of een ondersteunende laag",
|
||||
})
|
||||
|
||||
} else {
|
||||
const neededTags = <TagsFilter>this.source.osmTags.optimize()
|
||||
if (neededTags["and"]) {
|
||||
add(
|
||||
{
|
||||
en: "Elements on this layer match **all** of the following expressions:",
|
||||
nl: "Elementen in deze laag hebben **alle** van de volgende kenmerken:",
|
||||
|
||||
},
|
||||
)
|
||||
const parts = neededTags["and"]
|
||||
add(
|
||||
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
|
||||
)
|
||||
} else if (neededTags["or"]) {
|
||||
const parts = neededTags["or"]
|
||||
add(
|
||||
{
|
||||
en: "Elements on this layer match **any** of the following expressions:",
|
||||
nl: "Elementen in deze laag hebben **minstens één** van de volgende kenmerken:",
|
||||
|
||||
})
|
||||
add(
|
||||
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
|
||||
)
|
||||
} else {
|
||||
add({
|
||||
en: "Elements on this layer match the following expression:",
|
||||
nl: "Elementen in deze laag hebben de volgende kenmerken:",
|
||||
|
||||
})
|
||||
add(
|
||||
neededTags.asHumanString(true, false, {}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const link = Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
|
||||
.replaceAll("(", "%28")
|
||||
.replaceAll(")", "%29")
|
||||
const txt = new TypedTranslation<{ link }>(
|
||||
{
|
||||
en: "Execute on overpass-turbo.eu",
|
||||
nl: "Uitvoeren op overpass-turbo.eu",
|
||||
},
|
||||
).textFor(lang)
|
||||
paragraphs.push(`[🗺️ ${txt}](${link})`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Presets
|
||||
{
|
||||
if (this.presets.length > 0) {
|
||||
add(new Translation({
|
||||
en: "## Presets",
|
||||
nl: "## Nieuwe punten toevoegen",
|
||||
}))
|
||||
|
||||
add(new Translation({
|
||||
en: "The following options to create new points are included:",
|
||||
nl: "De volgende opties bestaan om nieuwe punten toe te voegen:",
|
||||
}))
|
||||
|
||||
const hTags = new Translation({
|
||||
en: "Used tags",
|
||||
nl: "Gebruikte tags",
|
||||
})
|
||||
const hTitle = new Translation({
|
||||
en: "Title",
|
||||
nl: "Titel",
|
||||
})
|
||||
const hSnaps = new Translation({
|
||||
en: "Snaps to layers",
|
||||
nl: "Klikt vast aan lagen",
|
||||
})
|
||||
const hDescription = new Translation({
|
||||
en: "Description",
|
||||
nl: "Beschrijving",
|
||||
})
|
||||
|
||||
add(MarkdownUtils.table(
|
||||
[
|
||||
hTags,
|
||||
hTitle,
|
||||
hSnaps,
|
||||
hDescription,
|
||||
].map(t => t.textFor(lang)),
|
||||
this.presets.map(preset => {
|
||||
let category = preset.title.textFor(lang)
|
||||
category = "**" + category + "**"
|
||||
return [
|
||||
new And(preset.tags).asHumanString(true),
|
||||
Translations.t.general.add.addNew.Subs({ category }).textFor(lang),
|
||||
preset.preciseInput?.snapToLayers
|
||||
?.map((id) => `[\`${id}\`](./Layers/${id}.md)`)
|
||||
?.join(", "),
|
||||
preset.description?.textFor(lang),
|
||||
]
|
||||
}),
|
||||
{ dropEmptyColumns: true },
|
||||
))
|
||||
|
||||
} else {
|
||||
add({
|
||||
en: "This layer does not allowing adding new points",
|
||||
nl: "Deze laag laat niet toe om nieuwe punten toe te voegen",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Supported attributes table
|
||||
{
|
||||
const keyValues = (Lists.noNull(this.tagRenderings
|
||||
.map((tr) => tr.FreeformValues())
|
||||
.filter((values) => values !== undefined)
|
||||
.filter((values) => values.key !== "id")
|
||||
.map((values) => {
|
||||
.filter((values) => values.key !== "id")))
|
||||
keyValues.sort((a, b) => a.key < b.key ? -1 : 1)
|
||||
if (keyValues.length > 0) {
|
||||
add(MarkdownUtils.title(2, new Translation({
|
||||
en: "Attribute overview",
|
||||
nl: "Attributenoverzicht",
|
||||
})))
|
||||
add({
|
||||
en: "This table gives an overview of most OpenStreetMap [keys](https://wiki.openstreetmap.org/wiki/Tags) that this layer shows and/or edits",
|
||||
nl: "Deze tabel geeft een overzicht van de meeste OpenStreetMap [sleutels](https://wiki.openstreetmap.org/wiki/Tags) die deze laag toont en/of aanpast",
|
||||
})
|
||||
const tableRows: string[][] = keyValues.map((values) => {
|
||||
const embedded: string[] = values.values?.map((v) =>
|
||||
OsmWiki.constructLinkMd(values.key, v)
|
||||
OsmWiki.constructLinkMd(values.key, v),
|
||||
) ?? ["_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 [
|
||||
[
|
||||
`<a target="_blank" href='${tagInfo}'><img src='https://mapcomplete.org/assets/svg/search.svg' height='18px'></a>`,
|
||||
`<a target="_blank" href='${statistics}'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a>`,
|
||||
`[🔎](${tagInfo})`,
|
||||
`[📈](${statistics})`,
|
||||
OsmWiki.constructLinkMd(values.key),
|
||||
].join(" "),
|
||||
values.type === undefined
|
||||
? "Multiple choice"
|
||||
? ""
|
||||
: `[${values.type}](../SpecialInputElements.md#${values.type})`,
|
||||
embedded.join(" "),
|
||||
]
|
||||
})
|
||||
)
|
||||
|
||||
let quickOverview: string[] = []
|
||||
if (tableRows.length > 0) {
|
||||
quickOverview = [
|
||||
"**Warning:**: this quick overview is incomplete",
|
||||
MarkdownUtils.table(
|
||||
["attribute", "type", "values which are supported by this layer"],
|
||||
tableRows
|
||||
),
|
||||
const header: { en: string, nl: string }[] = [
|
||||
{ en: "Key", nl: "OSM-sleutel" }, {
|
||||
en: "Type (for freeform input)",
|
||||
nl: "Inputtype",
|
||||
}, { en: "Predefined, supported options", nl: "Voorgedefinieerde, ondersteunde opties" },
|
||||
]
|
||||
}
|
||||
|
||||
let overpassLink: string = undefined
|
||||
if (this.source !== undefined) {
|
||||
try {
|
||||
overpassLink =
|
||||
"[Execute on overpass](" +
|
||||
Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
|
||||
.replaceAll("(", "%28")
|
||||
.replaceAll(")", "%29") +
|
||||
")"
|
||||
} catch (e) {
|
||||
console.error("Could not generate overpasslink for " + this.id)
|
||||
}
|
||||
}
|
||||
|
||||
const filterDocs: string[] = []
|
||||
if (this.filters.length > 0) {
|
||||
filterDocs.push("## Filters")
|
||||
filterDocs.push(...this.filters.map((filter) => filter.GenerateDocs()))
|
||||
}
|
||||
|
||||
const tagsDescription: string[] = []
|
||||
if (this.source !== null) {
|
||||
tagsDescription.push("## Basic tags for this layer")
|
||||
|
||||
const neededTags = <TagsFilter>this.source.osmTags.optimize()
|
||||
if (neededTags["and"]) {
|
||||
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")
|
||||
add(
|
||||
MarkdownUtils.table(
|
||||
header.map(tr => new Translation(tr).textFor(lang)),
|
||||
tableRows
|
||||
)
|
||||
} 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")
|
||||
)
|
||||
} else {
|
||||
tagsDescription.push(
|
||||
"Elements must match the expression **" +
|
||||
neededTags.asHumanString(true, false, {}) +
|
||||
"**"
|
||||
)
|
||||
}
|
||||
|
||||
tagsDescription.push(overpassLink)
|
||||
} else {
|
||||
tagsDescription.push("This is a special layer - data is not sourced from OpenStreetMap")
|
||||
}
|
||||
|
||||
return [
|
||||
[
|
||||
"# " + this.id + "\n",
|
||||
this._basedOn
|
||||
? `This layer is based on [${this._basedOn}](../Layers/${this._basedOn}.md)`
|
||||
: "",
|
||||
this.description.textFor(lang),
|
||||
"\n",
|
||||
].join("\n\n"),
|
||||
MarkdownUtils.list(extraProps),
|
||||
...usingLayer,
|
||||
...presets,
|
||||
...tagsDescription,
|
||||
"## Supported attributes",
|
||||
...quickOverview,
|
||||
"## Featureview elements and TagRenderings",
|
||||
this.generateDocumentationQuickTable(),
|
||||
...this.tagRenderings
|
||||
.filter((tr) => tr.labels.indexOf("ignore_docs") < 0)
|
||||
.map((tr) =>
|
||||
tr.generateDocumentation(
|
||||
|
||||
// Elements in the popup
|
||||
{
|
||||
add(MarkdownUtils.title(2, new Translation({
|
||||
en: "Overview of questions (and other elements) in the popup",
|
||||
nl: "Overzicht van vragen (en andere elementen) in de popup",
|
||||
})))
|
||||
for (const tagRendering of this.tagRenderings) {
|
||||
if (tagRendering.labels.indexOf("ignore_docs") >= 0) {
|
||||
continue
|
||||
}
|
||||
add(tagRendering.generateDocumentation(
|
||||
this.id,
|
||||
lang,
|
||||
reusedTagRenderings?.get(tr.id)?.map((l) => l.layer)
|
||||
)
|
||||
),
|
||||
...filterDocs,
|
||||
].join("\n\n")
|
||||
reusedTagRenderings?.get(tagRendering.id)?.map((l) => l.layer),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Filters
|
||||
if (this.filters.length > 0) {
|
||||
add(MarkdownUtils.title(2, new Translation({
|
||||
en: "Filters",
|
||||
nl: "Filters"
|
||||
})))
|
||||
for (const filter of this.filters) {
|
||||
add(filter.generateDocs({lang}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (Lists.noEmpty(Lists.noNull(paragraphs).map(p => typeof p === "string" ? p : p.textFor(lang)))).join("\n\n")
|
||||
}
|
||||
|
||||
public CustomCodeSnippets(): string[] {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { Unit } from "../Unit"
|
|||
import { Lists } from "../../Utils/Lists"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
import SubstitutingTag from "../../Logic/Tags/SubstitutingTag"
|
||||
import { Strings } from "../../Utils/Strings"
|
||||
|
||||
export interface Mapping {
|
||||
readonly if: UploadableTag
|
||||
|
@ -753,6 +754,8 @@ export default class TagRenderingConfig {
|
|||
* Given a value for the freeform key and an overview of the selected mappings, construct the correct tagsFilter to apply.
|
||||
* Result should be interpreted as "and"
|
||||
*
|
||||
* If this matches 'invalidValues', will return undefined
|
||||
*
|
||||
* const config = new TagRenderingConfig({"id":"bookcase-booktypes","render":{"en":"This place mostly serves {books}" },
|
||||
* "question":{"en":"What kind of books can be found in this public bookcase?"},
|
||||
* "freeform":{"key":"books","addExtraTags":["fixme=Freeform tag `books` used, to be doublechecked"],
|
||||
|
@ -959,17 +962,12 @@ export default class TagRenderingConfig {
|
|||
this.description]
|
||||
|
||||
if (this.question === undefined) {
|
||||
paragraphs.push(new Translation({
|
||||
paragraphs.push(MarkdownUtils.quote(new Translation({
|
||||
en: "_This tagrendering has no question and is thus read-only_",
|
||||
nl: "_Deze tagRendering heeft geen vraag en wordt dus enkel weergegeven_",
|
||||
}))
|
||||
})))
|
||||
} else {
|
||||
paragraphs.push(new TypedTranslation<{ question }>(
|
||||
{
|
||||
en: "If no attribute matches, the question *{question}* will be asked",
|
||||
nl: "Indien geen attribuut overeenkomt, wordt *{question}* gevraagd",
|
||||
},
|
||||
).Subs({ question: this.question.textFor(lang) }))
|
||||
paragraphs.push(MarkdownUtils.quote(this.question))
|
||||
}
|
||||
|
||||
if (this.render) {
|
||||
|
@ -1017,11 +1015,14 @@ export default class TagRenderingConfig {
|
|||
this.mappings.map((m) => {
|
||||
let icon = ""
|
||||
if (m.icon?.indexOf(";") < 0) {
|
||||
icon =
|
||||
"<img width='38px' height='38px' src='https://dev.mapcomplete.org/" +
|
||||
if (Strings.isEmoji(m.icon)) {
|
||||
icon = m.icon
|
||||
} else {
|
||||
icon = "<img width='38px' height='38px' src='https://dev.mapcomplete.org/" +
|
||||
m.icon +
|
||||
"'>"
|
||||
}
|
||||
}
|
||||
const msgs: Translation[] = [
|
||||
new TypedTranslation<{ icon, then, cond }>({
|
||||
en: "{icon} *{then}* is shown if {cond}",
|
||||
|
@ -1064,6 +1065,18 @@ export default class TagRenderingConfig {
|
|||
).Subs({ conditionAsLink }))
|
||||
}
|
||||
|
||||
if (this.invalidValues) {
|
||||
paragraphs.push(
|
||||
new Translation({
|
||||
en: "This tagRendering has some values that are not valid and cannot be entered. The following values are _not_ accepted:",
|
||||
nl: "Deze tagRendering heeft waardes die als ongeldig beschouwd worden en _niet_ ingevoerd kunnen worden:",
|
||||
}),
|
||||
)
|
||||
paragraphs.push(
|
||||
"❌ " + this.invalidValues.asHumanString(true),
|
||||
)
|
||||
}
|
||||
|
||||
if (this.labels?.length > 0) {
|
||||
paragraphs.push(new Translation({
|
||||
en: "This tagRendering has the following labels:",
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { Translation } from "../UI/i18n/Translation"
|
||||
import { Utils } from "../Utils"
|
||||
|
||||
export default class MarkdownUtils {
|
||||
public static table(
|
||||
header: string[],
|
||||
|
@ -41,4 +44,13 @@ export default class MarkdownUtils {
|
|||
}
|
||||
return "\n\n" + strings.map((item) => " - " + item).join("\n") + "\n\n"
|
||||
}
|
||||
|
||||
static quote(translation: Translation) {
|
||||
return translation.OnEveryLanguage(s => "> "+s)
|
||||
}
|
||||
|
||||
static title(number: number, translation: Translation) {
|
||||
const t = Utils.times(() => "#", number)+" "
|
||||
return translation.OnEveryLanguage(l => t+ l)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue