forked from MapComplete/MapComplete
229 lines
11 KiB
TypeScript
229 lines
11 KiB
TypeScript
import { RenderingSpecification, SpecialVisualization } from "./SpecialVisualization"
|
|
import { UploadToOsmViz } from "./Popup/UploadToOsmViz"
|
|
import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
|
import AutoApplyButtonVis from "./Popup/AutoApplyButtonVis"
|
|
import SpecialVisualisationUtils from "./SpecialVisualisationUtils"
|
|
import MarkdownUtils from "../Utils/MarkdownUtils"
|
|
import { ImageVisualisations } from "./SpecialVisualisations/ImageVisualisations"
|
|
import { NoteVisualisations } from "./SpecialVisualisations/NoteVisualisations"
|
|
import { FavouriteVisualisations } from "./SpecialVisualisations/FavouriteVisualisations"
|
|
import { UISpecialVisualisations } from "./SpecialVisualisations/UISpecialVisualisations"
|
|
import { SettingsVisualisations } from "./SpecialVisualisations/SettingsVisualisations"
|
|
import { ReviewSpecialVisualisations } from "./SpecialVisualisations/ReviewSpecialVisualisations"
|
|
import { DataImportSpecialVisualisations } from "./SpecialVisualisations/DataImportSpecialVisualisations"
|
|
import TagrenderingManipulationSpecialVisualisations
|
|
from "./SpecialVisualisations/TagrenderingManipulationSpecialVisualisations"
|
|
import {
|
|
WebAndCommunicationSpecialVisualisations,
|
|
} from "./SpecialVisualisations/WebAndCommunicationSpecialVisualisations"
|
|
import { DataVisualisations } from "./Popup/DataVisualisations"
|
|
import { DataExportVisualisations } from "./Popup/DataExportVisualisations"
|
|
import { Utils } from "../Utils"
|
|
|
|
export default class SpecialVisualizations {
|
|
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
|
|
public static specialVisualisationsDict: Map<string, SpecialVisualization> = new Map<
|
|
string,
|
|
SpecialVisualization
|
|
>()
|
|
|
|
static {
|
|
for (const specialVisualization of SpecialVisualizations.specialVisualizations) {
|
|
SpecialVisualizations.specialVisualisationsDict.set(
|
|
specialVisualization.funcName,
|
|
specialVisualization,
|
|
)
|
|
}
|
|
}
|
|
|
|
public static DocumentationFor(viz: string | SpecialVisualization): string {
|
|
if (typeof viz === "string") {
|
|
viz = SpecialVisualizations.specialVisualizations.find((sv) => sv.funcName === viz)
|
|
}
|
|
if (viz === undefined) {
|
|
return ""
|
|
}
|
|
const example =
|
|
viz.example ??
|
|
"`{" + viz.funcName + "(" + viz.args.map((arg) => arg.defaultValue).join(",") + ")}`"
|
|
|
|
let definitionPlace = ""
|
|
if (viz.definedIn) {
|
|
const path = viz.definedIn
|
|
definitionPlace = `Defined in [${path.markdownLocation}](${path.markdownLocation})`
|
|
}
|
|
return [
|
|
"### " + viz.funcName,
|
|
viz.docs,
|
|
viz.args.length > 0
|
|
? MarkdownUtils.table(
|
|
["name", "default", "description"],
|
|
viz.args.map((arg) => {
|
|
let defaultArg = arg.defaultValue ?? "_undefined_"
|
|
if (defaultArg == "") {
|
|
defaultArg = "_empty string_"
|
|
}
|
|
return [arg.name, defaultArg, arg.doc]
|
|
}),
|
|
)
|
|
: undefined,
|
|
definitionPlace,
|
|
"#### Example usage of " + viz.funcName,
|
|
example,
|
|
].join("\n\n")
|
|
}
|
|
|
|
public static constructSpecification(
|
|
template: string,
|
|
extraMappings: SpecialVisualization[] = [],
|
|
): RenderingSpecification[] {
|
|
return SpecialVisualisationUtils.constructSpecification(
|
|
template,
|
|
SpecialVisualizations.specialVisualisationsDict,
|
|
extraMappings,
|
|
)
|
|
}
|
|
|
|
public static HelpMessage(): string {
|
|
const vis = [...SpecialVisualizations.specialVisualizations]
|
|
vis.sort((a, b) => {
|
|
return a.funcName < b.funcName ? -1 : 1
|
|
})
|
|
vis.sort((a, b) => {
|
|
if (a.group === b.group) {
|
|
return 0
|
|
}
|
|
return (a.group ?? "xxx") < (b.group ?? "xxx") ? -1 : 1
|
|
})
|
|
|
|
const groupExplanations: Record<string, string> = {
|
|
default:
|
|
"These special visualisations are (mostly) interactive components that most elements get by default. You'll normally won't need them in custom layers. There are also a few miscellaneous elements supporting the map UI.",
|
|
data: "Visualises data of a POI, sometimes with data updating capabilities",
|
|
favourites:
|
|
"Elements relating to marking an object as favourite (giving it a heart). Default element",
|
|
settings: "Elements part of the usersettings-ui",
|
|
images: "Elements related to adding or manipulating images. Normally also added by default, but in some cases a tweaked version is needed",
|
|
notes: "Elements relating to OpenStreetMap-notes, e.g. the component to close and/or add a comment",
|
|
reviews:
|
|
"Elements relating to seeing and adding ratings and reviews with Mangrove.reviews",
|
|
data_import:
|
|
"Elements to help with importing data to OSM. For example: buttons to import a feature, apply tags on an element, apply multiple tags on an element or to work with maproulette",
|
|
tagrendering_manipulation:
|
|
"Special visualisations which reuse other tagRenderings to show data, but with a twist.",
|
|
web_and_communication:
|
|
"Tools to show data from external websites, which link to external websites or which link to external profiles",
|
|
ui: "Elements to support the user interface, e.g. 'title', 'translated'",
|
|
}
|
|
|
|
const helpTexts: string[] = []
|
|
let lastGroup: string = null
|
|
for (const viz of vis) {
|
|
if (viz.group?.toLowerCase() !== lastGroup) {
|
|
lastGroup = viz.group?.toLowerCase()
|
|
if (viz.group === undefined) {
|
|
helpTexts.push("## Unclassified elements\n\nVarious elements")
|
|
} else {
|
|
helpTexts.push("## " + viz.group)
|
|
if (!groupExplanations[viz.group.toLowerCase()]) {
|
|
throw (
|
|
"\n\n >>>> ERROR <<<< Unknown visualisation group type: " +
|
|
viz.group +
|
|
" (used by " +
|
|
viz.funcName +
|
|
")\n\n\n"
|
|
)
|
|
}
|
|
helpTexts.push(groupExplanations[viz.group])
|
|
}
|
|
}
|
|
helpTexts.push(SpecialVisualizations.DocumentationFor(viz))
|
|
}
|
|
|
|
const example = JSON.stringify(
|
|
{
|
|
render: {
|
|
special: {
|
|
type: "some_special_visualisation",
|
|
argname: "some_arg",
|
|
message: {
|
|
en: "some other really long message",
|
|
nl: "een boodschap in een andere taal",
|
|
},
|
|
other_arg_name: "more args",
|
|
},
|
|
before: {
|
|
en: "Some text to prefix before the special element (e.g. a title)",
|
|
nl: "Een tekst om voor het element te zetten (bv. een titel)",
|
|
},
|
|
after: {
|
|
en: "Some text to put after the element, e.g. a footer",
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
" ",
|
|
)
|
|
|
|
const firstPart = [
|
|
"# Special tag renderings",
|
|
"In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
|
|
"General usage is `{func_name()}`, `{func_name(arg, someotherarg)}` or `{func_name(args):cssClasses}`. Note that you _do not_ need to use quotes around your arguments, the comma is enough to separate them. This also implies you cannot use a comma in your args",
|
|
"# Using expanded syntax",
|
|
`Instead of using \`{"render": {"en": "{some_special_visualisation(some_arg, some other really long message, more args)} , "nl": "{some_special_visualisation(some_arg, een boodschap in een andere taal, more args)}}\`, one can also write`,
|
|
"```\n" + example + "\n```\n",
|
|
"In other words: use `{ \"before\": ..., \"after\": ..., \"special\": {\"type\": ..., \"argname\": ...argvalue...}`. The args are in the `special` block; an argvalue can be a string, a translation or another value. (Refer to class `RewriteSpecial` in case of problems)",
|
|
"# Overview of all special components",
|
|
].join("\n\n")
|
|
return firstPart + "\n\n" + helpTexts.join("\n\n")
|
|
}
|
|
|
|
private static initList(): SpecialVisualization[] {
|
|
const specialVisualizations: SpecialVisualization[] = [
|
|
...ImageVisualisations.initList(),
|
|
...NoteVisualisations.initList(),
|
|
...FavouriteVisualisations.initList(),
|
|
...UISpecialVisualisations.initList(),
|
|
...SettingsVisualisations.initList(),
|
|
...ReviewSpecialVisualisations.initList(),
|
|
...DataImportSpecialVisualisations.initList(),
|
|
...TagrenderingManipulationSpecialVisualisations.initList(),
|
|
...WebAndCommunicationSpecialVisualisations.initList(),
|
|
...DataVisualisations.initList(),
|
|
...DataExportVisualisations.initList(),
|
|
new UploadToOsmViz(),
|
|
new MultiApplyViz(),
|
|
]
|
|
|
|
specialVisualizations.push(new AutoApplyButtonVis(specialVisualizations))
|
|
|
|
if (Utils.runningFromConsole) {
|
|
// Some sanity checks
|
|
const regex = /[a-zA-Z_]+/
|
|
const invalid = specialVisualizations
|
|
.map((sp, i) => ({ sp, i }))
|
|
.filter((sp) => sp.sp.funcName === undefined || !sp.sp.funcName.match(regex))
|
|
|
|
if (invalid.length > 0) {
|
|
throw (
|
|
"Invalid special visualisation found: funcName is undefined or doesn't match " +
|
|
regex +
|
|
invalid.map((sp) => sp.i).join(", ") +
|
|
". Did you perhaps type \n funcName: \"funcname\" // type declaration uses COLON\ninstead of:\n funcName = \"funcName\" // value definition uses EQUAL"
|
|
)
|
|
}
|
|
|
|
const allNames = specialVisualizations.map((f) => f.funcName)
|
|
const seen = new Set<string>()
|
|
for (let name of allNames) {
|
|
name = name.toLowerCase()
|
|
if (seen.has(name)) {
|
|
throw "Invalid special visualisations: detected a duplicate name: " + name
|
|
}
|
|
seen.add(name)
|
|
}
|
|
}
|
|
|
|
return specialVisualizations
|
|
}
|
|
}
|