From 004eead4eec12cf28df7d97a354cb92851d5ee2b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 31 Jul 2020 01:45:54 +0200 Subject: [PATCH] Refactoring: introduction of global state to simplify getting common objects --- Customizations/Layers/Artwork.ts | 4 +- Customizations/Layers/Widths.ts | 15 ++- Customizations/Layout.ts | 60 ---------- Customizations/Layouts/Smoothness.ts | 1 + Customizations/TagRendering.ts | 19 ++-- Customizations/UIElementConstructor.ts | 6 +- Helpers.ts | 10 +- InitUiElements.ts | 50 ++++----- Logic/FilteredLayer.ts | 40 +++---- Logic/ImageSearcher.ts | 8 +- Logic/LayerUpdater.ts | 18 ++- Logic/Leaflet/GeoLocationHandler.ts | 50 ++++----- Logic/Leaflet/StrayClickHandler.ts | 24 ++-- Logic/Osm/Changes.ts | 16 +-- Logic/Osm/Geocoding.ts | 4 +- Logic/Osm/OsmConnection.ts | 40 +++++-- Logic/Osm/OsmImageUploadHandler.ts | 15 +-- Logic/QueryParameters.ts | 5 +- State.ts | 134 ++++++++++++++++++++++ UI/CenterMessageBox.ts | 65 +++++------ UI/FeatureInfoBox.ts | 12 +- UI/FullScreenMessageBoxHandler.ts | 25 ++--- UI/Image/ImageCarousel.ts | 24 ++-- UI/Image/ImageCarouselWithUpload.ts | 22 ++-- UI/ImageUploadFlow.ts | 27 +++-- UI/MoreScreen.ts | 14 +-- UI/PendingChanges.ts | 20 ++-- UI/SearchAndGo.ts | 9 +- UI/ShareScreen.ts | 7 +- UI/SimpleAddUI.ts | 49 ++++---- UI/UserBadge.ts | 30 +++-- UI/WelcomeMessage.ts | 60 ++++++++++ UI/i18n/Locale.ts | 5 +- index.ts | 150 ++++++++----------------- 34 files changed, 532 insertions(+), 506 deletions(-) create mode 100644 State.ts create mode 100644 UI/WelcomeMessage.ts diff --git a/Customizations/Layers/Artwork.ts b/Customizations/Layers/Artwork.ts index df2e847..78c1e39 100644 --- a/Customizations/Layers/Artwork.ts +++ b/Customizations/Layers/Artwork.ts @@ -57,9 +57,9 @@ export class Artwork extends LayerDefinition { const artistQuestion = new TagRenderingOptions({ question: t.artist.question, freeform: { - key: "artist", + key: "artist_name", template: "$$$", - renderTemplate: "{artist}" + renderTemplate: "{artist_name}" } }); diff --git a/Customizations/Layers/Widths.ts b/Customizations/Layers/Widths.ts index ea8c84f..dc10b31 100644 --- a/Customizations/Layers/Widths.ts +++ b/Customizations/Layers/Widths.ts @@ -171,7 +171,7 @@ export class Widths extends LayerDefinition { return { icon: null, color: c, - weight: 10, + weight: 9, dashArray: dashArray } } @@ -259,15 +259,14 @@ export class Widths extends LayerDefinition { tags.targetWidth = r(props.targetWidth); tags.short = ""; if (props.width < props.targetWidth) { - tags.short = "Er is dus " + r(props.targetWidth - props.width) + "m te weinig" + tags.short = r(props.targetWidth - props.width) } }, - freeform: { - key: "width:carriageway", - renderTemplate: "De totale nodige ruimte voor vlot en veilig verkeer is dus {targetWidth}m
" + - "{short}", - template: "$$$", - } + mappings:[ + {k: new Tag("short","*"), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus {targetWidth}m
" + + "Er is dus {short}m te weinig", substitute: true}, + {k: new Tag("short",""), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus {targetWidth}m"} + ] } ).OnlyShowIf(this._notCarFree), diff --git a/Customizations/Layout.ts b/Customizations/Layout.ts index 8dad971..7081688 100644 --- a/Customizations/Layout.ts +++ b/Customizations/Layout.ts @@ -1,12 +1,6 @@ import {LayerDefinition} from "./LayerDefinition"; import {UIElement} from "../UI/UIElement"; -import {FixedUiElement} from "../UI/Base/FixedUiElement"; -import Translation from "../UI/i18n/Translation"; import Translations from "../UI/i18n/Translations"; -import Locale from "../UI/i18n/Locale"; -import {VariableUiElement} from "../UI/Base/VariableUIElement"; -import {UIEventSource} from "../UI/UIEventSource"; -import {OsmConnection, UserDetails} from "../Logic/Osm/OsmConnection"; /** * A layout is a collection of settings of the global view (thus: welcome text, title, selection of layers). @@ -83,59 +77,5 @@ export class Layout { this.welcomeBackMessage = Translations.W(welcomeBackMessage); this.welcomeTail = Translations.W(welcomeTail); } - - -} - -export class WelcomeMessage extends UIElement { - private readonly layout: Layout; - private readonly userDetails: UIEventSource; - private languagePicker: UIElement; - private osmConnection: OsmConnection; - - private readonly description: UIElement; - private readonly plzLogIn: UIElement; - private readonly welcomeBack: UIElement; - private readonly tail: UIElement; - - - constructor(layout: Layout, - languagePicker: UIElement, - osmConnection: OsmConnection) { - super(osmConnection?.userDetails); - this.languagePicker = languagePicker; - this.ListenTo(Locale.language); - this.osmConnection = osmConnection; - this.layout = layout; - this.userDetails = osmConnection?.userDetails; - - this.description = layout.welcomeMessage; - this.plzLogIn = layout.gettingStartedPlzLogin; - this.welcomeBack = layout.welcomeBackMessage; - this.tail = layout.welcomeTail; - } - - InnerRender(): string { - - let loginStatus = ""; - if (this.userDetails !== undefined) { - loginStatus = (this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render(); - loginStatus = loginStatus + "
" - } - - return "" + - this.description.Render() + - "
" + - loginStatus + - this.tail.Render() + - "
" + - this.languagePicker.Render() + - "
"; - } - - protected InnerUpdate(htmlElement: HTMLElement) { - this.osmConnection?.registerActivateOsmAUthenticationClass() - } - } diff --git a/Customizations/Layouts/Smoothness.ts b/Customizations/Layouts/Smoothness.ts index 6bf3dfc..1590f2d 100644 --- a/Customizations/Layouts/Smoothness.ts +++ b/Customizations/Layouts/Smoothness.ts @@ -11,6 +11,7 @@ export class SmoothnessLayer extends LayerDefinition { this.name = "smoothness"; this.minzoom = 17; this.overpassFilter = new Or([ + new Tag("highway","unclassified"), new Tag("highway", "residential"), new Tag("highway", "cycleway"), new Tag("highway", "footway"), diff --git a/Customizations/TagRendering.ts b/Customizations/TagRendering.ts index c31c04b..5b60904 100644 --- a/Customizations/TagRendering.ts +++ b/Customizations/TagRendering.ts @@ -5,7 +5,7 @@ import {FixedUiElement} from "../UI/Base/FixedUiElement"; import {SaveButton} from "../UI/SaveButton"; import {Changes} from "../Logic/Osm/Changes"; import {VariableUiElement} from "../UI/Base/VariableUIElement"; -import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor"; +import {Dependencies, TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor"; import {OnlyShowIfConstructor} from "./OnlyShowIf"; import {UserDetails} from "../Logic/Osm/OsmConnection"; import {TextField} from "../UI/Input/TextField"; @@ -17,6 +17,7 @@ import Translations from "../UI/i18n/Translations"; import Locale from "../UI/i18n/Locale"; import * as EmailValidator from 'email-validator'; import {parsePhoneNumberFromString} from 'libphonenumber-js' +import {State} from "../State"; export class TagRenderingOptions implements TagDependantUIElementConstructor { @@ -144,8 +145,8 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { } - construct(dependencies: { tags: UIEventSource, changes: Changes }): TagDependantUIElement { - return new TagRendering(dependencies.tags, dependencies.changes, this.options); + construct(dependencies: Dependencies): TagDependantUIElement { + return new TagRendering(dependencies.tags, this.options); } IsKnown(properties: any): boolean { @@ -161,7 +162,6 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor { class TagRendering extends UIElement implements TagDependantUIElement { - private _userDetails: UIEventSource; private _priority: number; @@ -189,7 +189,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { private readonly _editMode: UIEventSource = new UIEventSource(false); - constructor(tags: UIEventSource, changes: Changes, options: { + constructor(tags: UIEventSource, options: { priority?: number question?: string | UIElement, @@ -206,13 +206,12 @@ class TagRendering extends UIElement implements TagDependantUIElement { }) { super(tags); this.ListenTo(Locale.language); - const self = this; this.ListenTo(this._questionSkipped); this.ListenTo(this._editMode); + this.ListenTo(State.state.osmConnection.userDetails); - this._userDetails = changes.login.userDetails; - this.ListenTo(this._userDetails); + const self = this; this._priority = options.priority ?? 0; this._tagsPreprocessor = function (properties) { @@ -265,7 +264,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { const save = () => { const selection = self._questionElement.GetValue().data; if (selection) { - changes.addTag(tags.data.id, selection); + State.state.changes.addTag(tags.data.id, selection); } self._editMode.setData(false); } @@ -521,7 +520,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { } const html = answer.Render(); let editButton = ""; - if (this._userDetails.data.loggedIn && this._question !== undefined) { + if (State.state.osmConnection.userDetails.data.loggedIn && this._question !== undefined) { editButton = this._editButton.Render(); } diff --git a/Customizations/UIElementConstructor.ts b/Customizations/UIElementConstructor.ts index e171664..dfde608 100644 --- a/Customizations/UIElementConstructor.ts +++ b/Customizations/UIElementConstructor.ts @@ -3,9 +3,13 @@ import {Changes} from "../Logic/Osm/Changes"; import {UIElement} from "../UI/UIElement"; +export interface Dependencies { + tags: UIEventSource +} + export interface TagDependantUIElementConstructor { - construct(dependencies: {tags: UIEventSource, changes: Changes}): TagDependantUIElement; + construct(dependencies: Dependencies): TagDependantUIElement; IsKnown(properties: any): boolean; IsQuestioning(properties: any): boolean; Priority(): number; diff --git a/Helpers.ts b/Helpers.ts index 75336dd..2566c05 100644 --- a/Helpers.ts +++ b/Helpers.ts @@ -1,5 +1,6 @@ import {UIEventSource} from "./UI/UIEventSource"; import {Changes} from "./Logic/Osm/Changes"; +import {State} from "./State"; export class Helpers { @@ -13,9 +14,11 @@ export class Helpers { } - static SetupAutoSave(changes: Changes, millisTillChangesAreSaved: UIEventSource, saveAfterXMillis: number) { - + static SetupAutoSave() { + const changes = State.state.changes; + const millisTillChangesAreSaved = State.state.secondsTillChangesAreSaved; + const saveAfterXMillis = State.state.secondsTillChangesAreSaved.data * 1000; changes.pendingChangesES.addCallback(function () { var c = changes.pendingChangesES.data; @@ -53,7 +56,8 @@ export class Helpers { * -> Asks the user not to close. The 'not to close' dialog should profide enough time to upload * -> WHen uploading is done, the window is closed anyway */ - static LastEffortSave(changes: Changes) { + static LastEffortSave() { + const changes = State.state.changes; window.addEventListener("beforeunload", function (e) { // Quickly save everyting! if (changes.pendingChangesES.data == 0) { diff --git a/InitUiElements.ts b/InitUiElements.ts index 9eb596f..e2b043a 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -1,4 +1,4 @@ -import {Layout, WelcomeMessage} from "./Customizations/Layout"; +import {Layout} from "./Customizations/Layout"; import Locale from "./UI/i18n/Locale"; import Translations from "./UI/i18n/Translations"; import {TabbedComponent} from "./UI/Base/TabbedComponent"; @@ -17,6 +17,8 @@ import {Preset} from "./UI/SimpleAddUI"; import {Changes} from "./Logic/Osm/Changes"; import {OsmConnection} from "./Logic/Osm/OsmConnection"; import {Basemap} from "./Logic/Leaflet/Basemap"; +import {State} from "./State"; +import {WelcomeMessage} from "./UI/WelcomeMessage"; export class InitUiElements { @@ -36,17 +38,16 @@ export class InitUiElements { } - private static CreateWelcomePane(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap) { + private static CreateWelcomePane() { - const welcome = new WelcomeMessage(layoutToUse, - Locale.CreateLanguagePicker(layoutToUse, Translations.t.general.pickLanguage), - osmConnection) + const welcome = new WelcomeMessage() + const layoutToUse = State.state.layoutToUse.data; const fullOptions = new TabbedComponent([ {header: ``, content: welcome}, {header: ``, content: Translations.t.general.openStreetMapIntro}, - {header: ``, content: new ShareScreen(layoutToUse, bm.Location)}, - {header: ``, content: new MoreScreen(layoutToUse.name, bm.Location)} + {header: ``, content: new ShareScreen()}, + {header: ``, content: new MoreScreen()} ]) return fullOptions; @@ -54,10 +55,9 @@ export class InitUiElements { } - static InitWelcomeMessage(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap, - fullScreenMessage: UIEventSource) { + static InitWelcomeMessage() { - const fullOptions = this.CreateWelcomePane(layoutToUse, osmConnection, bm); + const fullOptions = this.CreateWelcomePane(); const help = new FixedUiElement(`
help
`); const close = new FixedUiElement(`
close
`); @@ -70,43 +70,39 @@ export class InitUiElements { , true ).AttachTo("messagesbox"); let dontCloseYet = true; - bm.Location.addCallback(() => { - if(dontCloseYet){ - dontCloseYet = false; + const openedTime = new Date().getTime(); + State.state.locationControl.addCallback(() => { + if (new Date().getTime() - openedTime < 15 * 1000) { + // Don't autoclose the first 15 secs return; } checkbox.isEnabled.setData(false); }) - const fullOptions2 = this.CreateWelcomePane(layoutToUse, osmConnection, bm); - fullScreenMessage.setData(fullOptions2) + const fullOptions2 = this.CreateWelcomePane(); + State.state.fullScreenMessage.setData(fullOptions2) new FixedUiElement(`
help
`).onClick(() => { - fullScreenMessage.setData(fullOptions2) + State.state.fullScreenMessage.setData(fullOptions2) }).AttachTo("help-button-mobile"); } - static InitLayers(layoutToUse: Layout, osmConnection: OsmConnection, - changes: Changes, - allElements: ElementStorage, - bm: Basemap, - fullScreenMessage: UIEventSource, - selectedElement: UIEventSource): { + static InitLayers(): { minZoom: number flayers: FilteredLayer[], presets: Preset[] } { - const addButtons:Preset[] + const addButtons: Preset[] = []; const flayers: FilteredLayer[] = [] let minZoom = 0; - - for (const layer of layoutToUse.layers) { + const state = State.state; + for (const layer of state.layoutToUse.data.layers) { const generateInfo = (tagsES, feature) => { @@ -115,14 +111,12 @@ export class InitUiElements { tagsES, layer.title, layer.elementsToShow, - changes, - osmConnection.userDetails ) }; minZoom = Math.max(minZoom, layer.minzoom); - const flayer = FilteredLayer.fromDefinition(layer, bm, allElements, changes, osmConnection.userDetails, selectedElement, generateInfo); + const flayer = FilteredLayer.fromDefinition(layer, generateInfo); for (const preset of layer.presets ?? []) { diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index b5fd592..e753269 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -9,6 +9,7 @@ import codegrid from "codegrid-js"; import {Changes} from "./Osm/Changes"; import {UserDetails} from "./Osm/OsmConnection"; import {Basemap} from "./Leaflet/Basemap"; +import {State} from "../State"; /*** * A filtered layer is a layer which offers a 'set-data' function @@ -25,12 +26,10 @@ export class FilteredLayer { public readonly filters: TagsFilter; public readonly isDisplayed: UIEventSource = new UIEventSource(true); public readonly layerDef: LayerDefinition; - private readonly _map: Basemap; private readonly _maxAllowedOverlap: number; private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize? : number[], popupAnchor?: number[], iconAnchor?:number[] } }; - private readonly _storage: ElementStorage; /** The featurecollection from overpass */ @@ -43,22 +42,17 @@ export class FilteredLayer { * The leaflet layer object which should be removed on rerendering */ private _geolayer; - private _selectedElement: UIEventSource<{ feature: any }>; private _showOnPopup: (tags: UIEventSource, feature: any) => UIElement; private static readonly grid = codegrid.CodeGrid(); constructor( layerDef: LayerDefinition, - map: Basemap, storage: ElementStorage, - changes: Changes, - selectedElement: UIEventSource, showOnPopup: ((tags: UIEventSource, feature: any) => UIElement) ) { this.layerDef = layerDef; this._wayHandling = layerDef.wayHandling; - this._selectedElement = selectedElement; this._showOnPopup = showOnPopup; this._style = layerDef.style; if (this._style === undefined) { @@ -67,33 +61,27 @@ export class FilteredLayer { } } this.name = name; - this._map = map; this.filters = layerDef.overpassFilter; - this._storage = storage; this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage; const self = this; this.isDisplayed.addCallback(function (isDisplayed) { + const map = State.state.bm.map; if (self._geolayer !== undefined && self._geolayer !== null) { if (isDisplayed) { - self._geolayer.addTo(self._map.map); + self._geolayer.addTo(map); } else { - self._map.map.removeLayer(self._geolayer); + map.removeLayer(self._geolayer); } } }) } static fromDefinition( - definition, - basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource, - selectedElement: UIEventSource<{feature: any}>, + definition, showOnPopup: (tags: UIEventSource, feature: any) => UIElement): FilteredLayer { return new FilteredLayer( - definition, - basemap, allElements, changes, - selectedElement, - showOnPopup); + definition, showOnPopup); } @@ -170,7 +158,7 @@ export class FilteredLayer { let self = this; if (this._geolayer !== undefined && this._geolayer !== null) { - this._map.map.removeLayer(this._geolayer); + State.state.bm.map.removeLayer(this._geolayer); } this._dataFromOverpass = data; const fusedFeatures = []; @@ -227,7 +215,7 @@ export class FilteredLayer { icon: new L.icon(style.icon), }); } - let eventSource = self._storage.addOrGetElement(feature); + let eventSource = State.state.allElements.addOrGetElement(feature); const uiElement = self._showOnPopup(eventSource, feature); const popup = L.popup({}, marker).setContent(uiElement.Render()); marker.bindPopup(popup) @@ -246,7 +234,7 @@ export class FilteredLayer { } else { self._geolayer.setStyle(function (feature) { const style = self._style(feature.properties); - if (self._selectedElement.data?.feature === feature) { + if (State.state.selectedElement.data?.feature === feature) { if (style.weight !== undefined) { style.weight = style.weight * 2; }else{ @@ -258,14 +246,14 @@ export class FilteredLayer { } } - let eventSource = self._storage.addOrGetElement(feature); + let eventSource = State.state.allElements.addOrGetElement(feature); eventSource.addCallback(feature.updateStyle); layer.on("click", function (e) { - const previousFeature = self._selectedElement.data?.feature; - self._selectedElement.setData({feature: feature}); + const previousFeature =State.state.selectedElement.data?.feature; + State.state.selectedElement.setData({feature: feature}); feature.updateStyle(); previousFeature?.updateStyle(); @@ -281,7 +269,7 @@ export class FilteredLayer { }) .setContent(uiElement.Render()) .setLatLng(e.latlng) - .openOn(self._map.map); + .openOn(State.state.bm.map); uiElement.Update(); uiElement.Activate(); L.DomEvent.stop(e); // Marks the event as consumed @@ -290,7 +278,7 @@ export class FilteredLayer { }); if (this.isDisplayed.data) { - this._geolayer.addTo(this._map.map); + this._geolayer.addTo(State.state.bm.map); } } diff --git a/Logic/ImageSearcher.ts b/Logic/ImageSearcher.ts index 900560c..b53a73d 100644 --- a/Logic/ImageSearcher.ts +++ b/Logic/ImageSearcher.ts @@ -5,6 +5,7 @@ import {SimpleImageElement} from "../UI/Image/SimpleImageElement"; import {UIElement} from "../UI/UIElement"; import {Changes} from "./Osm/Changes"; import {ImgurImage} from "../UI/Image/ImgurImage"; +import {State} from "../State"; /** * There are multiple way to fetch images for an object @@ -27,16 +28,13 @@ export class ImageSearcher extends UIEventSource { private readonly _wdItem = new UIEventSource(""); private readonly _commons = new UIEventSource(""); private _activated: boolean = false; - private _changes: Changes; public _deletedImages = new UIEventSource([]); - constructor(tags: UIEventSource, - changes: Changes) { + constructor(tags: UIEventSource) { super([]); this._tags = tags; - this._changes = changes; const self = this; this._wdItem.addCallback(() => { @@ -119,7 +117,7 @@ export class ImageSearcher extends UIEventSource { return; } console.log("Deleting image...", key, " --> ", url); - this._changes.addChange(this._tags.data.id, key, ""); + State.state.changes.addChange(this._tags.data.id, key, ""); this._deletedImages.data.push(url); this._deletedImages.ping(); } diff --git a/Logic/LayerUpdater.ts b/Logic/LayerUpdater.ts index 385923a..def77ac 100644 --- a/Logic/LayerUpdater.ts +++ b/Logic/LayerUpdater.ts @@ -4,9 +4,9 @@ import {FilteredLayer} from "./FilteredLayer"; import {Bounds} from "./Bounds"; import {Overpass} from "./Osm/Overpass"; import {Basemap} from "./Leaflet/Basemap"; +import {State} from "../State"; export class LayerUpdater { - private _map: Basemap; private _layers: FilteredLayer[]; private widenFactor: number; @@ -26,12 +26,10 @@ export class LayerUpdater { * @param minzoom * @param layers */ - constructor(map: Basemap, - minzoom: number, + constructor(minzoom: number, widenFactor: number, layers: FilteredLayer[]) { this.widenFactor = widenFactor; - this._map = map; this._layers = layers; this._minzoom = minzoom; var filters: TagsFilter[] = []; @@ -41,7 +39,7 @@ export class LayerUpdater { this._overpass = new Overpass(new Or(filters)); const self = this; - map.Location.addCallback(function () { + State.state.locationControl.addCallback(function () { self.update(); }); self.update(); @@ -67,9 +65,7 @@ export class LayerUpdater { renderLayers(rest); }, 50) } - renderLayers(this._layers); - } private handleFail(reason: any) { @@ -89,8 +85,8 @@ export class LayerUpdater { if (this.IsInBounds()) { return; } - console.log("Zoom level: ",this._map.map.getZoom(), "Least needed zoom:", this._minzoom) - if (this._map.map.getZoom() < this._minzoom || this._map.Location.data.zoom < this._minzoom) { + console.log("Zoom level: ",State.state.bm.map.getZoom(), "Least needed zoom:", this._minzoom) + if (State.state.bm.map.getZoom() < this._minzoom || State.state.bm.Location.data.zoom < this._minzoom) { return; } @@ -98,7 +94,7 @@ export class LayerUpdater { console.log("Still running a query, skip"); } - const bounds = this._map.map.getBounds(); + const bounds = State.state.bm.map.getBounds(); const diff = this.widenFactor; @@ -131,7 +127,7 @@ export class LayerUpdater { } - const b = this._map.map.getBounds(); + const b = State.state.bm.map.getBounds(); if (b.getSouth() < this.previousBounds.south) { return false; } diff --git a/Logic/Leaflet/GeoLocationHandler.ts b/Logic/Leaflet/GeoLocationHandler.ts index 06b483b..0df1746 100644 --- a/Logic/Leaflet/GeoLocationHandler.ts +++ b/Logic/Leaflet/GeoLocationHandler.ts @@ -1,25 +1,20 @@ -import {Basemap} from "./Basemap"; import L from "leaflet"; import {UIEventSource} from "../../UI/UIEventSource"; import {UIElement} from "../../UI/UIElement"; import {Helpers} from "../../Helpers"; +import {State} from "../../State"; export class GeoLocationHandler extends UIElement { - currentLocation: UIEventSource<{ - latlng: number, - accuracy: number - }> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined); - private _isActive: UIEventSource = new UIEventSource(false); private _permission: UIEventSource = new UIEventSource(""); - private _map: Basemap; private _marker: any; + private _hasLocation: UIEventSource; - constructor(map: Basemap) { + constructor() { super(undefined); - this._map = map; - this.ListenTo(this.currentLocation); + this._hasLocation = State.state.currentGPSLocation.map((location) => location !== undefined); + this.ListenTo(this._hasLocation); this.ListenTo(this._isActive); this.ListenTo(this._permission); @@ -29,23 +24,24 @@ export class GeoLocationHandler extends UIElement { function onAccuratePositionProgress(e) { console.log(e.accuracy); console.log(e.latlng); - self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); + State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); } function onAccuratePositionFound(e) { console.log(e.accuracy); console.log(e.latlng); - self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); + State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); } function onAccuratePositionError(e) { console.log("onerror", e.message); - + } - map.map.on('accuratepositionprogress', onAccuratePositionProgress); - map.map.on('accuratepositionfound', onAccuratePositionFound); - map.map.on('accuratepositionerror', onAccuratePositionError); + const map = State.state.bm.map; + map.on('accuratepositionprogress', onAccuratePositionProgress); + map.on('accuratepositionfound', onAccuratePositionFound); + map.on('accuratepositionerror', onAccuratePositionError); const icon = L.icon( @@ -55,12 +51,12 @@ export class GeoLocationHandler extends UIElement { iconAnchor: [20, 20], // point of the icon which will correspond to marker's location }) - this.currentLocation.addCallback((location) => { + State.state.currentGPSLocation.addCallback((location) => { const newMarker = L.marker(location.latlng, {icon: icon}); newMarker.addTo(map.map); if (self._marker !== undefined) { - map.map.removeLayer(self._marker); + map.removeLayer(self._marker); } self._marker = newMarker; }); @@ -81,7 +77,7 @@ export class GeoLocationHandler extends UIElement { } InnerRender(): string { - if (this.currentLocation.data) { + if (this._hasLocation.data) { return "locate me"; } if (this._isActive.data) { @@ -94,17 +90,17 @@ export class GeoLocationHandler extends UIElement { private StartGeolocating() { const self = this; - + const map = State.state.bm.map; if (self._permission.data === "denied") { return ""; } - if (self.currentLocation.data !== undefined) { - self._map.map.flyTo(self.currentLocation.data.latlng, 18); + if (State.state.currentGPSLocation.data !== undefined) { + map.flyTo(State.state.currentGPSLocation.data.latlng, 18); } console.log("Searching location using GPS") - self._map.map.findAccuratePosition({ + map.findAccuratePosition({ maxWait: 10000, // defaults to 10000 desiredAccuracy: 50 // defaults to 20 }); @@ -113,13 +109,13 @@ export class GeoLocationHandler extends UIElement { if (!self._isActive.data) { self._isActive.setData(true); Helpers.DoEvery(60000, () => { - - if(document.visibilityState !== "visible"){ + + if (document.visibilityState !== "visible") { console.log("Not starting gps: document not visible") return; } - - self._map.map.findAccuratePosition({ + + map.findAccuratePosition({ maxWait: 10000, // defaults to 10000 desiredAccuracy: 50 // defaults to 20 }); diff --git a/Logic/Leaflet/StrayClickHandler.ts b/Logic/Leaflet/StrayClickHandler.ts index 2ba9719..fefc181 100644 --- a/Logic/Leaflet/StrayClickHandler.ts +++ b/Logic/Leaflet/StrayClickHandler.ts @@ -2,29 +2,23 @@ import {Basemap} from "./Basemap"; import L from "leaflet"; import {UIEventSource} from "../../UI/UIEventSource"; import {UIElement} from "../../UI/UIElement"; +import {State} from "../../State"; /** * The stray-click-hanlders adds a marker to the map if no feature was clicked. * Shows the given uiToShow-element in the messagebox */ export class StrayClickHandler { - private _basemap: Basemap; private _lastMarker; - private _fullScreenMessage: UIEventSource; private _uiToShow: (() => UIElement); constructor( - basemap: Basemap, - selectElement: UIEventSource<{ feature: any }>, - fullScreenMessage: UIEventSource, uiToShow: (() => UIElement)) { - this._basemap = basemap; - this._fullScreenMessage = fullScreenMessage; this._uiToShow = uiToShow; const self = this; - const map = basemap.map; - basemap.LastClickLocation.addCallback(function (lastClick) { - selectElement.setData(undefined); + const map = State.state.bm.map; + State.state.bm.LastClickLocation.addCallback(function (lastClick) { + State.state.selectedElement.setData(undefined); if (self._lastMarker !== undefined) { map.removeLayer(self._lastMarker); @@ -32,9 +26,9 @@ export class StrayClickHandler { self._lastMarker = L.marker([lastClick.lat, lastClick.lon], { icon: L.icon({ iconUrl: "./assets/add.svg", - iconSize: [50,50], - iconAnchor: [25,50], - popupAnchor: [0,-45] + iconSize: [50, 50], + iconAnchor: [25, 50], + popupAnchor: [0, -45] }) }); const uiElement = uiToShow(); @@ -45,13 +39,13 @@ export class StrayClickHandler { self._lastMarker.bindPopup(popup).openPopup(); self._lastMarker.on("click", () => { - fullScreenMessage.setData(self._uiToShow()); + State.state.fullScreenMessage.setData(self._uiToShow()); }); uiElement.Update(); uiElement.Activate(); }); - selectElement.addCallback(() => { + State.state.selectedElement.addCallback(() => { if (self._lastMarker !== undefined) { map.removeLayer(self._lastMarker); this._lastMarker = undefined; diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 0ae51c3..ac83715 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -7,14 +7,12 @@ import {OsmConnection} from "./OsmConnection"; import {OsmNode, OsmObject} from "./OsmObject"; import {And, Tag, TagsFilter} from "../TagsFilter"; import {ElementStorage} from "../ElementStorage"; +import {State} from "../../State"; export class Changes { private static _nextId = -1; // New assined ID's are negative - public readonly login: OsmConnection; - public readonly _allElements: ElementStorage; - private _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll private newElements: OsmObject[] = []; // Gets reset on uploadAll @@ -27,8 +25,6 @@ export class Changes { login: OsmConnection, allElements: ElementStorage) { this._changesetComment = changesetComment; - this.login = login; - this._allElements = allElements; } addTag(elementId: string, tagsFilter : TagsFilter){ @@ -66,7 +62,7 @@ console.log("Received change",key, value) return; } - const eventSource = this._allElements.getElement(elementId); + const eventSource = State.state.allElements.getElement(elementId); eventSource.data[key] = value; eventSource.ping(); @@ -104,7 +100,7 @@ console.log("Received change",key, value) ] } } - this._allElements.addOrGetElement(geojson); + State.state.allElements.addOrGetElement(geojson); // The basictags are COPIED, the id is included in the properties // The tags are not yet written into the OsmObject, but this is applied onto a @@ -208,16 +204,16 @@ console.log("Received change",key, value) for (const oldId in idMapping) { const newId = idMapping[oldId]; - const element = self._allElements.getElement(oldId); + const element = State.state.allElements.getElement(oldId); element.data.id = newId; - self._allElements.addElementById(newId, element); + State.state.allElements.addElementById(newId, element); element.ping(); } } console.log("Beginning upload..."); // At last, we build the changeset and upload - self.login.UploadChangeset(self._changesetComment, + State.state.osmConnection.UploadChangeset(self._changesetComment, function (csId) { let modifications = ""; diff --git a/Logic/Osm/Geocoding.ts b/Logic/Osm/Geocoding.ts index ea11389..6fa5962 100644 --- a/Logic/Osm/Geocoding.ts +++ b/Logic/Osm/Geocoding.ts @@ -1,14 +1,14 @@ import {Basemap} from "../Leaflet/Basemap"; import $ from "jquery" +import {State} from "../../State"; export class Geocoding { private static readonly host = "https://nominatim.openstreetmap.org/search?"; static Search(query: string, - basemap: Basemap, handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void), onFail: (() => void)) { - const b = basemap.map.getBounds(); + const b = State.state.bm.map.getBounds(); console.log(b); $.getJSON( Geocoding.host + "format=json&limit=1&viewbox=" + diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 8d98cd6..423fcf1 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -10,7 +10,6 @@ export class UserDetails { public img: string; public unreadMessages = 0; public totalMessages = 0; - public osmConnection: OsmConnection; public dryRun: boolean; home: { lon: number; lat: number }; } @@ -23,24 +22,43 @@ export class OsmConnection { constructor(dryRun: boolean, oauth_token: UIEventSource) { - this.auth = new osmAuth({ - oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', - oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', - singlepage: true, - landing: window.location.href, - auto: true // show a login form if the user is not authenticated and - // you try to do a call + let pwaStandAloneMode = false; + try { - }); + if (window.matchMedia('(display-mode: standalone)').matches || window.matchMedia('(display-mode: fullscreen)').matches) { + pwaStandAloneMode = true; + } + } catch (e) { + console.warn("Detecting standalone mode failed", e, ". Assuming in browser and not worrying furhter") + } + + + if (pwaStandAloneMode) { + // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... + this.auth = new osmAuth({ + oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', + oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', + singlepage: false, + auto: true + }); + } else { + + this.auth = new osmAuth({ + oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', + oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', + singlepage: true, + landing: window.location.href, + auto: true + }); + } this.userDetails = new UIEventSource(new UserDetails()); - this.userDetails.data.osmConnection = this; this.userDetails.data.dryRun = dryRun; this._dryRun = dryRun; - if(oauth_token.data !== undefined){ + if (oauth_token.data !== undefined) { console.log(oauth_token.data) const self = this; this.auth.bootstrapToken(oauth_token.data, diff --git a/Logic/Osm/OsmImageUploadHandler.ts b/Logic/Osm/OsmImageUploadHandler.ts index 35940f5..935a97c 100644 --- a/Logic/Osm/OsmImageUploadHandler.ts +++ b/Logic/Osm/OsmImageUploadHandler.ts @@ -6,27 +6,19 @@ import {UIEventSource} from "../../UI/UIEventSource"; import {ImageUploadFlow} from "../../UI/ImageUploadFlow"; import {UserDetails} from "./OsmConnection"; import {SlideShow} from "../../UI/SlideShow"; +import {State} from "../../State"; export class OsmImageUploadHandler { private _tags: UIEventSource; - private _changeHandler: Changes; - private _userdetails: UIEventSource; private _slideShow: SlideShow; private _preferedLicense: UIEventSource; constructor(tags: UIEventSource, - userdetails: UIEventSource, preferedLicense: UIEventSource, - changeHandler: Changes, slideShow : SlideShow ) { this._slideShow = slideShow; // To move the slideshow (if any) to the last, just added element - if (tags === undefined || userdetails === undefined || changeHandler === undefined) { - throw "Something is undefined" - } this._tags = tags; - this._changeHandler = changeHandler; - this._userdetails = userdetails; this._preferedLicense = preferedLicense; } @@ -36,14 +28,14 @@ export class OsmImageUploadHandler { const title = tags.name ?? "Unknown area"; const description = [ - "author:" + this._userdetails.data.name, + "author:" + State.state.osmConnection.userDetails.data.name, "license:" + license, "wikidata:" + tags.wikidata, "osmid:" + tags.id, "name:" + tags.name ].join("\n"); - const changes = this._changeHandler; + const changes = State.state.changes; return { title: title, description: description, @@ -73,7 +65,6 @@ export class OsmImageUploadHandler { getUI(): ImageUploadFlow { const self = this; return new ImageUploadFlow( - this._userdetails, this._preferedLicense, function (license) { return self.generateOptions(license) diff --git a/Logic/QueryParameters.ts b/Logic/QueryParameters.ts index cc244da..93b5765 100644 --- a/Logic/QueryParameters.ts +++ b/Logic/QueryParameters.ts @@ -51,7 +51,10 @@ export class QueryParameters { } public static GetQueryParameter(key: string, deflt: string): UIEventSource { - QueryParameters.defaults[key] = deflt; + if (deflt !== undefined) { + console.log(key, "-->", deflt) + QueryParameters.defaults[key] = deflt; + } if (QueryParameters.knownSources[key] !== undefined) { return QueryParameters.knownSources[key]; } diff --git a/State.ts b/State.ts new file mode 100644 index 0000000..959c07e --- /dev/null +++ b/State.ts @@ -0,0 +1,134 @@ +import {UIEventSource} from "./UI/UIEventSource"; +import {UIElement} from "./UI/UIElement"; +import {QueryParameters} from "./Logic/QueryParameters"; +import {LocalStorageSource} from "./Logic/LocalStorageSource"; +import {Layout} from "./Customizations/Layout"; +import {Utils} from "./Utils"; +import {LayerDefinition} from "./Customizations/LayerDefinition"; +import {ElementStorage} from "./Logic/ElementStorage"; +import {Changes} from "./Logic/Osm/Changes"; +import {Basemap} from "./Logic/Leaflet/Basemap"; +import {OsmConnection} from "./Logic/Osm/OsmConnection"; + +/** + * Contains the global state: a bunch of UI-event sources + */ + +export class State { + + // The singleton of the global state + public static state: State; + + + /** + THe layout to use + */ + public readonly layoutToUse = new UIEventSource(undefined); + + /** + The mapping from id -> UIEventSource + */ + public allElements: ElementStorage; + /** + THe change handler + */ + public changes: Changes; + /** + THe basemap with leaflet instance + */ + public bm: Basemap; + /** + The user crednetials + */ + public osmConnection: OsmConnection; + + /** + * 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; + + + /** + * 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); + + + 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); + + + } +} \ No newline at end of file diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index 833a46b..d82a14b 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -2,75 +2,60 @@ import {UIElement} from "./UIElement"; import {UIEventSource} from "./UIEventSource"; import {OsmConnection} from "../Logic/Osm/OsmConnection"; import Translations from "./i18n/Translations"; +import {State} from "../State"; export class CenterMessageBox extends UIElement { - private readonly _location: UIEventSource<{ zoom: number }>; - private readonly _zoomInMore = new UIEventSource(true); - private readonly _centermessage: UIEventSource; - private readonly _osmConnection: OsmConnection; private readonly _queryRunning: UIEventSource; + private startZoom: number; constructor( startZoom: number, - centermessage: UIEventSource, - osmConnection: OsmConnection, - location: UIEventSource<{ zoom: number }>, queryRunning: UIEventSource ) { - super(centermessage); + super(State.state.centerMessage); + this.startZoom = startZoom; - this._centermessage = centermessage; - this._location = location; - this._osmConnection = osmConnection; - this._queryRunning = queryRunning; + this.ListenTo(State.state.locationControl); this.ListenTo(queryRunning); + this._queryRunning = queryRunning; - const self = this; - location.addCallback(function () { - self._zoomInMore.setData(location.data.zoom < startZoom); - }); - this.ListenTo(this._zoomInMore); } + private prep(): { innerHtml: string, done: boolean } { + if (State.state.centerMessage.data != "") { + return {innerHtml: State.state.centerMessage.data, done: false}; + } + if (this._queryRunning.data) { + return {innerHtml: Translations.t.centerMessage.loadingData.Render(), done: false}; + } else if (State.state.locationControl.data.zoom < this.startZoom) { + return {innerHtml: Translations.t.centerMessage.zoomIn.Render(), done: false}; + } else { + return {innerHtml: Translations.t.centerMessage.ready.Render(), done: true}; + } + } + InnerRender(): string { - - if (this._centermessage.data != "") { - return this._centermessage.data; - } - if (this._queryRunning.data) { - return Translations.t.centerMessage.loadingData.Render(); - } else if (this._zoomInMore.data) { - return Translations.t.centerMessage.zoomIn.Render(); - } - return Translations.t.centerMessage.ready.Render(); + return this.prep().innerHtml; } - private ShouldShowSomething() : boolean{ - if (this._queryRunning.data) { - return true; - } - return this._zoomInMore.data; - } - InnerUpdate(htmlElement: HTMLElement) { const pstyle = htmlElement.parentElement.style; - if (this._centermessage.data != "") { + if (State.state.centerMessage.data != "") { pstyle.opacity = "1"; pstyle.pointerEvents = "all"; - this._osmConnection.registerActivateOsmAUthenticationClass(); + State.state.osmConnection.registerActivateOsmAUthenticationClass(); return; } pstyle.pointerEvents = "none"; - - if (this.ShouldShowSomething()) { - pstyle.opacity = "0.5"; - } else { + if (this.prep().done) { pstyle.opacity = "0"; + } else { + pstyle.opacity = "0.5"; } } diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index ca4aab7..933eb97 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -11,6 +11,7 @@ import Translations from "./i18n/Translations"; import {Changes} from "../Logic/Osm/Changes"; import {UserDetails} from "../Logic/Osm/OsmConnection"; import {FixedUiElement} from "./Base/FixedUiElement"; +import {State} from "../State"; export class FeatureInfoBox extends UIElement { @@ -23,14 +24,11 @@ export class FeatureInfoBox extends UIElement { */ private _tagsES: UIEventSource; private _changes: Changes; - private _userDetails: UIEventSource; - private _title: UIElement; private _osmLink: UIElement; private _wikipedialink: UIElement; - private _infoboxes: TagDependantUIElement[]; private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone(); @@ -41,15 +39,11 @@ export class FeatureInfoBox extends UIElement { tagsES: UIEventSource, title: TagRenderingOptions | UIElement | string, elementsToShow: TagDependantUIElementConstructor[], - changes: Changes, - userDetails: UIEventSource ) { super(tagsES); this._feature = feature; this._tagsES = tagsES; - this._changes = changes; - this._userDetails = userDetails; - this.ListenTo(userDetails); + this.ListenTo(State.state.osmConnection.userDetails); const deps = {tags: this._tagsES, changes: this._changes} @@ -112,7 +106,7 @@ export class FeatureInfoBox extends UIElement { let questionsHtml = ""; - if (this._userDetails.data.loggedIn && questions.length > 0) { + if (State.state.osmConnection.userDetails.data.loggedIn && questions.length > 0) { // We select the most important question and render that one let mostImportantQuestion; let score = -1000; diff --git a/UI/FullScreenMessageBoxHandler.ts b/UI/FullScreenMessageBoxHandler.ts index bb6e08d..1f0a97b 100644 --- a/UI/FullScreenMessageBoxHandler.ts +++ b/UI/FullScreenMessageBoxHandler.ts @@ -2,25 +2,29 @@ import {UIEventSource} from "./UIEventSource"; import {UIElement} from "./UIElement"; import {VariableUiElement} from "./Base/VariableUIElement"; import Translations from "./i18n/Translations"; +import {State} from "../State"; /** * Handles the full screen popup on mobile */ export class FullScreenMessageBoxHandler { - + private _uielement: UIEventSource; - constructor(uielement: UIEventSource, - onClear: (() => void)) { - this._uielement = uielement; - this.listenTo(uielement); + constructor(onClear: (() => void)) { + this._uielement = State.state.fullScreenMessage; + const self = this; + this._uielement.addCallback(function () { + self.update(); + }); + this.update(); if (window !== undefined) { window.onhashchange = function () { if (location.hash === "") { // No more element: back to the map! - uielement.setData(undefined); + self._uielement.setData(undefined); onClear(); } } @@ -28,7 +32,7 @@ export class FullScreenMessageBoxHandler { Translations.t.general.returnToTheMap .onClick(() => { - uielement.setData(undefined); + self._uielement.setData(undefined); onClear(); }) .AttachTo("to-the-map"); @@ -36,13 +40,6 @@ export class FullScreenMessageBoxHandler { } - listenTo(uiEventSource: UIEventSource) { - const self = this; - uiEventSource.addCallback(function () { - self.update(); - }) - } - update() { const wrapper = document.getElementById("messagesboxmobilewrapper"); diff --git a/UI/Image/ImageCarousel.ts b/UI/Image/ImageCarousel.ts index e69fa3a..dc2033c 100644 --- a/UI/Image/ImageCarousel.ts +++ b/UI/Image/ImageCarousel.ts @@ -3,12 +3,15 @@ import {ImageSearcher} from "../../Logic/ImageSearcher"; import {UIEventSource} from "../UIEventSource"; import {SlideShow} from "../SlideShow"; import {FixedUiElement} from "../Base/FixedUiElement"; -import {VerticalCombine} from "../Base/VerticalCombine"; import {VariableUiElement} from "../Base/VariableUIElement"; import {ConfirmDialog} from "../ConfirmDialog"; -import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor"; +import { + Dependencies, + TagDependantUIElement, + TagDependantUIElementConstructor +} from "../../Customizations/UIElementConstructor"; import {Changes} from "../../Logic/Osm/Changes"; -import {UserDetails} from "../../Logic/Osm/OsmConnection"; +import {State} from "../../State"; export class ImageCarouselConstructor implements TagDependantUIElementConstructor{ IsKnown(properties: any): boolean { @@ -23,8 +26,8 @@ export class ImageCarouselConstructor implements TagDependantUIElementConstructo return 0; } - construct(dependencies: { tags: UIEventSource, changes: Changes }): TagDependantUIElement { - return new ImageCarousel(dependencies.tags, dependencies.changes); + construct(dependencies: Dependencies): TagDependantUIElement { + return new ImageCarousel(dependencies.tags); } } @@ -41,14 +44,11 @@ export class ImageCarousel extends TagDependantUIElement { private readonly _deleteButton: UIElement; private readonly _isDeleted: UIElement; - private readonly _userDetails : UIEventSource; - - constructor(tags: UIEventSource, changes: Changes) { + constructor(tags: UIEventSource) { super(tags); - this._userDetails = changes.login.userDetails; const self = this; - this.searcher = new ImageSearcher(tags, changes); + this.searcher = new ImageSearcher(tags); this._uiElements = this.searcher.map((imageURLS: string[]) => { const uiElements: UIElement[] = []; @@ -65,11 +65,11 @@ export class ImageCarousel extends TagDependantUIElement { const showDeleteButton = this.slideshow._currentSlide.map((i) => { - if(!self._userDetails.data.loggedIn){ + if(!State.state.osmConnection.userDetails.data.loggedIn){ return false; } return self.searcher.IsDeletable(self.searcher.data[i]); - }, [this.searcher, this._userDetails]); + }, [this.searcher, State.state.osmConnection.userDetails]); this.slideshow._currentSlide.addCallback(() => { showDeleteButton.ping(); // This pings the showDeleteButton, which indicates that it has to hide it's subbuttons }) diff --git a/UI/Image/ImageCarouselWithUpload.ts b/UI/Image/ImageCarouselWithUpload.ts index dcf2a7f..667206e 100644 --- a/UI/Image/ImageCarouselWithUpload.ts +++ b/UI/Image/ImageCarouselWithUpload.ts @@ -1,9 +1,12 @@ -import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor"; +import { + Dependencies, + TagDependantUIElement, + TagDependantUIElementConstructor +} from "../../Customizations/UIElementConstructor"; import {ImageCarousel} from "./ImageCarousel"; -import {UIEventSource} from "../UIEventSource"; import {ImageUploadFlow} from "../ImageUploadFlow"; -import {Changes} from "../../Logic/Osm/Changes"; import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler"; +import {State} from "../../State"; export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{ IsKnown(properties: any): boolean { @@ -27,16 +30,13 @@ class ImageCarouselWithUpload extends TagDependantUIElement { private _imageElement: ImageCarousel; private _pictureUploader: ImageUploadFlow; - constructor(dependencies: {tags: UIEventSource, changes: Changes}) { + constructor(dependencies: Dependencies) { super(dependencies.tags); const tags = dependencies.tags; - const changes = dependencies.changes; - this._imageElement = new ImageCarousel(tags, changes); - const userDetails = changes.login.userDetails; - const license = changes.login.GetPreference( "pictures-license"); - this._pictureUploader = new OsmImageUploadHandler(tags, - userDetails, license, - changes, this._imageElement.slideshow).getUI(); + this._imageElement = new ImageCarousel(tags); + const userDetails = State.state.osmConnection.userDetails; + const license = State.state.osmConnection.GetPreference( "pictures-license"); + this._pictureUploader = new OsmImageUploadHandler(tags, license, this._imageElement.slideshow).getUI(); } diff --git a/UI/ImageUploadFlow.ts b/UI/ImageUploadFlow.ts index 582b1a7..7f1b340 100644 --- a/UI/ImageUploadFlow.ts +++ b/UI/ImageUploadFlow.ts @@ -9,6 +9,7 @@ import Translations from "./i18n/Translations"; import {fail} from "assert"; import Combine from "./Base/Combine"; import {VerticalCombine} from "./Base/VerticalCombine"; +import {State} from "../State"; export class ImageUploadFlow extends UIElement { private _licensePicker: UIElement; @@ -17,10 +18,8 @@ export class ImageUploadFlow extends UIElement { private _didFail: UIEventSource = new UIEventSource(false); private _allDone: UIEventSource = new UIEventSource(false); private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) }; - private _userdetails: UIEventSource; constructor( - userInfo: UIEventSource, preferedLicense: UIEventSource, uploadOptions: ((license: string) => { @@ -30,9 +29,7 @@ export class ImageUploadFlow extends UIElement { allDone: (() => void) }) ) { - super(undefined); - this._userdetails = userInfo; - this.ListenTo(userInfo); + super(State.state.osmConnection.userDetails); this._uploadOptions = uploadOptions; this.ListenTo(this._isUploading); this.ListenTo(this._didFail); @@ -56,11 +53,11 @@ export class ImageUploadFlow extends UIElement { InnerRender(): string { const t = Translations.t.image; - if (this._userdetails === undefined) { + if (State.state.osmConnection.userDetails === undefined) { return ""; // No user details -> logging in is probably disabled or smthing } - if (!this._userdetails.data.loggedIn) { + if (!State.state.osmConnection.userDetails.data.loggedIn) { return `
${t.pleaseLogin.Render()}
`; } @@ -79,6 +76,16 @@ export class ImageUploadFlow extends UIElement { currentState.push(t.uploadDone) } + let currentStateHtml = ""; + if (currentState.length > 0) { + currentStateHtml = new VerticalCombine(currentState).Render(); + if (!this._allDone.data) { + currentStateHtml = "" + + currentStateHtml + + ""; + } + } + return "" + "
" + @@ -89,9 +96,9 @@ export class ImageUploadFlow extends UIElement { `${Translations.t.image.addPicture.R()}` + "
" + "
" + + currentStateHtml + Translations.t.image.respectPrivacy.Render() + "
" + this._licensePicker.Render() + "
" + - new VerticalCombine(currentState).Render() + "" + "
" + "; - private currentLayout: string; - constructor(currentLayout: string, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) { - super(currentLocation); - this.currentLayout = currentLayout; - this.currentLocation = currentLocation; + constructor() { + super(State.state.locationControl); } InnerRender(): string { @@ -30,12 +27,13 @@ export class MoreScreen extends UIElement { if (layout.hideFromOverview) { continue } - if (layout.name === this.currentLayout) { + if (layout.name === State.state.layoutToUse.data.name) { continue; } + const currentLocation = State.state.locationControl.data; const linkText = - `https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${this.currentLocation.data.zoom}&lat=${this.currentLocation.data.lat}&lon=${this.currentLocation.data.lon}` + `https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}` const link = new SubtleButton(layout.icon, new Combine([ diff --git a/UI/PendingChanges.ts b/UI/PendingChanges.ts index 384f7c7..1c6c981 100644 --- a/UI/PendingChanges.ts +++ b/UI/PendingChanges.ts @@ -1,23 +1,21 @@ import {UIElement} from "./UIElement"; import {UIEventSource} from "./UIEventSource"; import {Changes} from "../Logic/Osm/Changes"; +import {State} from "../State"; export class PendingChanges extends UIElement { private _pendingChangesCount: UIEventSource; - private _countdown: UIEventSource; private _isSaving: UIEventSource; - constructor(changes: Changes, - countdown: UIEventSource) { - super(changes.pendingChangesES); - this.ListenTo(changes.isSaving); - this.ListenTo(countdown); - this._pendingChangesCount = changes.pendingChangesES; - this._countdown = countdown; - this._isSaving = changes.isSaving; + constructor() { + super(State.state.changes.pendingChangesES); + this.ListenTo(State.state.changes.isSaving); + this.ListenTo(State.state.secondsTillChangesAreSaved); + this._pendingChangesCount = State.state.changes.pendingChangesES; + this._isSaving = State.state.changes.isSaving; this.onClick(() => { - changes.uploadAll(); + State.state.changes.uploadAll(); }) } @@ -29,7 +27,7 @@ export class PendingChanges extends UIElement { return ""; } - var restingSeconds = this._countdown.data / 1000; + var restingSeconds =State.state.secondsTillChangesAreSaved.data / 1000; var dots = ""; while (restingSeconds > 0) { dots += "."; diff --git a/UI/SearchAndGo.ts b/UI/SearchAndGo.ts index 0e0a574..23d9ee5 100644 --- a/UI/SearchAndGo.ts +++ b/UI/SearchAndGo.ts @@ -8,6 +8,7 @@ import {TextField} from "./Input/TextField"; import {Geocoding} from "../Logic/Osm/Geocoding"; import Translations from "./i18n/Translations"; import {Basemap} from "../Logic/Leaflet/Basemap"; +import {State} from "../State"; export class SearchAndGo extends UIElement { @@ -23,12 +24,10 @@ export class SearchAndGo extends UIElement { ); private _foundEntries = new UIEventSource([]); - private _map: Basemap; private _goButton = new FixedUiElement("GO"); - constructor(map: Basemap) { + constructor() { super(undefined); - this._map = map; this.ListenTo(this._foundEntries); const self = this; @@ -48,7 +47,7 @@ export class SearchAndGo extends UIElement { this._searchField.Clear(); this._placeholder.setData(Translations.t.general.search.searching); const self = this; - Geocoding.Search(searchString, this._map, (result) => { + Geocoding.Search(searchString, (result) => { if (result.length == 0) { this._placeholder.setData(Translations.t.general.search.nothing); @@ -60,7 +59,7 @@ export class SearchAndGo extends UIElement { [bb[0], bb[2]], [bb[1], bb[3]] ] - self._map.map.fitBounds(bounds); + State.state.bm.map.fitBounds(bounds); this._placeholder.setData(Translations.t.general.search.search); }, () => { diff --git a/UI/ShareScreen.ts b/UI/ShareScreen.ts index 591a3f3..8b6e2b1 100644 --- a/UI/ShareScreen.ts +++ b/UI/ShareScreen.ts @@ -9,6 +9,7 @@ import {CheckBox} from "./Input/CheckBox"; import {VerticalCombine} from "./Base/VerticalCombine"; import {QueryParameters} from "../Logic/QueryParameters"; import {Img} from "./Img"; +import {State} from "../State"; export class ShareScreen extends UIElement { @@ -19,7 +20,7 @@ export class ShareScreen extends UIElement { private _link: UIElement; private _linkStatus: UIElement; - constructor(layout: Layout, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) { + constructor() { super(undefined) const tr = Translations.t.general.sharescreen; @@ -32,6 +33,10 @@ export class ShareScreen extends UIElement { true ) optionCheckboxes.push(includeLocation); + + const currentLocation = State.state.locationControl; + const layout = State.state.layoutToUse.data; + optionParts.push(includeLocation.isEnabled.map((includeL) => { if (includeL) { return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}` diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index ecb24af..c16bb4e 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -11,6 +11,7 @@ import {VerticalCombine} from "./Base/VerticalCombine"; import Locale from "./i18n/Locale"; import {Changes} from "../Logic/Osm/Changes"; import {UserDetails} from "../Logic/Osm/OsmConnection"; +import {State} from "../State"; export interface Preset { description: string | UIElement, @@ -24,13 +25,9 @@ export interface Preset { * Asks to add a feature at the last clicked location, at least if zoom is sufficient */ export class SimpleAddUI extends UIElement { - private _zoomlevel: UIEventSource<{ zoom: number }>; private _addButtons: UIElement[]; - private _lastClickLocation: UIEventSource<{ lat: number; lon: number }>; - private _changes: Changes; - private _selectedElement: UIEventSource<{ feature: any }>; + private _dataIsLoading: UIEventSource; - private _userDetails: UIEventSource; private _confirmPreset: UIEventSource = new UIEventSource(undefined); @@ -39,24 +36,17 @@ export class SimpleAddUI extends UIElement { private goToInboxButton: UIElement = new SubtleButton("./assets/envelope.svg", Translations.t.general.goToInbox, {url:"https://www.openstreetmap.org/messages/inbox", newTab: false}); - constructor(zoomlevel: UIEventSource<{ zoom: number }>, - lastClickLocation: UIEventSource<{ lat: number, lon: number }>, - changes: Changes, - selectedElement: UIEventSource<{ feature: any }>, + constructor( dataIsLoading: UIEventSource, - userDetails: UIEventSource, addButtons: { description: string | UIElement, name: string | UIElement; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[], ) { - super(zoomlevel); + super(State.state.locationControl); this.ListenTo(Locale.language); - this._zoomlevel = zoomlevel; - this._lastClickLocation = lastClickLocation; - this._changes = changes; - this._selectedElement = selectedElement; + this.ListenTo(State.state.osmConnection.userDetails); + this._dataIsLoading = dataIsLoading; - this._userDetails = userDetails; - this.ListenTo(userDetails); this.ListenTo(dataIsLoading); + this._addButtons = []; this.ListenTo(this._confirmPreset); this.clss = "add-ui" @@ -102,26 +92,27 @@ export class SimpleAddUI extends UIElement { const self = this; return () => { - const loc = self._lastClickLocation.data; - let feature = self._changes.createElement(option.tags, loc.lat, loc.lon); + const loc = State.state.bm.lastClickLocation.data; + let feature = State.state.changes.createElement(option.tags, loc.lat, loc.lon); option.layerToAddTo.AddNewElement(feature); - self._selectedElement.setData({feature: feature}); + State.state.selectedElement.setData({feature: feature}); } } InnerRender(): string { + const userDetails = State.state.osmConnection.userDetails; if (this._confirmPreset.data !== undefined) { - if(this._userDetails.data.dryRun){ + if(userDetails.data.dryRun){ this.CreatePoint(this._confirmPreset.data)(); return; } return new Combine([ Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}), - this._userDetails.data.dryRun ? "TESTING - changes won't be saved":"", + userDetails.data.dryRun ? "TESTING - changes won't be saved":"", this.confirmButton, this.cancelButton @@ -134,15 +125,15 @@ export class SimpleAddUI extends UIElement { let header: UIElement = Translations.t.general.add.header; - if(this._userDetails === undefined){ + if(userDetails === undefined){ return header.Render(); } - if (!this._userDetails.data.loggedIn) { + if (!userDetails.data.loggedIn) { return new Combine([header, Translations.t.general.add.pleaseLogin]).Render() } - if (this._userDetails.data.unreadMessages > 0) { + if (userDetails.data.unreadMessages > 0) { return new Combine([header, "", Translations.t.general.readYourMessages, "", @@ -150,7 +141,7 @@ export class SimpleAddUI extends UIElement { ]).Render(); } - if (this._userDetails.data.dryRun) { + if (userDetails.data.dryRun) { header = new Combine([header, "", "Test mode - changes won't be saved", @@ -158,13 +149,13 @@ export class SimpleAddUI extends UIElement { ]); } - if (this._userDetails.data.csCount < 5) { + if (userDetails.data.csCount < 5) { return new Combine([header, "", Translations.t.general.fewChangesBefore, ""]).Render(); } - if (this._zoomlevel.data.zoom < 19) { + if (State.state.locationControl.data.zoom < 19) { return new Combine([header, Translations.t.general.add.zoomInFurther]).Render() } @@ -183,7 +174,7 @@ export class SimpleAddUI extends UIElement { } InnerUpdate(htmlElement: HTMLElement) { - this._userDetails.data.osmConnection.registerActivateOsmAUthenticationClass(); + State.state.osmConnection.registerActivateOsmAUthenticationClass(); } } \ No newline at end of file diff --git a/UI/UserBadge.ts b/UI/UserBadge.ts index 3438f5b..0b44bf9 100644 --- a/UI/UserBadge.ts +++ b/UI/UserBadge.ts @@ -6,6 +6,9 @@ import {VariableUiElement} from "./Base/VariableUIElement"; import Translations from "./i18n/Translations"; import {UserDetails} from "../Logic/Osm/OsmConnection"; import {Basemap} from "../Logic/Leaflet/Basemap"; +import {State} from "../State"; +import {PendingChanges} from "./PendingChanges"; +import Locale from "./i18n/Locale"; /** * Handles and updates the user badge @@ -14,27 +17,22 @@ export class UserBadge extends UIElement { private _userDetails: UIEventSource; private _pendingChanges: UIElement; private _logout: UIElement; - private _basemap: Basemap; private _homeButton: UIElement; private _languagePicker: UIElement; - constructor(userDetails: UIEventSource, - pendingChanges: UIElement, - languagePicker: UIElement, - basemap: Basemap) { - super(userDetails); - this._userDetails = userDetails; - this._pendingChanges = pendingChanges; - this._basemap = basemap; - this._languagePicker = languagePicker; + constructor() { + super(State.state.osmConnection.userDetails); + this._userDetails = State.state.osmConnection.userDetails; + this._pendingChanges = new PendingChanges(); + this._languagePicker = Locale.CreateLanguagePicker(); this._logout = new FixedUiElement("logout") .onClick(() => { - userDetails.data.osmConnection.LogOut(); + State.state.osmConnection.LogOut(); }); - userDetails.addCallback(function () { + this._userDetails.addCallback(function () { const profilePic = document.getElementById("profile-pic"); if (profilePic) { @@ -45,18 +43,18 @@ export class UserBadge extends UIElement { }); this._homeButton = new VariableUiElement( - userDetails.map((userinfo) => { + this._userDetails.map((userinfo) => { if (userinfo.home) { return "home "; } return ""; }) ).onClick(() => { - const home = userDetails.data?.home; + const home = State.state.osmConnection.userDetails.data?.home; if (home === undefined) { return; } - basemap.map.flyTo([home.lat, home.lon], 18); + State.state.bm.map.flyTo([home.lat, home.lon], 18); }); } @@ -91,7 +89,7 @@ export class UserBadge extends UIElement { iconSize: [20, 20], iconAnchor: [10, 10] }); - L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(this._basemap.map); + L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(State.state.bm.map); } const settings = diff --git a/UI/WelcomeMessage.ts b/UI/WelcomeMessage.ts new file mode 100644 index 0000000..91dca1c --- /dev/null +++ b/UI/WelcomeMessage.ts @@ -0,0 +1,60 @@ +import {UIElement} from "../UI/UIElement"; +import {UIEventSource} from "../UI/UIEventSource"; +import {OsmConnection, UserDetails} from "../Logic/Osm/OsmConnection"; +import Locale from "../UI/i18n/Locale"; +import {State} from "../State"; +import {Layout} from "../Customizations/Layout"; +import Translations from "./i18n/Translations"; +import {VariableUiElement} from "./Base/VariableUIElement"; + +export class WelcomeMessage extends UIElement { + private readonly layout: Layout; + private languagePicker: UIElement; + private osmConnection: OsmConnection; + + private readonly description: UIElement; + private readonly plzLogIn: UIElement; + private readonly welcomeBack: UIElement; + private readonly tail: UIElement; + + + constructor() { + super(State.state.osmConnection.userDetails); + this.languagePicker = Locale.CreateLanguagePicker(Translations.t.general.pickLanguage); + this.ListenTo(Locale.language); + + function fromLayout(f: (layout: Layout) => (string | UIElement)): UIElement { + return new VariableUiElement( + State.state.layoutToUse.map((layout) => Translations.W(f(layout)).Render()) + ) + } + + this.description = fromLayout((layout) => layout.welcomeMessage); + this.plzLogIn = fromLayout((layout) => layout.gettingStartedPlzLogin); + this.welcomeBack = fromLayout((layout) => layout.welcomeBackMessage); + this.tail = fromLayout((layout) => layout.welcomeTail); + } + + InnerRender(): string { + + let loginStatus = ""; + if (State.state.featureSwitchUserbadge.data) { + loginStatus = (State.state.osmConnection.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render(); + loginStatus = loginStatus + "
" + } + + return "" + + this.description.Render() + + "
" + + loginStatus + + this.tail.Render() + + "
" + + this.languagePicker.Render() + + "
"; + } + + protected InnerUpdate(htmlElement: HTMLElement) { + this.osmConnection?.registerActivateOsmAUthenticationClass() + } + +} \ No newline at end of file diff --git a/UI/i18n/Locale.ts b/UI/i18n/Locale.ts index cea0aa0..b2b5019 100644 --- a/UI/i18n/Locale.ts +++ b/UI/i18n/Locale.ts @@ -3,14 +3,15 @@ import {LocalStorageSource} from "../../Logic/LocalStorageSource"; import {DropDown} from "../Input/DropDown"; import {Layout} from "../../Customizations/Layout"; import {UIElement} from "../UIElement"; +import {State} from "../../State"; export default class Locale { public static language: UIEventSource = LocalStorageSource.Get('language', "en"); - public static CreateLanguagePicker(layoutToUse: Layout, label: string | UIElement = "") { + public static CreateLanguagePicker(label: string | UIElement = "") { - return new DropDown(label, layoutToUse.supportedLanguages.map(lang => { + return new DropDown(label, State.state.layoutToUse.data.supportedLanguages.map(lang => { return {value: lang, shown: lang} } ), Locale.language); diff --git a/index.ts b/index.ts index 7a2e8b1..8992340 100644 --- a/index.ts +++ b/index.ts @@ -31,6 +31,7 @@ import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap"; import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler"; import {OsmConnection} from "./Logic/Osm/OsmConnection"; import {Changes} from "./Logic/Osm/Changes"; +import {State} from "./State"; // --------------------- Special actions based on the parameters ----------------- @@ -79,70 +80,23 @@ defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data; const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"]; console.log("Using layout: ", layoutToUse.name); -if(layoutToUse === undefined){ +if (layoutToUse === undefined) { console.log("Incorrect layout") } - -// ----------------- Setup a few event sources ------------- - - -// The message that should be shown at the center of the screen -const 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 -const secondsTillChangesAreSaved = new UIEventSource(0); - -// const leftMessage = new UIEventSource<() => UIElement>(undefined); - -// This message is shown full screen on mobile devices -const fullScreenMessage = new UIEventSource(undefined); - -// The latest element that was selected - used to generate the right UI at the right place -const selectedElement = new UIEventSource<{ feature: any }>(undefined); - -const zoom = QueryParameters.GetQueryParameter("z", undefined) - .syncWith(LocalStorageSource.Get("zoom")); -const lat = QueryParameters.GetQueryParameter("lat", undefined) - .syncWith(LocalStorageSource.Get("lat")); -const lon = QueryParameters.GetQueryParameter("lon", undefined) - .syncWith(LocalStorageSource.Get("lon")); - -function featSw(key: string, deflt: boolean): UIEventSource { - return QueryParameters.GetQueryParameter(key, "" + deflt).map((str) => { - return str !== "false"; - }); -} - -const featureSwitchUserbadge = featSw("fs-userbadge", layoutToUse.enableUserBadge); -const featureSwitchSearch = featSw("fs-search", layoutToUse.enableSearch); -const featureSwitchLayers = featSw("fs-layers", layoutToUse.enableLayers); -const featureSwitchAddNew = featSw("fs-add-new", layoutToUse.enableAdd); -const featureSwitchWelcomeMessage = featSw("fs-welcome-message", true); -const featureSwitchIframe = featSw("fs-iframe", false); - - -const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({ - zoom: Utils.asFloat(zoom.data) ?? layoutToUse.startzoom, - lat: Utils.asFloat(lat.data) ?? layoutToUse.startLat, - lon: Utils.asFloat(lon.data) ?? layoutToUse.startLon -}); - -locationControl.addCallback((latlonz) => { - zoom.setData(latlonz.zoom.toString()); - lat.setData(latlonz.lat.toString().substr(0, 6)); - lon.setData(latlonz.lon.toString().substr(0, 6)); -}) +// Setup the global state +State.state = new State(layoutToUse); +const state = State.state; // ----------------- Prepare the important objects ----------------- -const osmConnection: OsmConnection = new OsmConnection( +state.osmConnection = new OsmConnection( QueryParameters.GetQueryParameter("test", "false").data === "true", QueryParameters.GetQueryParameter("oauth_token", undefined) ); -Locale.language.syncWith(osmConnection.GetPreference("language")); +Locale.language.syncWith(state.osmConnection.GetPreference("language")); // @ts-ignore window.setLanguage = function (language: string) { @@ -158,13 +112,12 @@ Locale.language.addCallback((currentLanguage) => { }).ping() -const saveTimeout = 30000; // After this many milliseconds without changes, saves are sent of to OSM -const allElements = new ElementStorage(); -const changes = new Changes( - "Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name, - osmConnection, allElements); -const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( - locationControl.map((location) => { +state.allElements = new ElementStorage(); +state.changes = new Changes( + "Beantwoorden van vragen met #MapComplete voor vragenset #" + state.layoutToUse.data.name, + state.osmConnection, state.allElements); +state.bm = new Basemap("leafletDiv", state.locationControl, new VariableUiElement( + state.locationControl.map((location) => { const mapComplete = "Mapcomple " + " " + "Report bug"; @@ -184,9 +137,9 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( // ------------- Setup the layers ------------------------------- -const layerSetup = InitUiElements.InitLayers(layoutToUse, osmConnection, changes, allElements, bm, fullScreenMessage, selectedElement); +const layerSetup = InitUiElements.InitLayers(); -const layerUpdater = new LayerUpdater(bm, layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers); +const layerUpdater = new LayerUpdater(layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers); // --------------- Setting up layer selection ui -------- @@ -196,22 +149,24 @@ const closedFilterButton = ``; -let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {return {value: layer, shown: layer.name}}); -const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]); +let baseLayerOptions = BaseLayers.baseLayers.map((layer) => { + return {value: layer, shown: layer.name} +}); +const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, State.state.bm.CurrentLayer), openFilterButton]); const layerSelection = new Combine([`

Maplayers

`, new LayerSelection(layerSetup.flayers)]); let layerControl = backgroundMapPicker; if (layerSetup.flayers.length > 1) { layerControl = new Combine([layerSelection, backgroundMapPicker]); } -InitUiElements.OnlyIf(featureSwitchLayers, () => { +InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => { const checkbox = new CheckBox(layerControl, closedFilterButton); checkbox.AttachTo("filter__selection"); - bm.Location.addCallback(() => { + State.state.bm.Location.addCallback(() => { checkbox.isEnabled.setData(false); }); - + }); @@ -224,14 +179,10 @@ Locale.language.addCallback(e => { }) -InitUiElements.OnlyIf(featureSwitchAddNew, () => { - new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => { - return new SimpleAddUI(bm.Location, - bm.LastClickLocation, - changes, - selectedElement, +InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => { + new StrayClickHandler(() => { + return new SimpleAddUI( layerUpdater.runningQuery, - osmConnection.userDetails, layerSetup.presets); } ); @@ -242,7 +193,7 @@ InitUiElements.OnlyIf(featureSwitchAddNew, () => { * Show the questions and information for the selected element * This is given to the div which renders fullscreen on mobile devices */ -selectedElement.addCallback((feature) => { +State.state.selectedElement.addCallback((feature) => { if (feature?.feature?.properties === undefined) { return; } @@ -256,67 +207,54 @@ selectedElement.addCallback((feature) => { const featureBox = new FeatureInfoBox( feature.feature, - allElements.getElement(data.id), + State.state.allElements.getElement(data.id), layer.title, layer.elementsToShow, - changes, - osmConnection.userDetails ); - fullScreenMessage.setData(featureBox); + State.state.fullScreenMessage.setData(featureBox); break; } } } ); - -const pendingChanges = new PendingChanges(changes, secondsTillChangesAreSaved,); - -InitUiElements.OnlyIf(featureSwitchUserbadge, () => { - - new UserBadge(osmConnection.userDetails, - pendingChanges, - Locale.CreateLanguagePicker(layoutToUse), - bm) - .AttachTo('userbadge'); +console.log("Enable new:",State.state.featureSwitchAddNew.data,"deafult", layoutToUse.enableAdd) +InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { + new UserBadge().AttachTo('userbadge'); }); -InitUiElements.OnlyIf((featureSwitchSearch), () => { - new SearchAndGo(bm).AttachTo("searchbox"); +InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => { + new SearchAndGo().AttachTo("searchbox"); }); -new FullScreenMessageBoxHandler(fullScreenMessage, () => { - selectedElement.setData(undefined) +new FullScreenMessageBoxHandler(() => { + State.state.selectedElement.setData(undefined) }).update(); -InitUiElements.OnlyIf(featureSwitchWelcomeMessage, () => { - InitUiElements.InitWelcomeMessage(layoutToUse, - featureSwitchUserbadge.data ? osmConnection : undefined, bm, fullScreenMessage) +InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => { + InitUiElements.InitWelcomeMessage() }); -if ((window != window.top && !featureSwitchWelcomeMessage.data) || featureSwitchIframe.data) { +if ((window != window.top && !State.state.featureSwitchWelcomeMessage) || State.state.featureSwitchIframe.data) { new FixedUiElement(``).AttachTo("top-right") } new CenterMessageBox( layerSetup.minZoom, - centerMessage, - osmConnection, - locationControl, layerUpdater.runningQuery) .AttachTo("centermessage"); -Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout); -Helpers.LastEffortSave(changes); - -osmConnection.registerActivateOsmAUthenticationClass(); +Helpers.SetupAutoSave(); +Helpers.LastEffortSave(); -new GeoLocationHandler(bm).AttachTo("geolocate-button"); + +new GeoLocationHandler().AttachTo("geolocate-button"); -locationControl.ping() +State.state.osmConnection.registerActivateOsmAUthenticationClass(); +State.state.locationControl.ping()