diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 1ccf1c2f85..1074c81452 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -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. diff --git a/Models/ThemeConfig/LayerConfig.ts b/Models/ThemeConfig/LayerConfig.ts index 0582c57ac7..1ecf1966f9 100644 --- a/Models/ThemeConfig/LayerConfig.ts +++ b/Models/ThemeConfig/LayerConfig.ts @@ -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(" This layer is loaded from an external source, namely `" + this.source.geojsonSource + "`") + extraProps.push( + new Combine([ + Utils.runningFromConsole ? "" : 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( - "", - "https://taginfo.openstreetmap.org/keys/" + values.key + "#values" + Utils.runningFromConsole ? "" : 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 = ` ` + + 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(` `) + } + } else { + iconImg = this.mapRendering + .filter(mr => mr.location.has("point")) + .map(mr => mr.GenerateLeafletStyle(new UIEventSource({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[] { diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 656d880de1..2b09c765e3 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -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; public readonly question?: TypedTranslation; 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"); } } \ No newline at end of file diff --git a/UI/Base/Link.ts b/UI/Base/Link.ts index 9b640c1b1b..af2a79b35c 100644 --- a/UI/Base/Link.ts +++ b/UI/Base/Link.ts @@ -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 { diff --git a/UI/DashboardGui.ts b/UI/DashboardGui.ts index a8a5931e86..68e207e302 100644 --- a/UI/DashboardGui.ts +++ b/UI/DashboardGui.ts @@ -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 = new UIEventSource("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): { distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[]{ - const bbox= map.bounds.data + private visibleElements(map: MinimapObj & BaseUIElement, layers: Record): { 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() 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") diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index d036b9bec9..ea538f4abd 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -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), diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index aeafac6f44..73f0edb1e3 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -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": "" }, "all_tags": { - "#": "Prints all the tags", + "description": "Shows a table with all the tags of the feature", "render": "{all_tags()}" }, "level": { diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 13e1d46b02..4065ece841 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -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 { diff --git a/index.css b/index.css index 85da5cdc77..61a1fa1f52 100644 --- a/index.css +++ b/index.css @@ -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; diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index a4e03cc7ff..cdf296b60e 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -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";