forked from MapComplete/MapComplete
		
	Add first dashboard layout
This commit is contained in:
		
							parent
							
								
									f805178b9b
								
							
						
					
					
						commit
						812563ddc5
					
				
					 5 changed files with 201 additions and 11 deletions
				
			
		|  | @ -26,7 +26,8 @@ export default class Toggleable extends Combine { | |||
|     public readonly isVisible = new UIEventSource(false) | ||||
| 
 | ||||
|     constructor(title: Title | Combine | BaseUIElement, content: BaseUIElement, options?: { | ||||
|         closeOnClick: true | boolean | ||||
|         closeOnClick?: true | boolean, | ||||
|         height?: "100vh" | string | ||||
|     }) { | ||||
|         super([title, content]) | ||||
|         content.SetClass("animate-height border-l-4 pl-2 block") | ||||
|  | @ -72,7 +73,7 @@ export default class Toggleable extends Combine { | |||
| 
 | ||||
|         this.isVisible.addCallbackAndRun(isVisible => { | ||||
|             if (isVisible) { | ||||
|                 contentElement.style.maxHeight = "100vh" | ||||
|                 contentElement.style.maxHeight = options?.height ?? "100vh" | ||||
|                 contentElement.style.overflowY = "auto" | ||||
|                 contentElement.style["-webkit-mask-image"] = "unset" | ||||
|             } else { | ||||
|  |  | |||
							
								
								
									
										186
									
								
								UI/DashboardGui.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								UI/DashboardGui.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,186 @@ | |||
| import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; | ||||
| import {DefaultGuiState} from "./DefaultGuiState"; | ||||
| import {FixedUiElement} from "./Base/FixedUiElement"; | ||||
| import {Utils} from "../Utils"; | ||||
| import Combine from "./Base/Combine"; | ||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import * as home_location_json from "../assets/layers/home_location/home_location.json"; | ||||
| import State from "../State"; | ||||
| import Title from "./Base/Title"; | ||||
| import {MinimapObj} from "./Base/Minimap"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| import {GeoOperations} from "../Logic/GeoOperations"; | ||||
| import {BBox} from "../Logic/BBox"; | ||||
| import {OsmFeature} from "../Models/OsmFeature"; | ||||
| import SearchAndGo from "./BigComponents/SearchAndGo"; | ||||
| import FeatureInfoBox from "./Popup/FeatureInfoBox"; | ||||
| import {UIEventSource} from "../Logic/UIEventSource"; | ||||
| import LanguagePicker from "./LanguagePicker"; | ||||
| import Lazy from "./Base/Lazy"; | ||||
| import TagRenderingAnswer from "./Popup/TagRenderingAnswer"; | ||||
| 
 | ||||
| export default class DashboardGui { | ||||
|     private readonly guiState: DefaultGuiState; | ||||
|     private readonly state: FeaturePipelineState; | ||||
|     private readonly currentView: UIEventSource<string | BaseUIElement> = new UIEventSource<string | BaseUIElement>("No selection") | ||||
| 
 | ||||
| 
 | ||||
|     constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { | ||||
|         this.state = state; | ||||
|         this.guiState = guiState; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private singleElementCache: Record<string, BaseUIElement> = {} | ||||
| 
 | ||||
|     private singleElementView(element: OsmFeature, layer: LayerConfig, distance: number): BaseUIElement { | ||||
|         if (this.singleElementCache[element.properties.id] !== undefined) { | ||||
|             return this.singleElementCache[element.properties.id] | ||||
|         } | ||||
|         const tags = this.state.allElements.getEventSourceById(element.properties.id) | ||||
|         const title = new Combine([new Title(new TagRenderingAnswer(tags, layer.title, this.state), 4), | ||||
|             Math.floor(distance) + "m away" | ||||
|         ]).SetClass("flex"); | ||||
|         //  FeatureInfoBox.GenerateTitleBar(tags, layer, this.state)
 | ||||
| 
 | ||||
|         const currentView = this.currentView | ||||
|         const info = new Lazy(() => new Combine([ | ||||
|             FeatureInfoBox.GenerateTitleBar(tags, layer, this.state), | ||||
|             FeatureInfoBox.GenerateContent(tags, layer, this.state)]).SetStyle("overflox-x: hidden")); | ||||
|         title.onClick(() => { | ||||
|             currentView.setData(info) | ||||
|         }) | ||||
| 
 | ||||
|         currentView.addCallbackAndRunD(cv => { | ||||
|             if (cv == info) { | ||||
|                 title.SetClass("bg-blue-300") | ||||
|             } else { | ||||
|                 title.RemoveClass("bg-blue-300") | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         return title; | ||||
|     } | ||||
| 
 | ||||
|     private mainElementsView(elements: { element: OsmFeature, layer: LayerConfig, distance: number }[]): BaseUIElement { | ||||
|         const self = this | ||||
|         if (elements === undefined) { | ||||
|             return new FixedUiElement("Initializing") | ||||
|         } | ||||
|         if (elements.length == 0) { | ||||
|             return new FixedUiElement("No elements in view") | ||||
|         } | ||||
|         return new Combine(elements.map(e => self.singleElementView(e.element, e.layer, e.distance))) | ||||
|     } | ||||
| 
 | ||||
|     public setup(): void { | ||||
| 
 | ||||
|         const state = this.state; | ||||
| 
 | ||||
|         if (this.state.layoutToUse.customCss !== undefined) { | ||||
|             if (window.location.pathname.indexOf("index") >= 0) { | ||||
|                 Utils.LoadCustomCss(this.state.layoutToUse.customCss) | ||||
|             } | ||||
|         } | ||||
|         const map = this.SetupMap(); | ||||
| 
 | ||||
|         Utils.downloadJson("./service-worker-version").then(data => console.log("Service worker", data)).catch(e => console.log("Service worker not active")) | ||||
| 
 | ||||
|         document.getElementById("centermessage").classList.add("hidden") | ||||
| 
 | ||||
|         const layers: Record<string, LayerConfig> = {} | ||||
|         for (const layer of state.layoutToUse.layers) { | ||||
|             layers[layer.id] = layer; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         const elementsInview = map.bounds.map(bbox => { | ||||
|             if (bbox === undefined) { | ||||
|                 return undefined | ||||
|             } | ||||
|             const location = map.location.data; | ||||
|             const loc: [number, number] = [location.lon, location.lat] | ||||
| 
 | ||||
|             const elementsWithMeta: { features: OsmFeature[], layer: string }[] = state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox) | ||||
| 
 | ||||
|             let elements: { distance: number, center: [number, number], element: OsmFeature, layer: LayerConfig }[] = [] | ||||
|             let seenElements = new Set<string>() | ||||
|             for (const elementsWithMetaElement of elementsWithMeta) { | ||||
|                 for (const element of elementsWithMetaElement.features) { | ||||
|                     if (!bbox.overlapsWith(BBox.get(element))) { | ||||
|                         continue | ||||
|                     } | ||||
|                     if (seenElements.has(element.properties.id)) { | ||||
|                         continue | ||||
|                     } | ||||
|                     seenElements.add(element.properties.id) | ||||
|                     const center = GeoOperations.centerpointCoordinates(element); | ||||
|                     elements.push({ | ||||
|                         element, | ||||
|                         center, | ||||
|                         layer: layers[elementsWithMetaElement.layer], | ||||
|                         distance: GeoOperations.distanceBetween(loc, center) | ||||
|                     }) | ||||
| 
 | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             elements.sort((e0, e1) => e0.distance - e1.distance) | ||||
| 
 | ||||
| 
 | ||||
|             return elements; | ||||
|         }, [this.state.featurePipeline.newDataLoadedSignal]); | ||||
| 
 | ||||
|         const welcome = new Combine([state.layoutToUse.description, state.layoutToUse.descriptionTail]) | ||||
|         const self = this; | ||||
|         self.currentView.setData(welcome) | ||||
|         new Combine([ | ||||
| 
 | ||||
|             new Combine([map.SetClass("w-full h-64"), | ||||
|                 new Title(state.layoutToUse.title, 2).onClick(() => { | ||||
|                     self.currentView.setData(welcome) | ||||
|                 }), | ||||
|                 new LanguagePicker(Object.keys(state.layoutToUse.title)), | ||||
|                 new SearchAndGo(state), | ||||
|                 new Title( | ||||
|                     new VariableUiElement(elementsInview.map(elements => "There are " + elements?.length + " elements in view"))) | ||||
|                     .onClick(() => self.currentView.setData("Statistics")), | ||||
|                 new VariableUiElement(elementsInview.map(elements => this.mainElementsView(elements)))]) | ||||
|                 .SetClass("w-1/2 m-4"), | ||||
|             new VariableUiElement(this.currentView).SetClass("w-1/2 h-full overflow-y-auto m-4") | ||||
|         ]).SetClass("flex h-full") | ||||
|             .AttachTo("leafletDiv") | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private SetupElement() { | ||||
|         const t = new Title("Elements in view", 3) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private SetupMap(): MinimapObj & BaseUIElement { | ||||
|         const state = this.state; | ||||
|         const guiState = this.guiState; | ||||
| 
 | ||||
|         new ShowDataLayer({ | ||||
|             leafletMap: state.leafletMap, | ||||
|             layerToShow: new LayerConfig(home_location_json, "home_location", true), | ||||
|             features: state.homeLocation, | ||||
|             state | ||||
|         }) | ||||
| 
 | ||||
|         state.leafletMap.addCallbackAndRunD(_ => { | ||||
|             // Lets assume that all showDataLayers are initialized at this point
 | ||||
|             state.selectedElement.ping() | ||||
|             State.state.locationControl.ping(); | ||||
|             return true; | ||||
|         }) | ||||
| 
 | ||||
|         return state.mainMapObject | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -44,14 +44,9 @@ export default class DefaultGUI { | |||
|         } | ||||
| 
 | ||||
|     public setup(){ | ||||
|         if (this.state.layoutToUse.customCss !== undefined) { | ||||
|             Utils.LoadCustomCss(this.state.layoutToUse.customCss); | ||||
|         } | ||||
| 
 | ||||
|         this.SetupUIElements(); | ||||
|         this.SetupMap() | ||||
| 
 | ||||
| 
 | ||||
|         if (this.state.layoutToUse.customCss !== undefined && window.location.pathname.indexOf("index") >= 0) { | ||||
|             Utils.LoadCustomCss(this.state.layoutToUse.customCss) | ||||
|         } | ||||
|  | @ -144,7 +139,7 @@ export default class DefaultGUI { | |||
| 
 | ||||
|         new ShowDataLayer({ | ||||
|             leafletMap: state.leafletMap, | ||||
|             layerToShow: new LayerConfig(home_location_json, "all_known_layers", true), | ||||
|             layerToShow: new LayerConfig(home_location_json, "home_location", true), | ||||
|             features: state.homeLocation, | ||||
|             state | ||||
|         }) | ||||
|  |  | |||
|  | @ -46,7 +46,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static GenerateTitleBar(tags: UIEventSource<any>, | ||||
|     public static GenerateTitleBar(tags: UIEventSource<any>, | ||||
|                                     layerConfig: LayerConfig, | ||||
|                                     state: {}): BaseUIElement { | ||||
|         const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI"), state) | ||||
|  | @ -64,7 +64,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { | |||
|         ]) | ||||
|     } | ||||
| 
 | ||||
|     private static GenerateContent(tags: UIEventSource<any>, | ||||
|     public static GenerateContent(tags: UIEventSource<any>, | ||||
|                                    layerConfig: LayerConfig, | ||||
|                                    state: FeaturePipelineState): BaseUIElement { | ||||
|         let questionBoxes: Map<string, QuestionBox> = new Map<string, QuestionBox>(); | ||||
|  |  | |||
							
								
								
									
										8
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -9,6 +9,8 @@ import DefaultGUI from "./UI/DefaultGUI"; | |||
| import State from "./State"; | ||||
| import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; | ||||
| import {DefaultGuiState} from "./UI/DefaultGuiState"; | ||||
| import {QueryParameters} from "./Logic/Web/QueryParameters"; | ||||
| import DashboardGui from "./UI/DashboardGui"; | ||||
| 
 | ||||
| // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console
 | ||||
| MinimapImplementation.initialize() | ||||
|  | @ -36,8 +38,14 @@ class Init { | |||
|         // This 'leaks' the global state via the window object, useful for debugging
 | ||||
|         // @ts-ignore
 | ||||
|         window.mapcomplete_state = State.state; | ||||
|          | ||||
|         const mode = QueryParameters.GetQueryParameter("mode", "map", "The mode the application starts in, e.g. 'map' or 'dashboard'") | ||||
|         if(mode.data === "dashboard"){ | ||||
|             new DashboardGui(State.state, guiState).setup() | ||||
|         }else{ | ||||
|             new DefaultGUI(State.state, guiState).setup() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue