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" ;
2020-11-11 16:23:49 +01:00
import LayoutConfig from "./Customizations/JSON/LayoutConfig" ;
2021-07-24 01:59:57 +02: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-05-14 02:25:30 +02:00
import OverpassFeatureSource from "./Logic/Actors/OverpassFeatureSource" ;
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-24 01:59:57 +02:00
import { Relation } from "./Logic/Osm/ExtractRelations" ;
2021-05-06 01:33:09 +02:00
import OsmApiFeatureSource from "./Logic/FeatureSource/OsmApiFeatureSource" ;
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" ;
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-07-18 14:52:09 +02:00
public readonly layoutToUse = new UIEventSource < LayoutConfig > ( undefined , "layoutToUse" ) ;
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-07-24 01:59:57 +02:00
public allElements : ElementStorage ;
/ * *
2020-07-31 01:45:54 +02:00
THe change handler
* /
2021-07-24 01:59:57 +02:00
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-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-24 01:59:57 +02:00
public layerUpdater : OverpassFeatureSource ;
2020-08-26 15:36:04 +02:00
2021-07-24 01:59:57 +02:00
public osmApiFeatureSource : OsmApiFeatureSource ;
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-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
/ * *
* 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" ) ;
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-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-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 ;
this . layoutToUse . setData ( layoutToUse ) ;
// -- 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 ) ;
} ) ;
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 ( ) ;
} ) ;
}
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 > {
const queryParameterSource = QueryParameters . GetQueryParameter (
key ,
undefined ,
documentation
) ;
// I'm so sorry about someone trying to decipher this
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
return UIEventSource . flatten (
self . layoutToUse . map ( ( layout ) = > {
const defaultValue = deflt ( layout ) ;
const queryParam = QueryParameters . GetQueryParameter (
key ,
"" + defaultValue ,
documentation
) ;
return queryParam . map ( ( str ) = >
str === undefined ? defaultValue : str !== "false"
) ;
} ) ,
[ queryParameterSource ]
) ;
}
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-07-28 16:48:59 +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-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-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
this . osmConnection = new OsmConnection (
this . featureSwitchIsTesting . data ,
2021-07-16 02:06:33 +02:00
this . featureSwitchFakeUser . data ,
2021-07-24 01:59:57 +02:00
QueryParameters . GetQueryParameter (
"oauth_token" ,
undefined ,
"Used to complete the login"
) ,
layoutToUse ? . id ,
true ,
// @ts-ignore
this . featureSwitchApiURL . data
) ;
this . allElements = new ElementStorage ( ) ;
this . changes = new Changes ( ) ;
2021-07-28 02:51:07 +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-05-13 13:04:17 +02:00
this . osmApiFeatureSource = new OsmApiFeatureSource ( )
2021-07-24 01:59:57 +02:00
new PendingChangesUploader ( this . changes , this . selectedElement ) ;
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 ) = > {
const layoutToUse = self . layoutToUse . data ;
if ( layoutToUse === undefined ) {
return ;
}
if ( this . layoutToUse . data . language . indexOf ( currentLanguage ) < 0 ) {
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 ( ) ;
new TitleHandler ( this . layoutToUse , this . selectedElement , this . allElements ) ;
}
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
}