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 = 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 = { 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() 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 } }