2022-07-13 17:58:01 +02:00
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 { 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" ;
2022-07-15 12:56:16 +02:00
import Hash from "../Logic/Web/Hash" ;
import FilterView from "./BigComponents/FilterView" ;
2022-07-16 03:57:13 +02:00
import Translations from "./i18n/Translations" ;
import Constants from "../Models/Constants" ;
2022-07-18 00:28:26 +02:00
import SimpleAddUI from "./BigComponents/SimpleAddUI" ;
2022-07-20 14:06:39 +02:00
import BackToIndex from "./BigComponents/BackToIndex" ;
2022-07-25 18:55:15 +02:00
import StatisticsPanel from "./BigComponents/StatisticsPanel" ;
2022-07-15 12:56:16 +02:00
2022-07-13 17:58:01 +02:00
export default class DashboardGui {
private readonly state : FeaturePipelineState ;
2022-07-16 03:57:13 +02:00
private readonly currentView : UIEventSource < { title : string | BaseUIElement , contents : string | BaseUIElement } > = new UIEventSource ( undefined )
2022-07-13 17:58:01 +02:00
constructor ( state : FeaturePipelineState , guiState : DefaultGuiState ) {
this . state = state ;
2022-07-15 12:56:16 +02:00
}
2022-07-13 17:58:01 +02:00
2022-07-16 03:57:13 +02:00
private viewSelector ( shown : BaseUIElement , title : string | BaseUIElement , contents : string | BaseUIElement , hash? : string ) : BaseUIElement {
2022-07-15 12:56:16 +02:00
const currentView = this . currentView
2022-07-16 03:57:13 +02:00
const v = { title , contents }
2022-07-15 12:56:16 +02:00
shown . SetClass ( "pl-1 pr-1 rounded-md" )
shown . onClick ( ( ) = > {
2022-07-16 03:57:13 +02:00
currentView . setData ( v )
2022-07-15 12:56:16 +02:00
} )
Hash . hash . addCallbackAndRunD ( h = > {
if ( h === hash ) {
2022-07-16 03:57:13 +02:00
currentView . setData ( v )
2022-07-15 12:56:16 +02:00
}
} )
currentView . addCallbackAndRunD ( cv = > {
2022-07-16 03:57:13 +02:00
if ( cv == v ) {
2022-07-15 12:56:16 +02:00
shown . SetClass ( "bg-unsubtle" )
Hash . hash . setData ( hash )
} else {
shown . RemoveClass ( "bg-unsubtle" )
}
} )
return shown ;
2022-07-13 17:58:01 +02:00
}
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 ) ,
2022-07-16 03:57:13 +02:00
distance < 900 ? Math . floor ( distance ) + "m away" :
Utils . Round ( distance / 1000 ) + "km away"
2022-07-15 12:56:16 +02:00
] ) . SetClass ( "flex justify-between" ) ;
2022-07-13 17:58:01 +02:00
2022-07-16 03:57:13 +02:00
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
) ;
2022-07-13 17:58:01 +02:00
}
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 ) ) )
}
2022-07-25 18:55:15 +02:00
private documentationButtonFor ( layerConfig : LayerConfig ) : BaseUIElement {
2022-07-16 03:57:13 +02:00
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_" ) ) ;
2022-07-18 00:28:26 +02:00
if ( layers . length === 1 ) {
2022-07-16 03:57:13 +02:00
return this . documentationButtonFor ( layers [ 0 ] )
}
2022-07-18 00:28:26 +02:00
return this . viewSelector ( new FixedUiElement ( "Documentation" ) , "Documentation" ,
2022-07-16 03:57:13 +02:00
new Combine ( layers . map ( l = > this . documentationButtonFor ( l ) . SetClass ( "flex flex-col" ) ) ) )
}
2022-07-25 18:55:15 +02:00
2022-07-16 03:57:13 +02:00
2022-07-13 17:58:01 +02:00
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 ( ) ;
2022-07-20 12:04:14 +02:00
Utils . downloadJson ( "./service-worker-version" ) . then ( data = > console . log ( "Service worker" , data ) ) . catch ( _ = > console . log ( "Service worker not active" ) )
2022-07-13 17:58:01 +02:00
document . getElementById ( "centermessage" ) . classList . add ( "hidden" )
const layers : Record < string , LayerConfig > = { }
for ( const layer of state . layoutToUse . layers ) {
layers [ layer . id ] = layer ;
}
2022-07-15 12:56:16 +02:00
const self = this ;
2022-07-20 12:04:14 +02:00
const elementsInview = new UIEventSource < { distance : number , center : [ number , number ] , element : OsmFeature , layer : LayerConfig } [ ] > ( [ ] ) ;
2022-07-16 03:57:13 +02:00
function update() {
2022-07-25 18:55:15 +02:00
const mapCenter = < [ number , number ] > [ self . state . locationControl . data . lon , self . state . locationControl . data . lon ]
const elements = self . state . featurePipeline . getAllVisibleElementsWithmeta ( self . state . currentBounds . data ) . map ( el = > {
const distance = GeoOperations . distanceBetween ( el . center , mapCenter )
return { . . . el , distance }
} )
elements . sort ( ( e0 , e1 ) = > e0 . distance - e1 . distance )
elementsInview . setData ( elements )
2022-07-15 12:56:16 +02:00
}
2022-07-16 03:57:13 +02:00
2022-07-15 12:56:16 +02:00
map . bounds . addCallbackAndRun ( update )
state . featurePipeline . newDataLoadedSignal . addCallback ( update ) ;
state . filteredLayers . addCallbackAndRun ( fls = > {
for ( const fl of fls ) {
fl . isDisplayed . addCallback ( update )
fl . appliedFilters . addCallback ( update )
2022-07-13 17:58:01 +02:00
}
2022-07-15 12:56:16 +02:00
} )
2022-07-13 17:58:01 +02:00
2022-07-18 00:28:26 +02:00
const filterView = new Lazy ( ( ) = > {
return new FilterView ( state . filteredLayers , state . overlayToggles )
} ) ;
2022-07-13 17:58:01 +02:00
const welcome = new Combine ( [ state . layoutToUse . description , state . layoutToUse . descriptionTail ] )
2022-07-16 03:57:13 +02:00
self . currentView . setData ( { title : state.layoutToUse.title , contents : welcome } )
2022-07-18 00:28:26 +02:00
const filterViewIsOpened = new UIEventSource ( false )
2022-07-20 12:04:14 +02:00
filterViewIsOpened . addCallback ( _ = > self . currentView . setData ( { title : "filters" , contents : filterView } ) )
2022-07-18 00:28:26 +02:00
const newPointIsShown = new UIEventSource ( false ) ;
2022-07-20 12:04:14 +02:00
const addNewPoint = new SimpleAddUI (
2022-07-18 00:28:26 +02:00
new UIEventSource ( true ) ,
new UIEventSource ( undefined ) ,
filterViewIsOpened ,
state ,
state . locationControl
) ;
const addNewPointTitle = "Add a missing point"
this . currentView . addCallbackAndRunD ( cv = > {
2022-07-20 12:04:14 +02:00
newPointIsShown . setData ( cv . contents === addNewPoint )
2022-07-18 00:28:26 +02:00
} )
newPointIsShown . addCallbackAndRun ( isShown = > {
2022-07-20 12:04:14 +02:00
if ( isShown ) {
if ( self . currentView . data . contents !== addNewPoint ) {
2022-07-18 00:28:26 +02:00
self . currentView . setData ( { title : addNewPointTitle , contents : addNewPoint } )
}
2022-07-20 12:04:14 +02:00
} else {
if ( self . currentView . data . contents === addNewPoint ) {
2022-07-18 00:28:26 +02:00
self . currentView . setData ( undefined )
}
}
} )
2022-07-20 12:04:14 +02:00
2022-07-13 17:58:01 +02:00
new Combine ( [
2022-07-15 12:56:16 +02:00
new Combine ( [
2022-07-16 03:57:13 +02:00
this . viewSelector ( new Title ( state . layoutToUse . title . Clone ( ) , 2 ) , state . layoutToUse . title . Clone ( ) , welcome , "welcome" ) ,
2022-07-15 12:56:16 +02:00
map . SetClass ( "w-full h-64 shrink-0 rounded-lg" ) ,
2022-07-13 17:58:01 +02:00
new SearchAndGo ( state ) ,
2022-07-15 12:56:16 +02:00
this . viewSelector ( new Title (
2022-07-16 03:57:13 +02:00
new VariableUiElement ( elementsInview . map ( elements = > "There are " + elements ? . length + " elements in view" ) ) ) ,
"Statistics" ,
2022-07-25 18:55:15 +02:00
new StatisticsPanel ( elementsInview , this . state ) , "statistics" ) ,
2022-07-16 03:57:13 +02:00
2022-07-15 12:56:16 +02:00
this . viewSelector ( new FixedUiElement ( "Filter" ) ,
2022-07-18 00:28:26 +02:00
"Filters" , filterView , "filters" ) ,
2022-07-20 12:04:14 +02:00
this . viewSelector ( new Combine ( [ "Add a missing point" ] ) , addNewPointTitle ,
addNewPoint
2022-07-15 12:56:16 +02:00
) ,
2022-07-16 03:57:13 +02:00
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 ( ) ,
2022-07-20 14:06:39 +02:00
new LanguagePicker ( Object . keys ( state . layoutToUse . title . translations ) ) . SetClass ( "mt-2" ) ,
new BackToIndex ( )
2022-07-20 14:39:19 +02:00
] ) . SetClass ( "w-1/2 lg:w-1/4 m-4 flex flex-col shrink-0 grow-0" ) ,
2022-07-16 03:57:13 +02:00
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" )
2022-07-20 14:39:19 +02:00
} ) ) . SetClass ( "w-1/2 lg:w-3/4 m-4 p-2 border-2 border-subtle rounded-xl m-4 ml-0 mr-8 shrink-0 grow-0" ) ,
2022-07-20 14:06:39 +02:00
2022-07-13 17:58:01 +02:00
] ) . SetClass ( "flex h-full" )
. AttachTo ( "leafletDiv" )
}
private SetupMap ( ) : MinimapObj & BaseUIElement {
const state = this . state ;
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
}
}