Refactoring: port doc generation to generate markdown directly without UIElements
This commit is contained in:
parent
7a7439b161
commit
8e9c03e258
17 changed files with 309 additions and 320 deletions
|
@ -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"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[]) {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
55
src/UI/BigComponents/HotkeyTable.svelte
Normal file
55
src/UI/BigComponents/HotkeyTable.svelte
Normal 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>
|
|
@ -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") {
|
||||||
|
|
|
@ -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"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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"],
|
||||||
[
|
[
|
||||||
[
|
[
|
||||||
|
|
|
@ -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: ">theme<",
|
id: ">theme<",
|
||||||
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-<layer-id>",
|
"layer-<layer-id>",
|
||||||
"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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue