More dashboard view, add documentation in dashboard view

This commit is contained in:
Pieter Vander Vennet 2022-07-16 03:57:13 +02:00
parent 89278f6d74
commit a89f5a0e3e
10 changed files with 189 additions and 97 deletions

View file

@ -25,6 +25,11 @@ export interface TagRenderingConfigJson {
*/
labels?: string[]
/**
* A human-readable text explaining what this tagRendering does
*/
description?: string | any
/**
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
* If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.

View file

@ -28,6 +28,9 @@ import {And} from "../../Logic/Tags/And";
import {Overpass} from "../../Logic/Osm/Overpass";
import Constants from "../Constants";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import Svg from "../../Svg";
import {UIEventSource} from "../../Logic/UIEventSource";
import {OsmTags} from "../OsmFeature";
export default class LayerConfig extends WithContextLoader {
@ -191,8 +194,8 @@ export default class LayerConfig extends WithContextLoader {
this.doNotDownload = json.doNotDownload ?? false;
this.passAllFeatures = json.passAllFeatures ?? false;
this.minzoom = json.minzoom ?? 0;
if(json["minZoom"] !== undefined){
throw "At "+context+": minzoom is written all lowercase"
if (json["minZoom"] !== undefined) {
throw "At " + context + ": minzoom is written all lowercase"
}
this.minzoomVisible = json.minzoomVisible ?? this.minzoom;
this.shownByDefault = json.shownByDefault ?? true;
@ -352,7 +355,7 @@ export default class LayerConfig extends WithContextLoader {
neededLayer: string;
}[] = []
, addedByDefault = false, canBeIncluded = true): BaseUIElement {
const extraProps = []
const extraProps : (string | BaseUIElement)[] = []
extraProps.push("This layer is shown at zoomlevel **" + this.minzoom + "** and higher")
@ -377,7 +380,11 @@ export default class LayerConfig extends WithContextLoader {
}
if (this.source.geojsonSource !== undefined) {
extraProps.push("<img src='../warning.svg' height='1rem'/> This layer is loaded from an external source, namely `" + this.source.geojsonSource + "`")
extraProps.push(
new Combine([
Utils.runningFromConsole ? "<img src='../warning.svg' height='1rem'/>" : undefined,
"This layer is loaded from an external source, namely ",
new FixedUiElement( this.source.geojsonSource).SetClass("code")]));
}
} else {
extraProps.push("This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.")
@ -409,16 +416,16 @@ export default class LayerConfig extends WithContextLoader {
if (values == undefined) {
return undefined
}
const embedded: (Link | string)[] = values.values?.map(v => Link.OsmWiki(values.key, v, true)) ?? ["_no preset options defined, or no values in them_"]
const embedded: (Link | string)[] = values.values?.map(v => Link.OsmWiki(values.key, v, true).SetClass("mr-2")) ?? ["_no preset options defined, or no values in them_"]
return [
new Combine([
new Link(
"<img src='https://mapcomplete.osm.be/assets/svg/statistics.svg' height='18px'>",
"https://taginfo.openstreetmap.org/keys/" + values.key + "#values"
Utils.runningFromConsole ? "<img src='https://mapcomplete.osm.be/assets/svg/statistics.svg' height='18px'>" : Svg.statistics_svg().SetClass("w-4 h-4 mr-2"),
"https://taginfo.openstreetmap.org/keys/" + values.key + "#values", true
), Link.OsmWiki(values.key)
]),
]).SetClass("flex"),
values.type === undefined ? "Multiple choice" : new Link(values.type, "../SpecialInputElements.md#" + values.type),
new Combine(embedded)
new Combine(embedded).SetClass("flex")
];
}))
@ -427,18 +434,27 @@ export default class LayerConfig extends WithContextLoader {
quickOverview = new Combine([
new FixedUiElement("Warning: ").SetClass("bold"),
"this quick overview is incomplete",
new Table(["attribute", "type", "values which are supported by this layer"], tableRows)
new Table(["attribute", "type", "values which are supported by this layer"], tableRows).SetClass("zebra-table")
]).SetClass("flex-col flex")
}
const icon = this.mapRendering
.filter(mr => mr.location.has("point"))
.map(mr => mr.icon?.render?.txt)
.find(i => i !== undefined)
let iconImg = ""
if (icon !== undefined) {
// This is for the documentation, so we have to use raw HTML
iconImg = `<img src='https://mapcomplete.osm.be/${icon}' height="100px"> `
let iconImg: BaseUIElement = new FixedUiElement("")
if (Utils.runningFromConsole) {
const icon = this.mapRendering
.filter(mr => mr.location.has("point"))
.map(mr => mr.icon?.render?.txt)
.find(i => i !== undefined)
// This is for the documentation in a markdown-file, so we have to use raw HTML
if (icon !== undefined) {
iconImg = new FixedUiElement(`<img src='https://mapcomplete.osm.be/${icon}' height="100px"> `)
}
} else {
iconImg = this.mapRendering
.filter(mr => mr.location.has("point"))
.map(mr => mr.GenerateLeafletStyle(new UIEventSource<OsmTags>({id:"node/-1"}), false, {includeBadges: false}).html)
.find(i => i !== undefined)
}
let overpassLink: BaseUIElement = undefined;
@ -467,7 +483,7 @@ export default class LayerConfig extends WithContextLoader {
new Title("Supported attributes", 2),
quickOverview,
...this.tagRenderings.map(tr => tr.GenerateDocumentation())
]).SetClass("flex-col")
]).SetClass("flex-col").SetClass("link-underline")
}
public CustomCodeSnippets(): string[] {

View file

@ -14,6 +14,8 @@ import List from "../../UI/Base/List";
import {MappingConfigJson, QuestionableTagRenderingConfigJson} from "./Json/QuestionableTagRenderingConfigJson";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import {Paragraph} from "../../UI/Base/Paragraph";
import spec = Mocha.reporters.spec;
import SpecialVisualizations from "../../UI/SpecialVisualizations";
export interface Mapping {
readonly if: TagsFilter,
@ -37,6 +39,7 @@ export default class TagRenderingConfig {
public readonly render?: TypedTranslation<object>;
public readonly question?: TypedTranslation<object>;
public readonly condition?: TagsFilter;
public readonly description?: Translation;
public readonly configuration_warnings: string[] = []
@ -55,6 +58,7 @@ export default class TagRenderingConfig {
public readonly mappings?: Mapping[]
public readonly labels: string[]
constructor(json: string | QuestionableTagRenderingConfigJson, context?: string) {
if (json === undefined) {
throw "Initing a TagRenderingConfig with undefined in " + context;
@ -106,6 +110,7 @@ export default class TagRenderingConfig {
this.labels = json.labels ?? []
this.render = Translations.T(json.render, translationKey + ".render");
this.question = Translations.T(json.question, translationKey + ".question");
this.description = Translations.T(json.description, translationKey + ".description");
this.condition = TagUtils.Tag(json.condition ?? {"and": []}, `${context}.condition`);
if (json.freeform) {
@ -563,8 +568,8 @@ export default class TagRenderingConfig {
new Combine(
[
new FixedUiElement(m.then.txt).SetClass("bold"),
"corresponds with ",
m.if.asHumanString(true, false, {})
" corresponds with ",
new FixedUiElement( m.if.asHumanString(true, false, {})).SetClass("code")
]
)
]
@ -599,12 +604,14 @@ export default class TagRenderingConfig {
labels = new Combine([
"This tagrendering has labels ",
...this.labels.map(label => new FixedUiElement(label).SetClass("code"))
])
]).SetClass("flex")
}
return new Combine([
new Title(this.id, 3),
this.description,
this.question !== undefined ?
new Combine(["The question is ", new FixedUiElement(this.question.txt).SetClass("bold")]) :
new Combine(["The question is ", new FixedUiElement(this.question.txt).SetClass("font-bold bold")]) :
new FixedUiElement(
"This tagrendering has no question and is thus read-only"
).SetClass("italic"),
@ -613,6 +620,6 @@ export default class TagRenderingConfig {
condition,
group,
labels
]).SetClass("flex-col");
]).SetClass("flex flex-col");
}
}

View file

@ -26,9 +26,9 @@ export default class Link extends BaseUIElement {
if (!hideKey) {
k = key + "="
}
return new Link(k + value, `https://wiki.openstreetmap.org/wiki/Tag:${key}%3D${value}`)
return new Link(k + value, `https://wiki.openstreetmap.org/wiki/Tag:${key}%3D${value}`, true)
}
return new Link(key, "https://wiki.openstreetmap.org/wiki/Key:" + key)
return new Link(key, "https://wiki.openstreetmap.org/wiki/Key:" + key, true)
}
AsMarkdown(): string {

View file

@ -23,30 +23,35 @@ import TagRenderingAnswer from "./Popup/TagRenderingAnswer";
import Hash from "../Logic/Web/Hash";
import FilterView from "./BigComponents/FilterView";
import {FilterState} from "../Models/FilteredLayer";
import Translations from "./i18n/Translations";
import Constants from "../Models/Constants";
import {Layer} from "leaflet";
import doc = Mocha.reporters.doc;
export default class DashboardGui {
private readonly state: FeaturePipelineState;
private readonly currentView: UIEventSource<string | BaseUIElement> = new UIEventSource<string | BaseUIElement>("No selection")
private readonly currentView: UIEventSource<{ title: string | BaseUIElement, contents: string | BaseUIElement }> = new UIEventSource(undefined)
constructor(state: FeaturePipelineState, guiState: DefaultGuiState) {
this.state = state;
}
private viewSelector(shown: BaseUIElement, fullview: BaseUIElement, hash?: string): BaseUIElement {
private viewSelector(shown: BaseUIElement, title: string | BaseUIElement, contents: string | BaseUIElement, hash?: string): BaseUIElement {
const currentView = this.currentView
const v = {title, contents}
shown.SetClass("pl-1 pr-1 rounded-md")
shown.onClick(() => {
currentView.setData(fullview)
currentView.setData(v)
})
Hash.hash.addCallbackAndRunD(h => {
if (h === hash) {
currentView.setData(fullview)
currentView.setData(v)
}
})
currentView.addCallbackAndRunD(cv => {
if (cv == fullview) {
if (cv == v) {
shown.SetClass("bg-unsubtle")
Hash.hash.setData(hash)
} else {
@ -64,16 +69,15 @@ export default class DashboardGui {
}
const tags = this.state.allElements.getEventSourceById(element.properties.id)
const title = new Combine([new Title(new TagRenderingAnswer(tags, layer.title, this.state), 4),
distance < 900 ? Math.floor(distance)+"m away":
Utils.Round(distance / 1000) + "km away"
distance < 900 ? Math.floor(distance) + "m away" :
Utils.Round(distance / 1000) + "km away"
]).SetClass("flex justify-between");
const info = new Lazy(() => new Combine([
FeatureInfoBox.GenerateTitleBar(tags, layer, this.state),
FeatureInfoBox.GenerateContent(tags, layer, this.state)]).SetStyle("overflox-x: hidden"));
return this.viewSelector(title, info);
return this.singleElementCache[element.properties.id] = this.viewSelector(title,
new Lazy(() => FeatureInfoBox.GenerateTitleBar(tags, layer, this.state)),
new Lazy(() => FeatureInfoBox.GenerateContent(tags, layer, this.state)),
// element.properties.id
);
}
private mainElementsView(elements: { element: OsmFeature, layer: LayerConfig, distance: number }[]): BaseUIElement {
@ -87,8 +91,8 @@ export default class DashboardGui {
return new Combine(elements.map(e => self.singleElementView(e.element, e.layer, e.distance)))
}
private visibleElements(map: MinimapObj & BaseUIElement, layers: Record<string, LayerConfig>): { distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]{
const bbox= map.bounds.data
private visibleElements(map: MinimapObj & BaseUIElement, layers: Record<string, LayerConfig>): { distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[] {
const bbox = map.bounds.data
if (bbox === undefined) {
return undefined
}
@ -101,10 +105,10 @@ export default class DashboardGui {
let seenElements = new Set<string>()
for (const elementsWithMetaElement of elementsWithMeta) {
const layer = layers[elementsWithMetaElement.layer]
const filtered = this.state.filteredLayers.data.find(fl => fl.layerDef == layer);
for (const element of elementsWithMetaElement.features) {
console.log("Inspecting ", element.properties.id)
if(!filtered.isDisplayed.data){
const filtered = this.state.filteredLayers.data.find(fl => fl.layerDef == layer);
for (let i = 0; i < elementsWithMetaElement.features.length; i++) {
const element = elementsWithMetaElement.features[i];
if (!filtered.isDisplayed.data) {
continue
}
if (seenElements.has(element.properties.id)) {
@ -117,8 +121,8 @@ export default class DashboardGui {
if (layer?.isShown?.GetRenderValue(element)?.Subs(element.properties)?.txt === "no") {
continue
}
const activeFilters : FilterState[] = Array.from(filtered.appliedFilters.data.values());
if(activeFilters.some(filter => !filter?.currentFilter?.matchesProperties(element.properties))){
const activeFilters: FilterState[] = Array.from(filtered.appliedFilters.data.values());
if (activeFilters.some(filter => !filter?.currentFilter?.matchesProperties(element.properties))) {
continue
}
const center = GeoOperations.centerpointCoordinates(element);
@ -138,7 +142,24 @@ export default class DashboardGui {
return elements;
}
private documentationButtonFor(layerConfig: LayerConfig): BaseUIElement {
return this.viewSelector(Translations.W(layerConfig.name?.Clone() ?? layerConfig.id), new Combine(["Documentation about ", layerConfig.name?.Clone() ?? layerConfig.id]),
layerConfig.GenerateDocumentation([]),
"documentation-" + layerConfig.id)
}
private allDocumentationButtons(): BaseUIElement {
const layers = this.state.layoutToUse.layers.filter(l => Constants.priviliged_layers.indexOf(l.id) < 0)
.filter(l => !l.id.startsWith("note_import_"));
if(layers.length === 1){
return this.documentationButtonFor(layers[0])
}
return this.viewSelector(new FixedUiElement("Documentation"), "Documentation",
new Combine(layers.map(l => this.documentationButtonFor(l).SetClass("flex flex-col"))))
}
public setup(): void {
const state = this.state;
@ -161,10 +182,11 @@ export default class DashboardGui {
const self = this;
const elementsInview = new UIEventSource([]);
function update(){
elementsInview.setData( self.visibleElements(map, layers))
function update() {
elementsInview.setData(self.visibleElements(map, layers))
}
map.bounds.addCallbackAndRun(update)
state.featurePipeline.newDataLoadedSignal.addCallback(update);
state.filteredLayers.addCallbackAndRun(fls => {
@ -175,28 +197,36 @@ export default class DashboardGui {
})
const welcome = new Combine([state.layoutToUse.description, state.layoutToUse.descriptionTail])
self.currentView.setData(welcome)
self.currentView.setData({title: state.layoutToUse.title, contents: welcome})
new Combine([
new Combine([
this.viewSelector(new Title(state.layoutToUse.title, 2), welcome),
this.viewSelector(new Title(state.layoutToUse.title.Clone(), 2), state.layoutToUse.title.Clone(), welcome, "welcome"),
map.SetClass("w-full h-64 shrink-0 rounded-lg"),
new SearchAndGo(state),
this.viewSelector(new Title(
new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))), new FixedUiElement("Stats")),
new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))),
"Statistics",
new FixedUiElement("Stats"), "statistics"),
this.viewSelector(new FixedUiElement("Filter"),
"Filters",
new Lazy(() => {
return new FilterView(state.filteredLayers, state.overlayToggles)
})
return new FilterView(state.filteredLayers, state.overlayToggles)
}), "filters"
),
new VariableUiElement(elementsInview.map(elements => this.mainElementsView(elements).SetClass("block mx-2")))
.SetClass("block shrink-2 overflow-x-scroll h-full border-2 border-subtle rounded-lg"),
new LanguagePicker(Object.keys(state.layoutToUse.title)).SetClass("mt-2")
])
.SetClass("w-1/2 m-4 flex flex-col"),
new VariableUiElement(this.currentView).SetClass("w-1/2 overflow-y-auto m-4 ml-0 p-2 border-2 border-subtle rounded-xl m-y-8")
new VariableUiElement(elementsInview.map(elements => this.mainElementsView(elements).SetClass("block m-2")))
.SetClass("block shrink-2 overflow-x-auto h-full border-2 border-subtle rounded-lg"),
this.allDocumentationButtons(),
new LanguagePicker(Object.keys(state.layoutToUse.title.translations)).SetClass("mt-2")
]).SetClass("w-1/2 m-4 flex flex-col shrink-0 grow-0"),
new VariableUiElement(this.currentView.map(({title, contents}) => {
return new Combine([
new Title(Translations.W(title), 2).SetClass("shrink-0 border-b-4 border-subtle"),
Translations.W(contents).SetClass("shrink-2 overflow-y-auto block")
]).SetClass("flex flex-col h-full")
})).SetClass("w-1/2 m-4 p-2 border-2 border-subtle rounded-xl m-4 ml-0 mr-8 shrink-0 grow-0")
]).SetClass("flex h-full")
.AttachTo("leafletDiv")

View file

@ -57,6 +57,7 @@ import {SaveButton} from "./Popup/SaveButton";
import {MapillaryLink} from "./BigComponents/MapillaryLink";
import {CheckBox} from "./Input/Checkboxes";
import Slider from "./Input/Slider";
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig";
export interface SpecialVisualization {
funcName: string,
@ -207,7 +208,7 @@ class NearbyImageVis implements SpecialVisualization {
const nearby = new Lazy(() => {
const towardsCenter = new CheckBox(t.onlyTowards, false)
const radiusValue = state?.osmConnection?.GetPreference("nearby-images-radius","300").sync(s => Number(s), [], i => ""+i) ?? new UIEventSource(300);
const radiusValue = state?.osmConnection?.GetPreference("nearby-images-radius", "300").sync(s => Number(s), [], i => "" + i) ?? new UIEventSource(300);
const radius = new Slider(25, 500, {
value:
@ -285,7 +286,13 @@ export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.init()
public static DocumentationFor(viz: SpecialVisualization): BaseUIElement {
public static DocumentationFor(viz: string | SpecialVisualization): BaseUIElement | undefined {
if (typeof viz === "string") {
viz = SpecialVisualizations.specialVisualizations.find(sv => sv.funcName === viz)
}
if(viz === undefined){
return undefined;
}
return new Combine(
[
new Title(viz.funcName, 3),

View file

@ -1,21 +1,27 @@
{
"id": "shared_questions",
"questions": {
"description": "Show the images block at this location",
"id": "questions"
},
"images": {
"description": "This block shows the known images which are linked with the `image`-keys, but also via `mapillary` and `wikidata`",
"render": "{image_carousel()}{image_upload()}{nearby_images(expandable)}"
},
"mapillary": {
"description": "Shows a button to open Mapillary on this location",
"render": "{mapillary()}"
},
"export_as_gpx": {
"description": "Shows a button to export this feature as GPX. Especially useful for route relations",
"render": "{export_as_gpx()}"
},
"export_as_geojson": {
"description": "Shows a button to export this feature as geojson. Especially useful for debugging or using this in other programs",
"render": "{export_as_geojson()}"
},
"wikipedia": {
"description": "Shows a wikipedia box with the corresponding wikipedia article",
"render": "{wikipedia():max-height:25rem}",
"question": {
"en": "What is the corresponding Wikidata entity?",
@ -93,9 +99,12 @@
}
},
"reviews": {
"description": "Shows the reviews module (including the possibility to leave a review)",
"render": "{reviews()}"
},
"minimap": {
"description": "Shows a small map with the feature. Added by default to every popup",
"render": "{minimap(18, id): width:100%; height:8rem; border-radius:2rem; overflow: hidden; pointer-events: none;}"
},
"phone": {
@ -855,7 +864,7 @@
"render": "<div class='subtle' style='font-size: small; margin-top: 2em; margin-bottom: 0.5em;'><a href='https://www.openStreetMap.org/changeset/{_last_edit:changeset}' target='_blank'>Last edited on {_last_edit:timestamp}</a> by <a href='https://www.openStreetMap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a></div>"
},
"all_tags": {
"#": "Prints all the tags",
"description": "Shows a table with all the tags of the feature",
"render": "{all_tags()}"
},
"level": {

View file

@ -819,6 +819,10 @@ video {
margin: 1.25rem;
}
.m-2 {
margin: 0.5rem;
}
.m-0\.5 {
margin: 0.125rem;
}
@ -831,10 +835,6 @@ video {
margin: 0.75rem;
}
.m-2 {
margin: 0.5rem;
}
.m-6 {
margin: 1.5rem;
}
@ -843,11 +843,6 @@ video {
margin: 1px;
}
.mx-2 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.my-2 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@ -879,6 +874,10 @@ video {
margin-left: 0px;
}
.mr-8 {
margin-right: 2rem;
}
.mt-4 {
margin-top: 1rem;
}
@ -887,6 +886,10 @@ video {
margin-top: 1.5rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.mt-1 {
margin-top: 0.25rem;
}
@ -903,10 +906,6 @@ video {
margin-right: 1rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
@ -1071,14 +1070,14 @@ video {
height: 3rem;
}
.h-1\/2 {
height: 50%;
}
.h-4 {
height: 1rem;
}
.h-1\/2 {
height: 50%;
}
.h-screen {
height: 100vh;
}
@ -1163,14 +1162,14 @@ video {
width: 3rem;
}
.w-0 {
width: 0px;
}
.w-4 {
width: 1rem;
}
.w-0 {
width: 0px;
}
.w-screen {
width: 100vw;
}
@ -1361,12 +1360,12 @@ video {
overflow: scroll;
}
.overflow-y-auto {
overflow-y: auto;
.overflow-x-auto {
overflow-x: auto;
}
.overflow-x-scroll {
overflow-x: scroll;
.overflow-y-auto {
overflow-y: auto;
}
.truncate {
@ -1441,6 +1440,10 @@ video {
border-width: 4px;
}
.border-b-4 {
border-bottom-width: 4px;
}
.border-l-4 {
border-left-width: 4px;
}
@ -2447,6 +2450,15 @@ input {
box-sizing: border-box;
}
.code {
display: inline-block;
background-color: lightgray;
padding: 0.5em;
word-break: break-word;
color: black;
box-sizing: border-box;
}
/** Switch layout **/
.small-image img {

View file

@ -580,6 +580,15 @@ input {
}
.code {
display: inline-block;
background-color: lightgray;
padding: 0.5em;
word-break: break-word;
color: black;
box-sizing: border-box;
}
/** Switch layout **/
.small-image img {
height: 1em;

View file

@ -1,18 +1,15 @@
import Combine from "../UI/Base/Combine";
import BaseUIElement from "../UI/BaseUIElement";
import Translations from "../UI/i18n/Translations";
import {existsSync, mkdir, mkdirSync, writeFileSync} from "fs";
import {existsSync, mkdirSync, writeFileSync} from "fs";
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import TableOfContents from "../UI/Base/TableOfContents";
import SimpleMetaTaggers, {SimpleMetaTagger} from "../Logic/SimpleMetaTagger";
import SimpleMetaTaggers from "../Logic/SimpleMetaTagger";
import ValidatedTextField from "../UI/Input/ValidatedTextField";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import SpecialVisualizations from "../UI/SpecialVisualizations";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import {ExtraFunctions} from "../Logic/ExtraFunctions";
import Title from "../UI/Base/Title";
import Minimap from "../UI/Base/Minimap";
import {QueryParameters} from "../Logic/Web/QueryParameters";
import QueryParameterDocumentation from "../UI/QueryParameterDocumentation";
import ScriptUtils from "./ScriptUtils";
import List from "../UI/Base/List";