import {UIElement} from "./UI/UIElement"; import {Layout} from "./Customizations/Layout"; import {Utils} from "./Utils"; import {LayerDefinition, Preset} from "./Customizations/LayerDefinition"; import {ElementStorage} from "./Logic/ElementStorage"; import {Changes} from "./Logic/Osm/Changes"; import {OsmConnection} from "./Logic/Osm/OsmConnection"; import Locale from "./UI/i18n/Locale"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; import Translations from "./UI/i18n/Translations"; import {CustomLayersState} from "./Logic/CustomLayersState"; import {FilteredLayer} from "./Logic/FilteredLayer"; import {LayerUpdater} from "./Logic/LayerUpdater"; import {UIEventSource} from "./Logic/UIEventSource"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {QueryParameters} from "./Logic/Web/QueryParameters"; /** * Contains the global state: a bunch of UI-event sources */ export class State { // The singleton of the global state public static state: State; public static vNumber = "0.0.6c"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { customLayoutUnlock: 50, themeGeneratorUnlock: 500, tagsVisibleAt: 200, tagsVisibleAndWikiLinked: 250 }; public static runningFromConsole: boolean = false; /** THe layout to use */ public readonly layoutToUse = new UIEventSource(undefined); public layoutDefinition : string; /** The mapping from id -> UIEventSource */ public allElements: ElementStorage; /** THe change handler */ public changes: Changes; /** THe basemap with leaflet instance */ public bm; /** The user credentials */ public osmConnection: OsmConnection; public layerUpdater : LayerUpdater; public filteredLayers: UIEventSource = new UIEventSource([]) public presets: UIEventSource = new UIEventSource([]) /** * The message that should be shown at the center of the screen */ public readonly centerMessage = new UIEventSource(""); /** * The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource */ public readonly secondsTillChangesAreSaved = new UIEventSource(0); /** This message is shown full screen on mobile devices */ public readonly fullScreenMessage = new UIEventSource(undefined); /** The latest element that was selected - used to generate the right UI at the right place */ public readonly selectedElement = new UIEventSource<{ feature: any }>(undefined); public readonly zoom = QueryParameters.GetQueryParameter("z", undefined) .syncWith(LocalStorageSource.Get("zoom")); public readonly lat = QueryParameters.GetQueryParameter("lat", undefined) .syncWith(LocalStorageSource.Get("lat")); public readonly lon = QueryParameters.GetQueryParameter("lon", undefined) .syncWith(LocalStorageSource.Get("lon")); public readonly featureSwitchUserbadge: UIEventSource; public readonly featureSwitchSearch: UIEventSource; public readonly featureSwitchLayers: UIEventSource; public readonly featureSwitchAddNew: UIEventSource; public readonly featureSwitchWelcomeMessage: UIEventSource; public readonly featureSwitchIframe: UIEventSource; public readonly featureSwitchMoreQuests: UIEventSource; public readonly featureSwitchShareScreen: UIEventSource; public readonly featureSwitchGeolocation: UIEventSource; /** * The map location: currently centered lat, lon and zoom */ public readonly locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>(undefined); /** * The location as delivered by the GPS */ public currentGPSLocation: UIEventSource<{ latlng: number, accuracy: number }> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined); /** After this many milliseconds without changes, saves are sent of to OSM */ public readonly saveTimeout = new UIEventSource(30 * 1000); /** * Layers can be marked as favourites, they show up in a custom layout */ public favourteLayers: UIEventSource = new UIEventSource([]) constructor(layoutToUse: Layout) { this.layoutToUse = new UIEventSource(layoutToUse); this.locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({ zoom: Utils.asFloat(this.zoom.data) ?? layoutToUse.startzoom, lat: Utils.asFloat(this.lat.data) ?? layoutToUse.startLat, lon: Utils.asFloat(this.lon.data) ?? layoutToUse.startLon }).addCallback((latlonz) => { this.zoom.setData(latlonz.zoom.toString()); this.lat.setData(latlonz.lat.toString().substr(0, 6)); this.lon.setData(latlonz.lon.toString().substr(0, 6)); }) const self = this; function featSw(key: string, deflt: (layout: Layout) => boolean): UIEventSource { const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined); // I'm so sorry about someone trying to decipher this // It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened return UIEventSource.flatten( self.layoutToUse.map((layout) => QueryParameters.GetQueryParameter(key, "" + deflt(layout)).map((str) => str === undefined ? deflt(layout) : str !== "false")), [queryParameterSource]); } this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge); this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch); this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers); this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAdd); this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true); this.featureSwitchIframe = featSw("fs-iframe", () => false); this.featureSwitchMoreQuests = featSw("fs-more-quests", () => layoutToUse?.enableMoreQuests); this.featureSwitchShareScreen = featSw("fs-share-screen", () => layoutToUse?.enableShareScreen); this.featureSwitchGeolocation = featSw("fs-geolocation", () => layoutToUse?.enableGeolocation); this.osmConnection = new OsmConnection( QueryParameters.GetQueryParameter("test", "false").data === "true", QueryParameters.GetQueryParameter("oauth_token", undefined) ); CustomLayersState.InitFavouriteLayers(this); Locale.language.syncWith(this.osmConnection.GetPreference("language")); Locale.language.addCallback((currentLanguage) => { if (layoutToUse.supportedLanguages.indexOf(currentLanguage) < 0) { console.log("Resetting language to", layoutToUse.supportedLanguages[0], "as", currentLanguage, " is unsupported") // The current language is not supported -> switch to a supported one Locale.language.setData(layoutToUse.supportedLanguages[0]); } }).ping() document.title = Translations.W(layoutToUse.title).InnerRender(); Locale.language.addCallback(e => { document.title = Translations.W(layoutToUse.title).InnerRender(); }) this.allElements = new ElementStorage(); this.changes = new Changes(this); if(State.runningFromConsole){ console.warn("running from console - not initializing map. Assuming test.html"); return; } if (document.getElementById("leafletDiv") === null) { console.warn("leafletDiv not found - not initializing map. Assuming test.html"); return; } } public GetFilteredLayerFor(id: string) : FilteredLayer{ for (const flayer of this.filteredLayers.data) { if(flayer.layerDef.id === id){ return flayer; } } return undefined; } }