forked from MapComplete/MapComplete
		
	More dashboard view, add documentation in dashboard view
This commit is contained in:
		
							parent
							
								
									89278f6d74
								
							
						
					
					
						commit
						a89f5a0e3e
					
				
					 10 changed files with 189 additions and 97 deletions
				
			
		|  | @ -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. | ||||
|  |  | |||
|  | @ -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[] { | ||||
|  |  | |||
|  | @ -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"); | ||||
|     } | ||||
| } | ||||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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") | ||||
| 
 | ||||
|  |  | |||
|  | @ -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), | ||||
|  |  | |||
|  | @ -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": { | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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"; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue