2020-07-31 01:45:54 +02:00
import { Utils } from "./Utils" ;
import { ElementStorage } from "./Logic/ElementStorage" ;
import { Changes } from "./Logic/Osm/Changes" ;
import { OsmConnection } from "./Logic/Osm/OsmConnection" ;
2020-07-31 04:58:58 +02:00
import Locale from "./UI/i18n/Locale" ;
2020-08-17 17:23:15 +02:00
import { UIEventSource } from "./Logic/UIEventSource" ;
import { LocalStorageSource } from "./Logic/Web/LocalStorageSource" ;
import { QueryParameters } from "./Logic/Web/QueryParameters" ;
2020-11-11 16:23:49 +01:00
import LayoutConfig from "./Customizations/JSON/LayoutConfig" ;
2020-12-08 23:44:34 +01:00
import { MangroveIdentity } from "./Logic/Web/MangroveReviews" ;
2021-01-03 00:19:42 +01:00
import InstalledThemes from "./Logic/Actors/InstalledThemes" ;
2021-01-04 04:06:21 +01:00
import BaseLayer from "./Models/BaseLayer" ;
2021-01-02 16:04:16 +01:00
import Loc from "./Models/Loc" ;
import Constants from "./Models/Constants" ;
2021-01-03 13:50:18 +01:00
2021-01-03 03:09:52 +01:00
import UpdateFromOverpass from "./Logic/Actors/UpdateFromOverpass" ;
import LayerConfig from "./Customizations/JSON/LayerConfig" ;
2021-01-08 18:02:07 +01:00
import TitleHandler from "./Logic/Actors/TitleHandler" ;
2021-02-20 22:18:42 +01:00
import PendingChangesUploader from "./Logic/Actors/PendingChangesUploader" ;
2021-04-18 14:24:30 +02:00
import { Relation } from "./Logic/Osm/ExtractRelations" ;
2021-05-06 01:33:09 +02:00
import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource" ;
2020-07-31 01:45:54 +02:00
/ * *
* Contains the global state : a bunch of UI - event sources
* /
2020-10-02 19:00:24 +02:00
export default class State {
2020-07-31 01:45:54 +02:00
// The singleton of the global state
public static state : State ;
2021-01-03 03:09:52 +01:00
2021-02-20 01:45:51 +01:00
2020-11-11 16:23:49 +01:00
public readonly layoutToUse = new UIEventSource < LayoutConfig > ( undefined ) ;
2020-07-31 01:45:54 +02:00
/ * *
The mapping from id - > UIEventSource < properties >
* /
public allElements : ElementStorage ;
/ * *
THe change handler
* /
public changes : Changes ;
/ * *
2021-01-02 21:03:40 +01:00
The leaflet instance of the big basemap
2020-07-31 01:45:54 +02:00
* /
2021-01-02 21:03:40 +01:00
public leafletMap = new UIEventSource < L.Map > ( undefined ) ;
2020-09-28 00:30:39 +02:00
/ * *
* Background layer id
* /
public availableBackgroundLayers : UIEventSource < BaseLayer [ ] > ;
2020-07-31 01:45:54 +02:00
/ * *
2020-08-22 16:00:33 +02:00
The user credentials
2020-07-31 01:45:54 +02:00
* /
public osmConnection : OsmConnection ;
2020-08-26 15:36:04 +02:00
2020-12-08 23:44:34 +01:00
public mangroveIdentity : MangroveIdentity ;
2020-08-26 15:36:04 +02:00
public favouriteLayers : UIEventSource < string [ ] > ;
2020-10-19 12:08:42 +02:00
public layerUpdater : UpdateFromOverpass ;
2021-05-06 01:33:09 +02:00
2021-05-06 03:03:54 +02:00
public osmApiFeatureSource : OsmApiFeatureSource ;
2020-08-26 15:36:04 +02:00
2021-01-03 03:09:52 +01:00
public filteredLayers : UIEventSource < {
readonly isDisplayed : UIEventSource < boolean > ,
2021-01-04 04:06:21 +01:00
readonly layerDef : LayerConfig ;
2021-01-03 03:09:52 +01:00
} [ ] > = new UIEventSource < {
readonly isDisplayed : UIEventSource < boolean > ,
readonly layerDef : LayerConfig ;
} [ ] > ( [ ] )
2020-12-08 23:44:34 +01:00
2020-07-31 01:45:54 +02:00
/ * *
* The message that should be shown at the center of the screen
* /
public readonly centerMessage = new UIEventSource < string > ( "" ) ;
/ * *
2021-01-27 01:14:16 +01:00
The latest element that was selected
2020-07-31 01:45:54 +02:00
* /
2021-03-12 13:48:49 +01:00
public readonly selectedElement = new UIEventSource < any > ( undefined , "Selected element" )
2020-07-31 01:45:54 +02:00
2021-04-18 14:24:30 +02:00
/ * *
* Keeps track of relations : which way is part of which other way ?
* Set by the overpass - updater ; used in the metatagging
* /
public readonly knownRelations = new UIEventSource < Map < string , { role : string , relation : Relation } [ ] > > ( undefined , "Relation memberships" )
2021-02-20 01:45:51 +01:00
2020-07-31 01:45:54 +02:00
public readonly featureSwitchUserbadge : UIEventSource < boolean > ;
public readonly featureSwitchSearch : UIEventSource < boolean > ;
public readonly featureSwitchLayers : UIEventSource < boolean > ;
public readonly featureSwitchAddNew : UIEventSource < boolean > ;
public readonly featureSwitchWelcomeMessage : UIEventSource < boolean > ;
public readonly featureSwitchIframe : UIEventSource < boolean > ;
2020-08-07 00:45:33 +02:00
public readonly featureSwitchMoreQuests : UIEventSource < boolean > ;
public readonly featureSwitchShareScreen : UIEventSource < boolean > ;
2020-08-08 14:43:48 +02:00
public readonly featureSwitchGeolocation : UIEventSource < boolean > ;
2021-01-27 01:14:16 +01:00
public readonly featureSwitchIsTesting : UIEventSource < boolean > ;
2021-03-22 01:05:01 +01:00
public readonly featureSwitchIsDebugging : UIEventSource < boolean > ;
2020-07-31 01:45:54 +02:00
/ * *
* The map location : currently centered lat , lon and zoom
* /
2021-01-02 16:04:16 +01:00
public readonly locationControl = new UIEventSource < Loc > ( undefined ) ;
2021-01-03 13:50:18 +01:00
public backgroundLayer ;
2021-01-02 21:03:40 +01:00
/ * L a s t l o c a t i o n w h e r e a c l i c k w a s r e g i s t e r e d
* /
public readonly LastClickLocation : UIEventSource < { lat : number , lon : number } > = new UIEventSource < { lat : number , lon : number } > ( undefined )
2020-07-31 01:45:54 +02:00
/ * *
* The location as delivered by the GPS
* /
public currentGPSLocation : UIEventSource < {
2021-01-03 03:09:52 +01:00
latlng : { lat : number , lng : number } ,
2020-07-31 01:45:54 +02:00
accuracy : number
2021-01-03 03:09:52 +01:00
} > = new UIEventSource < { latlng : { lat : number , lng : number } , accuracy : number } > ( undefined ) ;
2020-08-26 15:36:04 +02:00
public layoutDefinition : string ;
2020-11-11 16:23:49 +01:00
public installedThemes : UIEventSource < { layout : LayoutConfig ; definition : string } [ ] > ;
2020-07-31 04:58:58 +02:00
2021-01-08 18:02:07 +01:00
public layerControlIsOpened : UIEventSource < boolean > =
2021-01-04 04:06:21 +01:00
QueryParameters . GetQueryParameter ( "layer-control-toggle" , "false" , "Whether or not the layer control is shown" )
2021-01-08 18:02:07 +01:00
. map < boolean > ( ( str ) = > str !== "false" , [ ] , b = > "" + b )
2020-09-17 23:53:57 +02:00
2021-01-02 19:09:49 +01:00
public welcomeMessageOpenedTab = QueryParameters . GetQueryParameter ( "tab" , "0" , ` The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have > ${ Constants . userJourney . mapCompleteHelpUnlock } changesets) ` ) . map < number > (
2020-09-17 23:53:57 +02:00
str = > isNaN ( Number ( str ) ) ? 0 : Number ( str ) , [ ] , n = > "" + n
2020-08-30 01:13:18 +02:00
) ;
2020-07-31 01:45:54 +02:00
2020-11-11 16:23:49 +01:00
constructor ( layoutToUse : LayoutConfig ) {
2020-08-26 15:36:04 +02:00
const self = this ;
2021-02-20 01:45:51 +01:00
2020-09-18 00:31:54 +02:00
this . layoutToUse . setData ( layoutToUse ) ;
2021-01-27 01:14:16 +01:00
// -- Location control initialization
2021-02-20 01:45:51 +01:00
{
const zoom = State . asFloat (
2021-01-27 01:14:16 +01:00
QueryParameters . GetQueryParameter ( "z" , "" + ( layoutToUse ? . startZoom ? ? 1 ) , "The initial/current zoom level" )
. syncWith ( LocalStorageSource . Get ( "zoom" ) ) ) ;
const lat = State . asFloat ( QueryParameters . GetQueryParameter ( "lat" , "" + ( layoutToUse ? . startLat ? ? 0 ) , "The initial/current latitude" )
. syncWith ( LocalStorageSource . Get ( "lat" ) ) ) ;
const lon = State . asFloat ( QueryParameters . GetQueryParameter ( "lon" , "" + ( layoutToUse ? . startLon ? ? 0 ) , "The initial/current longitude of the app" )
. syncWith ( LocalStorageSource . Get ( "lon" ) ) ) ;
this . locationControl = new UIEventSource < Loc > ( {
zoom : Utils.asFloat ( zoom . data ) ,
lat : Utils.asFloat ( lat . data ) ,
lon : Utils.asFloat ( lon . data ) ,
} ) . addCallback ( ( latlonz ) = > {
zoom . setData ( latlonz . zoom ) ;
lat . setData ( latlonz . lat ) ;
lon . setData ( latlonz . lon ) ;
} ) ;
this . layoutToUse . addCallback ( layoutToUse = > {
const lcd = self . locationControl . data ;
lcd . zoom = lcd . zoom ? ? layoutToUse ? . startZoom ;
lcd . lat = lcd . lat ? ? layoutToUse ? . startLat ;
lcd . lon = lcd . lon ? ? layoutToUse ? . startLon ;
self . locationControl . ping ( ) ;
} ) ;
2020-07-31 01:45:54 +02:00
2021-01-27 01:14:16 +01:00
}
2021-02-20 01:45:51 +01:00
2021-01-27 01:14:16 +01:00
// Helper function to initialize feature switches
2020-11-13 23:58:11 +01:00
function featSw ( key : string , deflt : ( layout : LayoutConfig ) = > boolean , documentation : string ) : UIEventSource < boolean > {
const queryParameterSource = QueryParameters . GetQueryParameter ( key , undefined , documentation ) ;
2020-07-31 01:45:54 +02:00
// I'm so sorry about someone trying to decipher this
2020-08-26 15:36:04 +02:00
2021-01-02 21:03:40 +01:00
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
2020-07-31 01:45:54 +02:00
return UIEventSource . flatten (
2020-08-26 15:36:04 +02:00
self . layoutToUse . map ( ( layout ) = > {
const defaultValue = deflt ( layout ) ;
2020-11-12 12:18:02 +01:00
const queryParam = QueryParameters . GetQueryParameter ( key , "" + defaultValue , documentation )
2020-08-26 15:36:04 +02:00
return queryParam . map ( ( str ) = > str === undefined ? defaultValue : ( str !== "false" ) ) ;
} ) , [ queryParameterSource ] ) ;
2020-07-31 01:45:54 +02:00
}
2021-01-27 01:14:16 +01:00
// Feature switch initialization - not as a function as the UIEventSources are readonly
2021-02-20 01:45:51 +01:00
{
2021-01-27 01:14:16 +01:00
this . featureSwitchUserbadge = featSw ( "fs-userbadge" , ( layoutToUse ) = > layoutToUse ? . enableUserBadge ? ? true ,
"Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode." ) ;
this . featureSwitchSearch = featSw ( "fs-search" , ( layoutToUse ) = > layoutToUse ? . enableSearch ? ? true ,
"Disables/Enables the search bar" ) ;
this . featureSwitchLayers = featSw ( "fs-layers" , ( layoutToUse ) = > layoutToUse ? . enableLayers ? ? true ,
"Disables/Enables the layer control" ) ;
this . featureSwitchAddNew = featSw ( "fs-add-new" , ( layoutToUse ) = > layoutToUse ? . enableAddNewPoints ? ? true ,
"Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)" ) ;
this . featureSwitchWelcomeMessage = featSw ( "fs-welcome-message" , ( ) = > true ,
"Disables/enables the help menu or welcome message" ) ;
this . featureSwitchIframe = featSw ( "fs-iframe" , ( ) = > false ,
"Disables/Enables the iframe-popup" ) ;
this . featureSwitchMoreQuests = featSw ( "fs-more-quests" , ( layoutToUse ) = > layoutToUse ? . enableMoreQuests ? ? true ,
"Disables/Enables the 'More Quests'-tab in the welcome message" ) ;
this . featureSwitchShareScreen = featSw ( "fs-share-screen" , ( layoutToUse ) = > layoutToUse ? . enableShareScreen ? ? true ,
"Disables/Enables the 'Share-screen'-tab in the welcome message" ) ;
this . featureSwitchGeolocation = featSw ( "fs-geolocation" , ( layoutToUse ) = > layoutToUse ? . enableGeolocation ? ? true ,
"Disables/Enables the geolocation button" ) ;
this . featureSwitchIsTesting = QueryParameters . GetQueryParameter ( "test" , "false" ,
"If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org" )
2021-02-20 01:45:51 +01:00
. map ( str = > str === "true" , [ ] , b = > "" + b ) ;
2021-03-22 01:05:01 +01:00
this . featureSwitchIsDebugging = QueryParameters . GetQueryParameter ( "debug" , "false" ,
"If true, shows some extra debugging help such as all the available tags on every object" )
. map ( str = > str === "true" , [ ] , b = > "" + b )
2021-01-27 01:14:16 +01:00
}
2021-02-20 01:45:51 +01:00
2020-07-31 04:58:58 +02:00
this . osmConnection = new OsmConnection (
2021-01-27 01:14:16 +01:00
this . featureSwitchIsTesting . data ,
2020-11-13 23:58:11 +01:00
QueryParameters . GetQueryParameter ( "oauth_token" , undefined ,
"Used to complete the login" ) ,
2021-01-14 22:25:11 +01:00
layoutToUse ? . id ,
2020-08-30 01:13:18 +02:00
true
2020-07-31 04:58:58 +02:00
) ;
2020-12-08 23:44:34 +01:00
2021-01-27 01:14:16 +01:00
this . allElements = new ElementStorage ( ) ;
this . changes = new Changes ( ) ;
2021-05-06 03:03:54 +02:00
this . osmApiFeatureSource = new OsmApiFeatureSource ( this . locationControl )
2021-02-20 22:18:42 +01:00
new PendingChangesUploader ( this . changes , this . selectedElement ) ;
2021-02-20 01:45:51 +01:00
2020-12-08 23:44:34 +01:00
this . mangroveIdentity = new MangroveIdentity (
this . osmConnection . GetLongPreference ( "identity" , "mangrove" )
) ;
2020-08-26 15:36:04 +02:00
2021-01-03 00:19:42 +01:00
this . installedThemes = new InstalledThemes ( this . osmConnection ) . installedThemes ;
2020-09-15 14:00:31 +02:00
2021-01-02 16:04:16 +01:00
// Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
2021-02-20 01:45:51 +01:00
this . favouriteLayers = LocalStorageSource . Get ( "favouriteLayers" )
. syncWith ( this . osmConnection . GetLongPreference ( "favouriteLayers" ) )
. map (
str = > Utils . Dedup ( str ? . split ( ";" ) ) ? ? [ ] ,
[ ] , layers = > Utils . Dedup ( layers ) ? . join ( ";" )
) ;
2020-09-15 14:00:31 +02:00
2020-07-31 04:58:58 +02:00
Locale . language . syncWith ( this . osmConnection . GetPreference ( "language" ) ) ;
Locale . language . addCallback ( ( currentLanguage ) = > {
2020-08-26 15:36:04 +02:00
const layoutToUse = self . layoutToUse . data ;
if ( layoutToUse === undefined ) {
return ;
}
2020-11-11 16:23:49 +01:00
if ( this . layoutToUse . data . language . indexOf ( currentLanguage ) < 0 ) {
console . log ( "Resetting language to" , layoutToUse . language [ 0 ] , "as" , currentLanguage , " is unsupported" )
2020-07-31 04:58:58 +02:00
// The current language is not supported -> switch to a supported one
2020-11-11 16:23:49 +01:00
Locale . language . setData ( layoutToUse . language [ 0 ] ) ;
2020-07-31 04:58:58 +02:00
}
} ) . ping ( )
2021-01-25 03:12:09 +01:00
new TitleHandler ( this . layoutToUse , this . selectedElement , this . allElements ) ;
2021-02-20 01:45:51 +01:00
2020-07-31 16:17:16 +02:00
}
2020-09-10 19:33:06 +02:00
2021-01-03 03:09:52 +01:00
private static asFloat ( source : UIEventSource < string > ) : UIEventSource < number > {
2021-01-02 21:03:40 +01:00
return source . map ( str = > {
let parsed = parseFloat ( str ) ;
return isNaN ( parsed ) ? undefined : parsed ;
} , [ ] , fl = > {
if ( fl === undefined || isNaN ( fl ) ) {
return undefined ;
}
return ( "" + fl ) . substr ( 0 , 8 ) ;
} )
}
2021-02-20 01:45:51 +01:00
2021-01-03 03:09:52 +01:00
2020-07-31 16:17:16 +02:00
}