2021-07-24 01:59:57 +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" ;
2021-07-24 01:59:57 +02:00
import { UIEventSource } from "./Logic/UIEventSource" ;
import { LocalStorageSource } from "./Logic/Web/LocalStorageSource" ;
import { QueryParameters } from "./Logic/Web/QueryParameters" ;
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-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-07-15 09:34:00 +02:00
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline" ;
2021-07-27 19:39:57 +02:00
import FilteredLayer from "./Models/FilteredLayer" ;
2021-07-15 20:47:28 +02:00
import ChangeToElementsActor from "./Logic/Actors/ChangeToElementsActor" ;
2021-08-07 23:11:34 +02:00
import LayoutConfig from "./Models/ThemeConfig/LayoutConfig" ;
2021-09-28 17:30:48 +02:00
import { BBox } from "./Logic/BBox" ;
2021-10-10 00:53:14 +02:00
import SelectedElementTagsUpdater from "./Logic/Actors/SelectedElementTagsUpdater" ;
2021-10-14 21:43:14 +02:00
import TilesourceConfig from "./Models/ThemeConfig/TilesourceConfig" ;
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 {
2021-07-24 01:59:57 +02:00
// The singleton of the global state
public static state : State ;
2020-07-31 01:45:54 +02:00
2021-09-28 18:00:44 +02:00
public readonly layoutToUse : LayoutConfig ;
2021-01-03 03:09:52 +01:00
2021-07-24 01:59:57 +02:00
/ * *
2020-07-31 01:45:54 +02:00
The mapping from id - > UIEventSource < properties >
* /
2021-09-26 23:35:26 +02:00
public allElements : ElementStorage = new ElementStorage ( ) ;
2021-07-24 01:59:57 +02:00
/ * *
2020-07-31 01:45:54 +02:00
THe change handler
* /
2021-09-26 23:35:26 +02:00
public changes : Changes = new Changes ( ) ;
2021-07-24 01:59:57 +02:00
/ * *
2021-01-02 21:03:40 +01:00
The leaflet instance of the big basemap
2020-07-31 01:45:54 +02:00
* /
2021-07-18 14:52:09 +02:00
public leafletMap = new UIEventSource < L.Map > ( undefined , "leafletmap" ) ;
2021-07-24 01:59:57 +02:00
/ * *
* Background layer id
* /
public availableBackgroundLayers : UIEventSource < BaseLayer [ ] > ;
/ * *
2020-08-22 16:00:33 +02:00
The user credentials
2020-07-31 01:45:54 +02:00
* /
2021-07-24 01:59:57 +02:00
public osmConnection : OsmConnection ;
2020-08-26 15:36:04 +02:00
2021-07-24 01:59:57 +02:00
public mangroveIdentity : MangroveIdentity ;
2020-12-08 23:44:34 +01:00
2021-07-24 01:59:57 +02:00
public favouriteLayers : UIEventSource < string [ ] > ;
2020-08-26 15:36:04 +02:00
2021-07-28 02:51:07 +02:00
public filteredLayers : UIEventSource < FilteredLayer [ ] > = new UIEventSource < FilteredLayer [ ] > ( [ ] , "filteredLayers" ) ;
2020-12-08 23:44:34 +01:00
2021-10-14 21:43:14 +02:00
public overlayToggles : { config : TilesourceConfig , isDisplayed : UIEventSource < boolean > } [ ]
2021-07-24 01:59:57 +02:00
/ * *
2021-01-27 01:14:16 +01:00
The latest element that was selected
2020-07-31 01:45:54 +02:00
* /
2021-07-24 01:59:57 +02:00
public readonly selectedElement = new UIEventSource < any > (
undefined ,
"Selected element"
2021-07-20 15:14:51 +02:00
) ;
2021-06-15 00:28:59 +02:00
2021-07-24 01:59:57 +02:00
public readonly featureSwitchUserbadge : UIEventSource < boolean > ;
public readonly featureSwitchSearch : UIEventSource < boolean > ;
2021-07-28 16:48:59 +02:00
public readonly featureSwitchBackgroundSlection : UIEventSource < boolean > ;
2021-07-24 01:59:57 +02:00
public readonly featureSwitchAddNew : UIEventSource < boolean > ;
public readonly featureSwitchWelcomeMessage : UIEventSource < boolean > ;
public readonly featureSwitchIframe : UIEventSource < boolean > ;
public readonly featureSwitchMoreQuests : UIEventSource < boolean > ;
public readonly featureSwitchShareScreen : UIEventSource < boolean > ;
public readonly featureSwitchGeolocation : UIEventSource < boolean > ;
public readonly featureSwitchIsTesting : UIEventSource < boolean > ;
public readonly featureSwitchIsDebugging : UIEventSource < boolean > ;
public readonly featureSwitchShowAllQuestions : UIEventSource < boolean > ;
public readonly featureSwitchApiURL : UIEventSource < string > ;
public readonly featureSwitchFilter : UIEventSource < boolean > ;
2021-07-16 02:06:33 +02:00
public readonly featureSwitchEnableExport : UIEventSource < boolean > ;
public readonly featureSwitchFakeUser : UIEventSource < boolean > ;
2021-07-28 02:51:07 +02:00
public readonly featureSwitchExportAsPdf : UIEventSource < boolean > ;
2021-09-29 16:55:05 +02:00
public readonly overpassUrl : UIEventSource < string [ ] > ;
2021-08-23 15:48:42 +02:00
public readonly overpassTimeout : UIEventSource < number > ;
2021-09-28 18:00:44 +02:00
public readonly overpassMaxZoom : UIEventSource < number > = new UIEventSource < number > ( 17 , "overpass-max-zoom: point to switch between OSM-api and overpass" ) ;
2021-07-16 01:42:09 +02:00
2021-07-24 02:32:33 +02:00
public featurePipeline : FeaturePipeline ;
2020-07-31 01:45:54 +02:00
2021-07-24 01:59:57 +02:00
/ * *
* The map location : currently centered lat , lon and zoom
* /
2021-07-18 14:52:09 +02:00
public readonly locationControl = new UIEventSource < Loc > ( undefined , "locationControl" ) ;
2021-09-21 02:10:42 +02:00
/ * *
* The current visible extent of the screen
* /
public readonly currentBounds = new UIEventSource < BBox > ( undefined )
2021-09-28 17:30:48 +02:00
2021-07-24 01:59:57 +02:00
public backgroundLayer ;
public readonly backgroundLayerId : UIEventSource < string > ;
2021-07-20 15:14:51 +02:00
2021-07-24 01:59:57 +02: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 ) ;
2021-07-20 15:14:51 +02:00
2021-07-24 01:59:57 +02:00
/ * *
* The location as delivered by the GPS
* /
public currentGPSLocation : UIEventSource < {
latlng : { lat : number ; lng : number } ;
accuracy : number ;
} > = new UIEventSource < {
latlng : { lat : number ; lng : number } ;
accuracy : number ;
} > ( undefined ) ;
public layoutDefinition : string ;
public installedThemes : UIEventSource < { layout : LayoutConfig ; definition : string } [ ] > ;
2021-07-28 16:48:59 +02:00
public downloadControlIsOpened : UIEventSource < boolean > =
2021-07-20 15:14:51 +02:00
QueryParameters . GetQueryParameter (
2021-07-28 16:48:59 +02:00
"download-control-toggle" ,
2021-07-24 01:59:57 +02:00
"false" ,
2021-07-28 16:48:59 +02:00
"Whether or not the download panel is shown"
2021-07-24 01:59:57 +02:00
) . map < boolean > (
( str ) = > str !== "false" ,
[ ] ,
( b ) = > "" + b
) ;
2021-07-27 20:41:06 +02:00
public filterIsOpened : UIEventSource < boolean > =
2021-07-20 15:14:51 +02:00
QueryParameters . GetQueryParameter (
2021-07-24 01:59:57 +02:00
"filter-toggle" ,
"false" ,
2021-07-27 20:41:06 +02:00
"Whether or not the filter view is shown"
2021-07-24 01:59:57 +02:00
) . map < boolean > (
( str ) = > str !== "false" ,
[ ] ,
( b ) = > "" + b
) ;
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 > (
( str ) = > ( isNaN ( Number ( str ) ) ? 0 : Number ( str ) ) ,
2021-07-20 15:14:51 +02:00
[ ] ,
2021-07-24 01:59:57 +02:00
( n ) = > "" + n
2021-07-20 15:14:51 +02:00
) ;
2021-06-08 16:52:31 +02:00
2021-07-24 01:59:57 +02:00
constructor ( layoutToUse : LayoutConfig ) {
const self = this ;
2021-09-28 18:00:44 +02:00
this . layoutToUse = layoutToUse ;
2021-07-24 01:59:57 +02:00
// -- Location control initialization
{
const zoom = State . asFloat (
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" ) )
) ;
2021-07-18 14:52:09 +02:00
this . locationControl . setData ( {
2021-07-24 01:59:57 +02:00
zoom : Utils.asFloat ( zoom . data ) ,
lat : Utils.asFloat ( lat . data ) ,
lon : Utils.asFloat ( lon . data ) ,
2021-07-18 14:52:09 +02:00
} )
this . locationControl . addCallback ( ( latlonz ) = > {
// Sync th location controls
2021-07-24 01:59:57 +02:00
zoom . setData ( latlonz . zoom ) ;
lat . setData ( latlonz . lat ) ;
lon . setData ( latlonz . lon ) ;
} ) ;
2021-09-28 18:00:44 +02:00
2021-07-24 01:59:57 +02:00
}
2021-07-20 15:14:51 +02:00
2021-07-24 01:59:57 +02:00
// Helper function to initialize feature switches
function featSw (
key : string ,
deflt : ( layout : LayoutConfig ) = > boolean ,
documentation : string
) : UIEventSource < boolean > {
2021-09-28 18:00:44 +02:00
const defaultValue = deflt ( self . layoutToUse ) ;
const queryParam = QueryParameters . GetQueryParameter (
2021-07-24 01:59:57 +02:00
key ,
2021-09-28 18:00:44 +02:00
"" + defaultValue ,
2021-07-24 01:59:57 +02:00
documentation
) ;
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
2021-09-28 18:00:44 +02:00
return queryParam . map ( ( str ) = >
str === undefined ? defaultValue : str !== "false"
)
2021-07-24 01:59:57 +02:00
}
2021-07-20 15:14:51 +02:00
2021-07-24 01:59:57 +02:00
// Feature switch initialization - not as a function as the UIEventSources are readonly
{
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"
) ;
2021-07-28 16:48:59 +02:00
this . featureSwitchBackgroundSlection = featSw (
"fs-background" ,
( layoutToUse ) = > layoutToUse ? . enableBackgroundLayerSelection ? ? true ,
"Disables/Enables the background layer control"
2021-07-24 01:59:57 +02:00
) ;
2021-08-23 15:48:42 +02:00
2021-07-24 01:59:57 +02:00
this . featureSwitchFilter = featSw (
"fs-filter" ,
( layoutToUse ) = > layoutToUse ? . enableLayers ? ? true ,
"Disables/Enables the filter"
) ;
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 . featureSwitchShowAllQuestions = featSw (
"fs-all-questions" ,
( layoutToUse ) = > layoutToUse ? . enableShowAllQuestions ? ? false ,
"Always show all questions"
) ;
2021-07-28 02:51:07 +02:00
this . featureSwitchEnableExport = featSw (
"fs-export" ,
( layoutToUse ) = > layoutToUse ? . enableExportButton ? ? false ,
"Enable the export as GeoJSON and CSV button"
) ;
this . featureSwitchExportAsPdf = featSw (
"fs-pdf" ,
( layoutToUse ) = > layoutToUse ? . enablePdfDownload ? ? false ,
"Enable the PDF download button"
) ;
2021-08-23 15:48:42 +02:00
2021-07-24 01:59:57 +02:00
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"
) . map (
( str ) = > str === "true" ,
[ ] ,
( b ) = > "" + b
) ;
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-07-16 02:06:33 +02:00
this . featureSwitchFakeUser = QueryParameters . GetQueryParameter ( "fake-user" , "false" ,
"If true, 'dryrun' mode is activated and a fake user account is loaded" )
. map ( str = > str === "true" , [ ] , b = > "" + b ) ;
2021-06-08 16:52:31 +02:00
2021-07-24 01:59:57 +02:00
this . featureSwitchApiURL = QueryParameters . GetQueryParameter (
"backend" ,
"osm" ,
"The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'"
) ;
2021-07-24 02:32:33 +02:00
2021-08-23 15:48:42 +02:00
this . overpassUrl = QueryParameters . GetQueryParameter ( "overpassUrl" ,
2021-09-29 23:58:17 +02:00
( layoutToUse ? . overpassUrl ? ? Constants . defaultOverpassUrls ) . join ( "," ) ,
2021-08-23 15:48:42 +02:00
"Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter"
2021-09-29 16:55:05 +02:00
) . map ( param = > param . split ( "," ) , [ ] , urls = > urls . join ( "," ) )
2021-08-23 15:48:42 +02:00
this . overpassTimeout = QueryParameters . GetQueryParameter ( "overpassTimeout" ,
"" + layoutToUse ? . overpassTimeout ,
"Set a different timeout (in seconds) for queries in overpass" )
. map ( str = > Number ( str ) , [ ] , n = > "" + n )
2021-07-16 02:06:33 +02:00
2021-07-24 02:32:33 +02:00
this . featureSwitchUserbadge . addCallbackAndRun ( userbadge = > {
if ( ! userbadge ) {
this . featureSwitchAddNew . setData ( false )
}
} )
2021-06-15 00:28:59 +02:00
}
2021-07-24 01:59:57 +02:00
{
// Some other feature switches
const customCssQP = QueryParameters . GetQueryParameter (
"custom-css" ,
"" ,
"If specified, the custom css from the given link will be loaded additionaly"
) ;
if ( customCssQP . data !== undefined && customCssQP . data !== "" ) {
Utils . LoadCustomCss ( customCssQP . data ) ;
}
this . backgroundLayerId = QueryParameters . GetQueryParameter (
"background" ,
layoutToUse ? . defaultBackgroundId ? ? "osm" ,
"The id of the background layer to start with"
) ;
2021-06-15 00:28:59 +02:00
}
2021-07-24 01:59:57 +02:00
if ( Utils . runningFromConsole ) {
return ;
2021-07-20 15:14:51 +02:00
}
2021-07-24 01:59:57 +02:00
2021-09-28 17:30:48 +02:00
this . osmConnection = new OsmConnection ( {
changes : this.changes ,
dryRun : this.featureSwitchIsTesting.data ,
fakeUser : this.featureSwitchFakeUser.data ,
allElements : this.allElements ,
oauth_token : QueryParameters.GetQueryParameter (
2021-07-24 01:59:57 +02:00
"oauth_token" ,
undefined ,
"Used to complete the login"
) ,
2021-09-28 17:30:48 +02:00
layoutName : layoutToUse?.id ,
osmConfiguration : < 'osm' | 'osm-test' > this . featureSwitchApiURL . data
} )
2021-07-24 01:59:57 +02:00
2021-07-15 20:47:28 +02:00
new ChangeToElementsActor ( this . changes , this . allElements )
2021-07-28 02:51:07 +02:00
2021-07-24 01:59:57 +02:00
new PendingChangesUploader ( this . changes , this . selectedElement ) ;
2021-10-10 00:53:14 +02:00
new SelectedElementTagsUpdater ( this )
2021-07-24 01:59:57 +02:00
this . mangroveIdentity = new MangroveIdentity (
this . osmConnection . GetLongPreference ( "identity" , "mangrove" )
) ;
this . installedThemes = new InstalledThemes (
this . osmConnection
) . installedThemes ;
// Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
this . favouriteLayers = LocalStorageSource . Get ( "favouriteLayers" )
. syncWith ( this . osmConnection . GetLongPreference ( "favouriteLayers" ) )
. map (
( str ) = > Utils . Dedup ( str ? . split ( ";" ) ) ? ? [ ] ,
[ ] ,
( layers ) = > Utils . Dedup ( layers ) ? . join ( ";" )
) ;
Locale . language . syncWith ( this . osmConnection . GetPreference ( "language" ) ) ;
Locale . language
. addCallback ( ( currentLanguage ) = > {
2021-09-28 18:00:44 +02:00
const layoutToUse = self . layoutToUse ;
2021-07-24 01:59:57 +02:00
if ( layoutToUse === undefined ) {
return ;
}
2021-09-28 18:00:44 +02:00
if ( this . layoutToUse . language . indexOf ( currentLanguage ) < 0 ) {
2021-07-24 01:59:57 +02:00
console . log (
"Resetting language to" ,
layoutToUse . language [ 0 ] ,
"as" ,
currentLanguage ,
" is unsupported"
) ;
// The current language is not supported -> switch to a supported one
Locale . language . setData ( layoutToUse . language [ 0 ] ) ;
}
} )
. ping ( ) ;
2021-09-22 05:02:09 +02:00
new TitleHandler ( this ) ;
2021-10-14 21:43:14 +02:00
this . overlayToggles = this . layoutToUse . tileLayerSources . filter ( c = > c . name !== undefined ) . map ( c = > ( {
config : c ,
2021-10-15 00:52:31 +02:00
isDisplayed : QueryParameters.GetQueryParameter ( "overlay-" + c . id , "" + c . defaultState , "Wether or not the overlay " + c . id + " is shown" ) . map ( str = > str === "true" , [ ] , b = > "" + b )
2021-10-14 21:43:14 +02:00
} ) )
2021-07-24 01:59:57 +02:00
}
private static asFloat ( source : UIEventSource < string > ) : UIEventSource < number > {
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 ) ;
}
) ;
}
2020-07-31 16:17:16 +02:00
}