Refactoring: port doc generation to generate markdown directly without UIElements

This commit is contained in:
Pieter Vander Vennet 2024-07-12 03:17:15 +02:00
parent 7a7439b161
commit 8e9c03e258
17 changed files with 309 additions and 320 deletions

View file

@ -31,6 +31,7 @@ import { TagUtils } from "../src/Logic/Tags/TagUtils"
import Script from "./Script" import Script from "./Script"
import { Changes } from "../src/Logic/Osm/Changes" import { Changes } from "../src/Logic/Osm/Changes"
import TableOfContents from "../src/UI/Base/TableOfContents" import TableOfContents from "../src/UI/Base/TableOfContents"
import MarkdownUtils from "../src/Utils/MarkdownUtils"
/** /**
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use * Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
@ -56,15 +57,15 @@ class ToSlideshowJson {
sections.push(currentSection) sections.push(currentSection)
currentSection = [] currentSection = []
} }
line = line.replace('src="../../public/', 'src="./') line = line.replace("src=\"../../public/", "src=\"./")
line = line.replace('src="../../', 'src="./') line = line.replace("src=\"../../", "src=\"./")
currentSection.push(line) currentSection.push(line)
} }
sections.push(currentSection) sections.push(currentSection)
writeFileSync( writeFileSync(
this._target, this._target,
JSON.stringify({ JSON.stringify({
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0), sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0)
}) })
) )
} }
@ -83,7 +84,7 @@ class WikiPageGenerator {
generate() { generate() {
let wikiPage = let wikiPage =
'{|class="wikitable sortable"\n' + "{|class=\"wikitable sortable\"\n" +
"! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" + "! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" +
"|-" "|-"
@ -140,8 +141,8 @@ export class GenerateDocs extends Script {
mkdirSync("./Docs/Themes") mkdirSync("./Docs/Themes")
} }
this.WriteFile("./Docs/Tags_format.md", TagUtils.generateDocs(), [ this.WriteMarkdownFile("./Docs/Tags_format.md", TagUtils.generateDocs(), [
"src/Logic/Tags/TagUtils.ts", "src/Logic/Tags/TagUtils.ts"
]) ])
new ToSlideshowJson( new ToSlideshowJson(
@ -166,58 +167,30 @@ export class GenerateDocs extends Script {
}) })
this.WriteMarkdownFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), [ this.WriteMarkdownFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), [
"src/UI/SpecialVisualizations.ts", "src/UI/SpecialVisualizations.ts"
]) ])
this.WriteFile( this.WriteMarkdownFile(
"./Docs/CalculatedTags.md", "./Docs/CalculatedTags.md",
new Combine([ [
new Title("Metatags", 1), "# Metatags",
SimpleMetaTaggers.HelpText(), SimpleMetaTaggers.HelpText(),
ExtraFunctions.HelpText(), ExtraFunctions.HelpText()
]).SetClass("flex-col"), ].join("\n"),
["src/Logic/SimpleMetaTagger.ts", "src/Logic/ExtraFunctions.ts"] ["src/Logic/SimpleMetaTagger.ts", "src/Logic/ExtraFunctions.ts"]
) )
this.WriteFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [ this.WriteMarkdownFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [
"src/UI/InputElement/Validators.ts", "src/UI/InputElement/Validators.ts"
]) ])
this.WriteFile("./Docs/ChangesetMeta.md", Changes.getDocs(), [ this.WriteMarkdownFile("./Docs/ChangesetMeta.md", Changes.getDocs(), [
"src/Logic/Osm/Changes.ts", "src/Logic/Osm/Changes.ts",
"src/Logic/Osm/ChangesetHandler.ts", "src/Logic/Osm/ChangesetHandler.ts"
]) ])
new WikiPageGenerator().generate() new WikiPageGenerator().generate()
console.log("Generated docs") console.log("Generated docs")
} }
/**
* @deprecated
*/
private WriteFile(
filename,
html: string | BaseUIElement,
autogenSource: string[],
options?: {
noTableOfContents: boolean
}
): void {
if (!html) {
return
}
let md = new Combine([
Translations.W(html),
"\n\nThis document is autogenerated from " +
autogenSource
.map(
(file) =>
`[${file}](https://github.com/pietervdvn/MapComplete/blob/develop/${file})`
)
.join(", "),
]).AsMarkdown()
this.WriteMarkdownFile(filename, md, autogenSource, options)
}
private WriteMarkdownFile( private WriteMarkdownFile(
filename: string, filename: string,
markdown: string, markdown: string,
@ -254,22 +227,30 @@ export class GenerateDocs extends Script {
const warnAutomated = const warnAutomated =
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n" "[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n"
writeFileSync(filename, warnAutomated + md) const generatedFrom =
[
"This document is autogenerated from",
autogenSource.map(s => `[${s}](https://github.com/pietervdvn/MapComplete/blob/develop/${s})`).join(", ")
].join(" ")
writeFileSync(filename, warnAutomated + md+"\n\n" +generatedFrom+"\n")
} }
private generateHotkeyDocs() { private generateHotkeyDocs() {
new ThemeViewState(new LayoutConfig(<any>bookcases), new Set()) new ThemeViewState(new LayoutConfig(<any>bookcases), new Set())
this.WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), []) this.WriteMarkdownFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), ["src/UI/Base/Hotkeys.ts"])
} }
private generateBuiltinUnits() { private generateBuiltinUnits() {
const layer = new LayerConfig(<LayerConfigJson>unit, "units", true) const layer = new LayerConfig(<LayerConfigJson>unit, "units", true)
const els: (BaseUIElement | string)[] = [new Title(layer.id, 2)] const els: string[] = ["## " + layer.id]
for (const unit of layer.units) { for (const unit of layer.units) {
els.push(new Title(unit.quantity)) els.push("### " + unit.quantity)
for (const denomination of unit.denominations) { for (const denomination of unit.denominations) {
els.push(new Title(denomination.canonical, 4)) els.push("#### " + denomination.canonical)
if (denomination.useIfNoUnitGiven === true) { if (denomination.useIfNoUnitGiven === true) {
els.push("*Default denomination*") els.push("*Default denomination*")
} else if ( } else if (
@ -277,7 +258,7 @@ export class GenerateDocs extends Script {
denomination.useIfNoUnitGiven.length > 0 denomination.useIfNoUnitGiven.length > 0
) { ) {
els.push("Default denomination in the following countries:") els.push("Default denomination in the following countries:")
els.push(new List(denomination.useIfNoUnitGiven)) els.push(MarkdownUtils.list(denomination.useIfNoUnitGiven))
} }
if (denomination.prefix) { if (denomination.prefix) {
els.push("Prefixed") els.push("Prefixed")
@ -285,14 +266,14 @@ export class GenerateDocs extends Script {
if (denomination.alternativeDenominations.length > 0) { if (denomination.alternativeDenominations.length > 0) {
els.push( els.push(
"Alternative denominations:", "Alternative denominations:",
new List(denomination.alternativeDenominations) MarkdownUtils.list(denomination.alternativeDenominations)
) )
} }
} }
} }
this.WriteFile("./Docs/builtin_units.md", new Combine([new Title("Units", 1), ...els]), [ this.WriteMarkdownFile("./Docs/builtin_units.md", ["# Units", ...els].join("\n\n"), [
`assets/layers/unit/unit.json`, `assets/layers/unit/unit.json`
]) ])
} }
@ -373,7 +354,7 @@ export class GenerateDocs extends Script {
if (inlineSource !== undefined) { if (inlineSource !== undefined) {
source = `assets/themes/${inlineSource}/${inlineSource}.json` source = `assets/themes/${inlineSource}/${inlineSource}.json`
} }
this.WriteFile("./Docs/Layers/" + layer.id + ".md", element, [source]) this.WriteMarkdownFile("./Docs/Layers/" + layer.id + ".md", element, [source])
}) })
} }
@ -442,7 +423,7 @@ export class GenerateDocs extends Script {
"The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'" "The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
) )
this.WriteFile( this.WriteMarkdownFile(
"./Docs/URL_Parameters.md", "./Docs/URL_Parameters.md",
QueryParameterDocumentation.GenerateQueryParameterDocs(), QueryParameterDocumentation.GenerateQueryParameterDocs(),
["src/Logic/Web/QueryParameters.ts", "src/UI/QueryParameterDocumentation.ts"] ["src/Logic/Web/QueryParameters.ts", "src/UI/QueryParameterDocumentation.ts"]
@ -451,7 +432,7 @@ export class GenerateDocs extends Script {
private generateBuiltinQuestions() { private generateBuiltinQuestions() {
const qLayer = new LayerConfig(<LayerConfigJson>questions, "questions.json", true) const qLayer = new LayerConfig(<LayerConfigJson>questions, "questions.json", true)
this.WriteFile( this.WriteMarkdownFile(
"./Docs/BuiltinQuestions.md", "./Docs/BuiltinQuestions.md",
qLayer.GenerateDocumentation([], new Map(), []), qLayer.GenerateDocumentation([], new Map(), []),
["assets/layers/questions/questions.json"] ["assets/layers/questions/questions.json"]
@ -459,27 +440,25 @@ export class GenerateDocs extends Script {
} }
private generateForTheme(theme: LayoutConfig): void { private generateForTheme(theme: LayoutConfig): void {
const el = new Combine([ const el = [
new Title( ["##",
new Combine([
theme.title, theme.title,
"(", "(",
new Link(theme.id, "https://mapcomplete.org/" + theme.id), `[${theme.id}](https://mapcomplete.org/${theme.id})`,
")", ")"
]), ].join(" "),
2
), theme.description.txt,
theme.description,
"This theme contains the following layers:", "This theme contains the following layers:",
new List( MarkdownUtils.list(
theme.layers theme.layers
.filter((l) => !l.id.startsWith("note_import_")) .filter((l) => !l.id.startsWith("note_import_"))
.map((l) => new Link(l.id, "../Layers/" + l.id + ".md")) .map((l) => (`[${l.id}](../Layers/${l.id}.md)`))
), ),
"Available languages:", "Available languages:",
new List(theme.language.filter((ln) => ln !== "_context")), MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context"))
]).SetClass("flex flex-col") ].join("\n")
this.WriteFile( this.WriteMarkdownFile(
"./Docs/Themes/" + theme.id + ".md", "./Docs/Themes/" + theme.id + ".md",
el, el,
[`assets/themes/${theme.id}/${theme.id}.json`], [`assets/themes/${theme.id}/${theme.id}.json`],
@ -533,11 +512,11 @@ export class GenerateDocs extends Script {
} }
} }
const el = new Combine([ const el = [
new Title("Special and other useful layers", 1), "# Special and other useful layers",
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.", "MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.",
new Title("Priviliged layers", 1), "# Priviliged layers",
new List(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")), MarkdownUtils.list(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")),
...Utils.NoNull( ...Utils.NoNull(
Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id)) Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id))
).map((l) => ).map((l) =>
@ -549,15 +528,15 @@ export class GenerateDocs extends Script {
Constants.no_include.indexOf(<any>l.id) < 0 Constants.no_include.indexOf(<any>l.id) < 0
) )
), ),
new Title("Normal layers", 1), "# Normal layers",
"The following layers are included in MapComplete:", "The following layers are included in MapComplete:",
new List( MarkdownUtils.list(
Array.from(AllSharedLayers.sharedLayers.keys()).map( Array.from(AllSharedLayers.sharedLayers.keys()).map(
(id) => new Link(id, "./Layers/" + id + ".md") (id) => `[${id}](./Layers/${id}.md)`
) )
), )
]) ].join("\n\n")
this.WriteFile("./Docs/BuiltinLayers.md", el, ["src/Customizations/AllKnownLayouts.ts"]) this.WriteMarkdownFile("./Docs/BuiltinLayers.md", el, ["src/Customizations/AllKnownLayouts.ts"])
} }
} }

View file

@ -5,6 +5,7 @@ import List from "../UI/Base/List"
import Title from "../UI/Base/Title" import Title from "../UI/Base/Title"
import { BBox } from "./BBox" import { BBox } from "./BBox"
import { Feature, Geometry, MultiPolygon, Polygon } from "geojson" import { Feature, Geometry, MultiPolygon, Polygon } from "geojson"
import MarkdownUtils from "../Utils/MarkdownUtils"
export interface ExtraFuncParams { export interface ExtraFuncParams {
/** /**
@ -517,16 +518,16 @@ export class ExtraFunctions {
return record return record
} }
public static HelpText(): BaseUIElement { public static HelpText(): string {
const elems = [] const elems: string[] = []
for (const func of ExtraFunctions.allFuncs) { for (const func of ExtraFunctions.allFuncs) {
elems.push(new Title(func._name, 3), func._doc, new List(func._args ?? [], true)) elems.push("### "+func._name, func._doc, MarkdownUtils.list(func._args))
} }
return new Combine([ return [
ExtraFunctions.intro, ExtraFunctions.intro,
new List(ExtraFunctions.allFuncs.map((func) => `[${func._name}](#${func._name})`)), MarkdownUtils.list(ExtraFunctions.allFuncs.map((func) => `[${func._name}](#${func._name})`)),
...elems, ...elems,
]) ].join("\n")
} }
} }

View file

@ -21,6 +21,7 @@ import ChangeLocationAction from "./Actions/ChangeLocationAction"
import ChangeTagAction from "./Actions/ChangeTagAction" import ChangeTagAction from "./Actions/ChangeTagAction"
import FeatureSwitchState from "../State/FeatureSwitchState" import FeatureSwitchState from "../State/FeatureSwitchState"
import DeleteAction from "./Actions/DeleteAction" import DeleteAction from "./Actions/DeleteAction"
import MarkdownUtils from "../../Utils/MarkdownUtils"
/** /**
* Handles all changes made to OSM. * Handles all changes made to OSM.
@ -116,7 +117,7 @@ export class Changes {
return changes return changes
} }
public static getDocs(): BaseUIElement { public static getDocs(): string {
function addSource(items: any[], src: string) { function addSource(items: any[], src: string) {
items.forEach((i) => { items.forEach((i) => {
i["source"] = src i["source"] = src
@ -188,24 +189,24 @@ export class Changes {
...ReplaceGeometryAction.metatags, ...ReplaceGeometryAction.metatags,
...SplitAction.metatags,*/ ...SplitAction.metatags,*/
] ]
return new Combine([ return [
new Title("Metatags on a changeset", 1), "# Metatags on a changeset",
"You might encounter the following metatags on a changeset:", "You might encounter the following metatags on a changeset:",
new Table( MarkdownUtils.table(
["key", "value", "explanation", "source"], ["key", "value", "explanation", "source"],
metatagsDocs.map(({ key, value, docs, source, changeType, specialMotivation }) => [ metatagsDocs.map(({ key, value, docs, source, changeType, specialMotivation }) => [
key ?? changeType?.join(", ") ?? "", key ?? changeType?.join(", ") ?? "",
value, value,
new Combine([ [
docs, docs,
specialMotivation specialMotivation
? "This might give a reason per modified node or way" ? "This might give a reason per modified node or way"
: "", : "",
]), ].join("\n"),
source, source,
]), ]),
), ),
]) ].join("\n\n")
} }
private static GetNeededIds(changes: ChangeDescription[]) { private static GetNeededIds(changes: ChangeDescription[]) {

View file

@ -766,29 +766,27 @@ export default class SimpleMetaTaggers {
return somethingChanged return somethingChanged
} }
public static HelpText(): BaseUIElement { public static HelpText(): string {
const subElements: (string | BaseUIElement)[] = [ const subElements: string[] = [
new Combine([ [
"Metatags are extra tags available, in order to display more data or to give better questions.", "Metatags are extra tags available, in order to display more data or to give better questions.",
"They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.", "They are calculated automatically on every feature when the data arrives in the webbrowser. This document gives an overview of the available metatags.",
"**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object", "**Hint:** when using metatags, add the [query parameter](URL_Parameters.md) `debug=true` to the URL. This will include a box in the popup for features which shows all the properties of the object",
]).SetClass("flex-col"), ].join("\n"),
] ]
subElements.push(new Title("Metatags calculated by MapComplete", 2)) subElements.push("## Metatags calculated by MapComplete")
subElements.push( subElements.push(
new FixedUiElement(
"The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme" "The following values are always calculated, by default, by MapComplete and are available automatically on all elements in every theme"
) )
)
for (const metatag of SimpleMetaTaggers.metatags) { for (const metatag of SimpleMetaTaggers.metatags) {
subElements.push( subElements.push(
new Title(metatag.keys.join(", "), 3), "### "+metatag.keys.join(", "),
metatag.doc, metatag.doc,
metatag.isLazy ? "This is a lazy metatag and is only calculated when needed" : "" metatag.isLazy ? "This is a lazy metatag and is only calculated when needed" : ""
) )
} }
return new Combine(subElements).SetClass("flex-col") return subElements.join("\n\n")
} }
} }

View file

@ -11,6 +11,7 @@ import { RegexTag } from "../../Logic/Tags/RegexTag"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
import Table from "../../UI/Base/Table" import Table from "../../UI/Base/Table"
import Combine from "../../UI/Base/Combine" import Combine from "../../UI/Base/Combine"
import MarkdownUtils from "../../Utils/MarkdownUtils"
export type FilterConfigOption = { export type FilterConfigOption = {
question: Translation question: Translation
osmTags: TagsFilter | undefined osmTags: TagsFilter | undefined
@ -199,20 +200,20 @@ export default class FilterConfig {
) )
} }
public GenerateDocs(): BaseUIElement { public GenerateDocs(): string {
const hasField = this.options.some((opt) => opt.fields?.length > 0) const hasField = this.options.some((opt) => opt.fields?.length > 0)
return new Table( return MarkdownUtils.table(
Utils.NoNull(["id", "question", "osmTags", hasField ? "fields" : undefined]), Utils.NoNull(["id", "question", "osmTags", hasField ? "fields" : undefined]),
this.options.map((opt, i) => { this.options.map((opt, i) => {
const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i const isDefault = this.options.length > 1 && (this.defaultSelection ?? 0) == i
return Utils.NoNull([ return <string[]> Utils.NoNull([
this.id + "." + i, this.id + "." + i,
isDefault isDefault
? new Combine([opt.question.SetClass("font-bold"), "(default)"]) ? `*${opt.question.txt}* (default)`
: opt.question, : opt.question,
opt.osmTags?.asHumanString(false, false, {}) ?? "", opt.osmTags?.asHumanString() ?? "",
opt.fields?.length > 0 opt.fields?.length > 0
? new Combine(opt.fields.map((f) => f.name + " (" + f.type + ")")) ? (opt.fields.map((f) => f.name + " (" + f.type + ")")).join(" ")
: undefined, : undefined,
]) ])
}) })

View file

@ -14,13 +14,9 @@ import WithContextLoader from "./WithContextLoader"
import LineRenderingConfig from "./LineRenderingConfig" import LineRenderingConfig from "./LineRenderingConfig"
import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson" import { TagRenderingConfigJson } from "./Json/TagRenderingConfigJson"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
import Combine from "../../UI/Base/Combine"
import Title from "../../UI/Base/Title"
import List from "../../UI/Base/List"
import Link from "../../UI/Base/Link" import Link from "../../UI/Base/Link"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { TagsFilter } from "../../Logic/Tags/TagsFilter" import { TagsFilter } from "../../Logic/Tags/TagsFilter"
import Table from "../../UI/Base/Table"
import FilterConfigJson from "./Json/FilterConfigJson" import FilterConfigJson from "./Json/FilterConfigJson"
import { Overpass } from "../../Logic/Osm/Overpass" import { Overpass } from "../../Logic/Osm/Overpass"
import { FixedUiElement } from "../../UI/Base/FixedUiElement" import { FixedUiElement } from "../../UI/Base/FixedUiElement"
@ -28,6 +24,7 @@ import { ImmutableStore } from "../../Logic/UIEventSource"
import { OsmTags } from "../OsmFeature" import { OsmTags } from "../OsmFeature"
import Constants from "../Constants" import Constants from "../Constants"
import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson" import { QuestionableTagRenderingConfigJson } from "./Json/QuestionableTagRenderingConfigJson"
import MarkdownUtils from "../../Utils/MarkdownUtils"
export default class LayerConfig extends WithContextLoader { export default class LayerConfig extends WithContextLoader {
public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const public static readonly syncSelectionAllowed = ["no", "local", "theme-only", "global"] as const
@ -90,7 +87,7 @@ export default class LayerConfig extends WithContextLoader {
overpassScript: json.source["overpassScript"], overpassScript: json.source["overpassScript"],
isOsmCache: json.source["isOsmCache"], isOsmCache: json.source["isOsmCache"],
mercatorCrs: json.source["mercatorCrs"], mercatorCrs: json.source["mercatorCrs"],
idKey: json.source["idKey"], idKey: json.source["idKey"]
}, },
json.id json.id
) )
@ -159,7 +156,7 @@ export default class LayerConfig extends WithContextLoader {
let preciseInput: PreciseInput = { let preciseInput: PreciseInput = {
preferredBackground: ["photo"], preferredBackground: ["photo"],
snapToLayers: undefined, snapToLayers: undefined,
maxSnapDistance: undefined, maxSnapDistance: undefined
} }
if (pr["preciseInput"] !== undefined) { if (pr["preciseInput"] !== undefined) {
throw ( throw (
@ -172,7 +169,7 @@ export default class LayerConfig extends WithContextLoader {
let snapToLayers = pr.snapToLayer let snapToLayers = pr.snapToLayer
preciseInput = { preciseInput = {
snapToLayers, snapToLayers,
maxSnapDistance: pr.maxSnapDistance ?? 10, maxSnapDistance: pr.maxSnapDistance ?? 10
} }
} }
@ -184,7 +181,7 @@ export default class LayerConfig extends WithContextLoader {
`${translationContext}.presets.${i}.description` `${translationContext}.presets.${i}.description`
), ),
preciseInput: preciseInput, preciseInput: preciseInput,
exampleImages: pr.exampleImages, exampleImages: pr.exampleImages
} }
return config return config
}) })
@ -306,7 +303,7 @@ export default class LayerConfig extends WithContextLoader {
} }
this.titleIcons = this.ParseTagRenderings(<TagRenderingConfigJson[]>json.titleIcons ?? [], { this.titleIcons = this.ParseTagRenderings(<TagRenderingConfigJson[]>json.titleIcons ?? [], {
readOnlyMode: true, readOnlyMode: true
}) })
this.title = this.tr("title", undefined, translationContext) this.title = this.tr("title", undefined, translationContext)
@ -366,8 +363,8 @@ export default class LayerConfig extends WithContextLoader {
}[] = [], }[] = [],
addedByDefault = false, addedByDefault = false,
canBeIncluded = true canBeIncluded = true
): BaseUIElement { ): string {
const extraProps: (string | BaseUIElement)[] = [] 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")
if (canBeIncluded) { if (canBeIncluded) {
@ -404,13 +401,11 @@ export default class LayerConfig extends WithContextLoader {
if (this.source?.geojsonSource !== undefined) { if (this.source?.geojsonSource !== undefined) {
extraProps.push( extraProps.push(
new Combine([ [
Utils.runningFromConsole "<img src='../warning.svg' height='1rem'/>",
? "<img src='../warning.svg' height='1rem'/>"
: undefined,
"This layer is loaded from an external source, namely ", "This layer is loaded from an external source, namely ",
new FixedUiElement(this.source.geojsonSource).SetClass("code"), "`" + this.source.geojsonSource + "`"
]) ].join("\n\n")
) )
} }
} else { } else {
@ -419,44 +414,44 @@ export default class LayerConfig extends WithContextLoader {
) )
} }
let usingLayer: BaseUIElement[] = [] let usingLayer: string[] = []
if (!addedByDefault) { if (!addedByDefault) {
if (usedInThemes?.length > 0) { if (usedInThemes?.length > 0) {
usingLayer = [ usingLayer = [
new Title("Themes using this layer", 2), "## Themes using this layer",
new List( MarkdownUtils.list(
(usedInThemes ?? []).map( (usedInThemes ?? []).map(
(id) => new Link(id, "https://mapcomplete.org/" + id) (id) => (`[${id}](https://mapcomplete.org/${id})`)
)
) )
),
] ]
} else if (this.source !== null) { } else if (this.source !== null) {
usingLayer = [new FixedUiElement("No themes use this layer")] usingLayer = ["No themes use this layer"]
} }
} }
for (const dep of dependencies) { for (const dep of dependencies) {
extraProps.push( extraProps.push(
new Combine([ [
"This layer will automatically load ", "This layer will automatically load ",
new Link(dep.neededLayer, "./" + dep.neededLayer + ".md"), (`[${dep.neededLayer}](./${dep.neededLayer}.md)`),
" 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(" ")
) )
} }
for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) { for (const revDep of Utils.Dedup(layerIsNeededBy?.get(this.id) ?? [])) {
extraProps.push( extraProps.push(
new Combine([ [
"This layer is needed as dependency for layer", "This layer is needed as dependency for layer",
new Link(revDep, "#" + revDep), (`[${revDep}](#${revDep})`)
]) ].join(" ")
) )
} }
const tableRows = Utils.NoNull( const tableRows: string[][] = Utils.NoNull(
this.tagRenderings this.tagRenderings
.map((tr) => tr.FreeformValues()) .map((tr) => tr.FreeformValues())
.map((values) => { .map((values) => {
@ -467,32 +462,28 @@ export default class LayerConfig extends WithContextLoader {
Link.OsmWiki(values.key, v, true).SetClass("mr-2") Link.OsmWiki(values.key, v, true).SetClass("mr-2")
) ?? ["_no preset options defined, or no values in them_"] ) ?? ["_no preset options defined, or no values in them_"]
return [ return [
new Combine([ [
new Link( `<a target="_blank" href='https://taginfo.openstreetmap.org/keys/${ values.key}#values'><img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'></a>]`,
"<img src='https://mapcomplete.org/assets/svg/statistics.svg' height='18px'>", Link.OsmWiki(values.key)
"https://taginfo.openstreetmap.org/keys/" + values.key + "#values", ].join(" "),
true
),
Link.OsmWiki(values.key),
]).SetClass("flex"),
values.type === undefined values.type === undefined
? "Multiple choice" ? "Multiple choice"
: new Link(values.type, "../SpecialInputElements.md#" + values.type), : `[${values.type}](../SpecialInputElements.md#${values.type})`,
new Combine(embedded).SetClass("flex"), embedded.join(" ")
] ]
}) })
) )
let quickOverview: BaseUIElement = undefined let quickOverview: string[] = []
if (tableRows.length > 0) { if (tableRows.length > 0) {
quickOverview = new Combine([ quickOverview = [
new FixedUiElement("Warning: ").SetClass("bold"), ("**Warning:**"),
"this quick overview is incomplete", "this quick overview is incomplete",
new Table( MarkdownUtils.table(
["attribute", "type", "values which are supported by this layer"], ["attribute", "type", "values which are supported by this layer"],
tableRows tableRows
).SetClass("zebra-table"), )
]).SetClass("flex-col flex") ]
} }
let iconImg: BaseUIElement = new FixedUiElement("") let iconImg: BaseUIElement = new FixedUiElement("")
@ -503,35 +494,36 @@ export default class LayerConfig extends WithContextLoader {
.map( .map(
(mr) => (mr) =>
mr.RenderIcon(new ImmutableStore<OsmTags>({ id: "node/-1" }), { mr.RenderIcon(new ImmutableStore<OsmTags>({ id: "node/-1" }), {
includeBadges: false, includeBadges: false
}).html }).html
) )
.find((i) => i !== undefined) .find((i) => i !== undefined)
} }
let overpassLink: BaseUIElement = undefined let overpassLink: string = undefined
if (this.source !== undefined) { if (this.source !== undefined) {
try { try {
overpassLink = new Link( overpassLink = (
"Execute on overpass", "[Execute on overpass](" +
Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize()) Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
.replaceAll("(", "%28") .replaceAll("(", "%28")
.replaceAll(")", "%29") .replaceAll(")", "%29")
+ ")"
) )
} catch (e) { } catch (e) {
console.error("Could not generate overpasslink for " + this.id) console.error("Could not generate overpasslink for " + this.id)
} }
} }
const filterDocs: (string | BaseUIElement)[] = [] const filterDocs: (string)[] = []
if (this.filters.length > 0) { if (this.filters.length > 0) {
filterDocs.push(new Title("Filters", 4)) filterDocs.push("#### Filters")
filterDocs.push(...this.filters.map((filter) => filter.GenerateDocs())) filterDocs.push(...this.filters.map((filter) => filter.GenerateDocs()))
} }
const tagsDescription = [] const tagsDescription: string[] = []
if (this.source !== null) { if (this.source !== null) {
tagsDescription.push(new Title("Basic tags for this layer", 2)) tagsDescription.push("## Basic tags for this layer")
const neededTags = <TagsFilter>this.source.osmTags.optimize() const neededTags = <TagsFilter>this.source.osmTags.optimize()
if (neededTags["and"]) { if (neededTags["and"]) {
@ -559,20 +551,19 @@ export default class LayerConfig extends WithContextLoader {
tagsDescription.push("This is a special layer - data is not sourced from OpenStreetMap") tagsDescription.push("This is a special layer - data is not sourced from OpenStreetMap")
} }
return new Combine([ return [
new Combine([new Title(this.id, 1), iconImg, this.description, "\n"]).SetClass( [
"flex flex-col" "# " + this.id+"\n",
), iconImg,
new List(extraProps), this.description, "\n"].join("\n\n"),
MarkdownUtils.list(extraProps),
...usingLayer, ...usingLayer,
...tagsDescription, ...tagsDescription,
new Title("Supported attributes", 2), "## Supported attributes",
quickOverview, quickOverview,
...this.tagRenderings.map((tr) => tr.GenerateDocumentation()), ...this.tagRenderings.map((tr) => tr.GenerateDocumentation()),
...filterDocs, ...filterDocs
]) ] .join("\n\n")
.SetClass("flex-col")
.SetClass("link-underline")
} }
public CustomCodeSnippets(): string[] { public CustomCodeSnippets(): string[] {

View file

@ -3,11 +3,13 @@ import Combine from "./Combine"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Title from "./Title" import Title from "./Title"
import Table from "./Table" import Table from "./Table"
import { UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { VariableUiElement } from "./VariableUIElement" import { VariableUiElement } from "./VariableUIElement"
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import { FixedUiElement } from "./FixedUiElement" import { FixedUiElement } from "./FixedUiElement"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import MarkdownUtils from "../../Utils/MarkdownUtils"
import Locale from "../i18n/Locale"
export default class Hotkeys { export default class Hotkeys {
public static readonly _docs: UIEventSource< public static readonly _docs: UIEventSource<
@ -61,7 +63,7 @@ export default class Hotkeys {
return return
} }
if (key["ctrl"] !== undefined) { if (key["ctrl"] !== undefined) {
document.addEventListener("keydown", function (event) { document.addEventListener("keydown", function(event) {
if (event.ctrlKey && event.key === keycode) { if (event.ctrlKey && event.key === keycode) {
if (action() !== false) { if (action() !== false) {
event.preventDefault() event.preventDefault()
@ -69,7 +71,7 @@ export default class Hotkeys {
} }
}) })
} else if (key["shift"] !== undefined) { } else if (key["shift"] !== undefined) {
document.addEventListener(type, function (event) { document.addEventListener(type, function(event) {
if (Hotkeys.textElementSelected(event)) { if (Hotkeys.textElementSelected(event)) {
// A text element is selected, we don't do anything special // A text element is selected, we don't do anything special
return return
@ -81,7 +83,7 @@ export default class Hotkeys {
} }
}) })
} else if (key["alt"] !== undefined) { } else if (key["alt"] !== undefined) {
document.addEventListener(type, function (event) { document.addEventListener(type, function(event) {
if (event.altKey && event.key === keycode) { if (event.altKey && event.key === keycode) {
if (action() !== false) { if (action() !== false) {
event.preventDefault() event.preventDefault()
@ -89,7 +91,7 @@ export default class Hotkeys {
} }
}) })
} else if (key["nomod"] !== undefined) { } else if (key["nomod"] !== undefined) {
document.addEventListener(type, function (event) { document.addEventListener(type, function(event) {
if (Hotkeys.textElementSelected(event) && keycode !== "Escape") { if (Hotkeys.textElementSelected(event) && keycode !== "Escape") {
// A text element is selected, we don't do anything special // A text element is selected, we don't do anything special
return return
@ -104,9 +106,11 @@ export default class Hotkeys {
} }
} }
static generateDocumentation(): BaseUIElement { static prepareDocumentation(docs: {
return new VariableUiElement( key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
Hotkeys._docs.mapD((docs) => { documentation: string | Translation
alsoTriggeredBy: Translation[]
}[]){
let byKey: [string, string | Translation, Translation[] | undefined][] = docs let byKey: [string, string | Translation, Translation[] | undefined][] = docs
.map(({ key, documentation, alsoTriggeredBy }) => { .map(({ key, documentation, alsoTriggeredBy }) => {
const modifiers = Object.keys(key).filter( const modifiers = Object.keys(key).filter(
@ -124,7 +128,7 @@ export default class Hotkeys {
return <[string, string | Translation, Translation[] | undefined]>[ return <[string, string | Translation, Translation[] | undefined]>[
modifiers.join("+"), modifiers.join("+"),
documentation, documentation,
alsoTriggeredBy, alsoTriggeredBy
] ]
}) })
.sort() .sort()
@ -134,31 +138,39 @@ export default class Hotkeys {
byKey.splice(i, 1) byKey.splice(i, 1)
} }
} }
const t = Translations.t.hotkeyDocumentation return byKey
return new Combine([
new Title(t.title, 1),
t.intro,
new Table(
[t.key, t.action],
byKey.map(([key, doc, alsoTriggeredBy]) => {
let keyEl: BaseUIElement = new FixedUiElement(key).SetClass(
"literal-code w-fit h-fit"
)
if (alsoTriggeredBy?.length > 0) {
keyEl = new Combine([keyEl, ...alsoTriggeredBy]).SetClass(
"flex gap-x-4 items-center"
)
}
return [keyEl, doc]
})
),
])
})
)
} }
static generateDocumentationDynamic(): BaseUIElement { static generateDocumentationFor(docs: {
return new VariableUiElement(Hotkeys._docs.map((_) => Hotkeys.generateDocumentation())) key: { ctrl?: string; shift?: string; alt?: string; nomod?: string; onUp?: boolean }
documentation: string | Translation
alsoTriggeredBy: Translation[]
}[], language: string): string {
const tr = Translations.t.hotkeyDocumentation
function t(t: Translation | string){
if(typeof t === "string"){
return t
}
return t.textFor(language)
}
const contents: string[][] = this.prepareDocumentation(docs)
.map(([key, doc, alsoTriggeredBy]) => {
let keyEl: string = [key, ...(alsoTriggeredBy??[])].map(k => "`"+t(k)+"`").join(" ")
return [keyEl, t(doc)]
})
return [
"# "+t(tr.title),
t(tr.intro),
MarkdownUtils.table(
[t(tr.key), t(tr.action)],
contents
)
].join("\n")
}
public static generateDocumentation(language?: string){
return Hotkeys.generateDocumentationFor(Hotkeys._docs.data, language?? Locale.language.data)
} }
private static textElementSelected(event: KeyboardEvent): boolean { private static textElementSelected(event: KeyboardEvent): boolean {

View file

@ -98,7 +98,7 @@ export default class TableOfContents {
const intro = md.substring(0, firstTitleIndex) const intro = md.substring(0, firstTitleIndex)
const splitPoint = intro.lastIndexOf("\n") const splitPoint = intro.lastIndexOf("\n")
return md.substring(0, splitPoint) + toc + md.substring(splitPoint) return md.substring(0, splitPoint) +"\n" toc + md.substring(splitPoint)
} }
public static generateStructure( public static generateStructure(

View file

@ -5,6 +5,8 @@
*/ */
import { Utils } from "../Utils" import { Utils } from "../Utils"
/* @deprecated
*/
export default abstract class BaseUIElement { export default abstract class BaseUIElement {
protected _constructedHtmlElement: HTMLElement protected _constructedHtmlElement: HTMLElement
protected isDestroyed = false protected isDestroyed = false

View file

@ -1,15 +1,12 @@
<script lang="ts"> <script lang="ts">
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import Hotkeys from "../Base/Hotkeys"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import Add from "../../assets/svg/Add.svelte" import Add from "../../assets/svg/Add.svelte"
import Github from "../../assets/svg/Github.svelte" import Github from "../../assets/svg/Github.svelte"
import DocumentChartBar from "@babeard/svelte-heroicons/outline/DocumentChartBar"
import Mastodon from "../../assets/svg/Mastodon.svelte" import Mastodon from "../../assets/svg/Mastodon.svelte"
import Liberapay from "../../assets/svg/Liberapay.svelte" import Liberapay from "../../assets/svg/Liberapay.svelte"
import ToSvelte from "../Base/ToSvelte.svelte"
import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid" import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
import MapillaryLink from "./MapillaryLink.svelte" import MapillaryLink from "./MapillaryLink.svelte"
import OpenJosm from "../Base/OpenJosm.svelte" import OpenJosm from "../Base/OpenJosm.svelte"
@ -18,6 +15,7 @@
import Community from "../../assets/svg/Community.svelte" import Community from "../../assets/svg/Community.svelte"
import Bug from "../../assets/svg/Bug.svelte" import Bug from "../../assets/svg/Bug.svelte"
import ThemeViewState from "../../Models/ThemeViewState" import ThemeViewState from "../../Models/ThemeViewState"
import DocumentChartBar from "@babeard/svelte-heroicons/outline/DocumentChartBar"
export let state: ThemeViewState export let state: ThemeViewState

View file

@ -0,0 +1,55 @@
<script lang="ts">
import Hotkeys from "../Base/Hotkeys"
import { Translation } from "../i18n/Translation"
import { Utils } from "../../Utils"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
let keys = Hotkeys._docs
const t = Translations.t.hotkeyDocumentation
let byKey = Hotkeys.prepareDocumentation($keys)
$: {
byKey = Hotkeys.prepareDocumentation($keys)
}
</script>
<AccordionSingle>
<div slot="header">
<Tr t={t.title} />
</div>
<Tr t={t.intro} />
<table>
<tr>
<th>
<Tr t={t.key}></Tr>
</th>
<th>
<Tr t={t.action} />
</th>
</tr>
{#each byKey as [key, doc, alsoTriggeredBy] }
<tr>
<td class="flex items-center justify-center">
{#if alsoTriggeredBy}
<div class="flex items-center justify-center gap-x-1">
<div class="literal-code w-fit h-fit">{key}</div>
<div class="literal-code w-fit h-fit">{alsoTriggeredBy}</div>
</div>
{:else}
<div class="literal-code w-fit h-fit flex items-center w-full">{key}</div>
{/if}
</td>
<td>
<Tr t={doc} />
</td>
</tr>
{/each}
</table>
</AccordionSingle>

View file

@ -22,7 +22,7 @@
selectedElement.properties.id selectedElement.properties.id
) )
let isAddNew = tags.mapD(t => t.id.startsWith(LastClickFeatureSource.newPointElementId)) let isAddNew = tags.mapD(t => t?.id?.startsWith(LastClickFeatureSource.newPointElementId) ?? false)
function getLayer(properties: Record<string, string>) { function getLayer(properties: Record<string, string>) {
if (properties.id === "settings") { if (properties.id === "settings") {

View file

@ -1,51 +0,0 @@
import Combine from "../Base/Combine"
import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import { Store } from "../../Logic/UIEventSource"
import { LicenseInfo } from "../../Logic/ImageProviders/LicenseInfo"
import { FixedUiElement } from "../Base/FixedUiElement"
import Link from "../Base/Link"
/**
* Small box in the bottom left of an image, e.g. the image in a popup
*/
export default class Attribution extends VariableUiElement {
constructor(license: Store<LicenseInfo>, icon: BaseUIElement, date?: Date) {
if (license === undefined) {
throw "No license source given in the attribution element"
}
super(
license.map((license: LicenseInfo) => {
if (license === undefined) {
return undefined
}
let title = undefined
if (license?.title) {
title = Translations.W(license?.title).SetClass("block")
if (license.informationLocation) {
title = new Link(title, license.informationLocation.href, true)
}
}
return new Combine([
icon
?.SetClass("block left")
.SetStyle("height: 2em; width: 2em; padding-right: 0.5em;"),
new Combine([
title,
Translations.W(license?.artist ?? "").SetClass("block font-bold"),
Translations.W(license?.license ?? license?.licenseShortName),
date === undefined
? undefined
: new FixedUiElement(date.toLocaleDateString()),
]).SetClass("flex flex-col"),
]).SetClass(
"flex flex-row bg-black text-white text-sm absolute bottom-0 left-0 p-0.5 pl-5 pr-3 rounded-lg no-images"
)
})
)
}
}

View file

@ -99,15 +99,15 @@ export default class Validators {
private static _byType = Validators._byTypeConstructor() private static _byType = Validators._byTypeConstructor()
public static HelpText(): BaseUIElement { public static HelpText(): string {
const explanations: BaseUIElement[] = Validators.AllValidators.map((type) => const explanations: string[] = Validators.AllValidators.flatMap((type) =>
new Combine([new Title(type.name, 3), type.explanation]).SetClass("flex flex-col") ["### "+type.name, type.explanation]
) )
return new Combine([ return [
new Title("Available types for text fields", 1), "# Available types for text fields",
"The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them", "The listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them",
...explanations, ...explanations,
]).SetClass("flex flex-col") ].join("\n")
} }
private static _byTypeConstructor(): Map<ValidatorType, Validator> { private static _byTypeConstructor(): Map<ValidatorType, Validator> {

View file

@ -1,11 +1,11 @@
import Combine from "../../Base/Combine" import Combine from "../../Base/Combine"
import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata" import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata"
import WikidataSearchBox from "../../Wikipedia/WikidataSearchBox"
import { Validator } from "../Validator" import { Validator } from "../Validator"
import { Translation } from "../../i18n/Translation" import { Translation } from "../../i18n/Translation"
import Translations from "../../i18n/Translations" import Translations from "../../i18n/Translations"
import Title from "../../Base/Title" import Title from "../../Base/Title"
import Table from "../../Base/Table" import Table from "../../Base/Table"
import MarkdownUtils from "../../../Utils/MarkdownUtils"
export default class WikidataValidator extends Validator { export default class WikidataValidator extends Validator {
public static readonly _searchCache = new Map<string, Promise<WikidataResponse[]>>() public static readonly _searchCache = new Map<string, Promise<WikidataResponse[]>>()
@ -23,7 +23,7 @@ export default class WikidataValidator extends Validator {
"options", "options",
new Combine([ new Combine([
"A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.", "A JSON-object of type `{ removePrefixes: string[], removePostfixes: string[] }`.",
new Table( MarkdownUtils.table(
["subarg", "doc"], ["subarg", "doc"],
[ [
[ [

View file

@ -7,28 +7,29 @@ import { QueryParameters } from "../Logic/Web/QueryParameters"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState" import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import MarkdownUtils from "../Utils/MarkdownUtils"
export default class QueryParameterDocumentation { export default class QueryParameterDocumentation {
private static QueryParamDocsIntro = [ private static QueryParamDocsIntro: string[] = [
new Title("URL-parameters and URL-hash", 1), "# URL-parameters and URL-hash",
"This document gives an overview of which URL-parameters can be used to influence MapComplete.", "This document gives an overview of which URL-parameters can be used to influence MapComplete.",
new Title("What is a URL parameter?", 2), "## What is a URL parameter?",
'"URL-parameters are extra parts of the URL used to set the state.', '"URL-parameters are extra parts of the URL used to set the state.',
"For example, if the url is `https://mapcomplete.org/cyclofix?lat=51.0&lon=4.3&z=5&test=true#node/1234`, " + "For example, if the url is `https://mapcomplete.org/cyclofix?lat=51.0&lon=4.3&z=5&test=true#node/1234`, " +
"the URL-parameters are stated in the part between the `?` and the `#`. There are multiple, all separated by `&`, namely: ", "the URL-parameters are stated in the part between the `?` and the `#`. There are multiple, all separated by `&`, namely: ",
new List( MarkdownUtils.list(
[ [
"The url-parameter `lat` is `51.0` in this instance", "The url-parameter `lat` is `51.0` in this instance",
"The url-parameter `lon` is `4.3` in this instance", "The url-parameter `lon` is `4.3` in this instance",
"The url-parameter `z` is `5` in this instance", "The url-parameter `z` is `5` in this instance",
"The url-parameter `test` is `true` in this instance", "The url-parameter `test` is `true` in this instance",
].map((s) => Translations.W(s)) ]
), ),
"Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case.", "Finally, the URL-hash is the part after the `#`. It is `node/1234` in this case.",
] ]
public static UrlParamDocs(): Map<string, string> { public static UrlParamDocs(): Map<string, string> {
const dummyLayout = new LayoutConfig({ const dummyLayout = new LayoutConfig(<any>{
id: "&gt;theme&lt;", id: "&gt;theme&lt;",
title: { en: "<theme>" }, title: { en: "<theme>" },
description: "A theme to generate docs with", description: "A theme to generate docs with",
@ -59,26 +60,26 @@ export default class QueryParameterDocumentation {
QueryParameters.GetQueryParameter( QueryParameters.GetQueryParameter(
"layer-&lt;layer-id&gt;", "layer-&lt;layer-id&gt;",
"true", "true",
"Wether or not the layer with id <layer-id> is shown" "Whether the layer with id <layer-id> is shown"
) )
return QueryParameters.documentation return QueryParameters.documentation
} }
public static GenerateQueryParameterDocs(): BaseUIElement { public static GenerateQueryParameterDocs(): string {
const docs: (string | BaseUIElement)[] = [ const docs: string[] = [
...QueryParameterDocumentation.QueryParamDocsIntro, ...QueryParameterDocumentation.QueryParamDocsIntro,
...ThemeViewStateHashActor.documentation, ...ThemeViewStateHashActor.documentation,
] ]
this.UrlParamDocs().forEach((value, key) => { this.UrlParamDocs().forEach((value, key) => {
const c = new Combine([ const c = [
new Title(key, 2), "## "+key,
value, value,
QueryParameters.defaults[key] === undefined QueryParameters.defaults[key] === undefined
? "No default value set" ? "No default value set"
: `The default value is _${QueryParameters.defaults[key]}_`, : `The default value is _${QueryParameters.defaults[key]}_`,
]) ].join("\n\n")
docs.push(c) docs.push(c)
}) })
return new Combine(docs).SetClass("flex flex-col") return docs.join("\n\n")
} }
} }

View file

@ -74,6 +74,7 @@
import AboutMapComplete from "./BigComponents/AboutMapComplete.svelte" import AboutMapComplete from "./BigComponents/AboutMapComplete.svelte"
import IfNot from "./Base/IfNot.svelte" import IfNot from "./Base/IfNot.svelte"
import Hotkeys from "./Base/Hotkeys" import Hotkeys from "./Base/Hotkeys"
import HotkeyTable from "./BigComponents/HotkeyTable.svelte"
export let state: ThemeViewState export let state: ThemeViewState
let layout = state.layout let layout = state.layout
@ -575,7 +576,7 @@
<div slot="content0" class="flex flex-col"> <div slot="content0" class="flex flex-col">
<AboutMapComplete {state} /> <AboutMapComplete {state} />
<div class="m-2 flex flex-col"> <div class="m-2 flex flex-col">
<ToSvelte construct={Hotkeys.generateDocumentationDynamic} /> <HotkeyTable/>
</div> </div>
</div> </div>