forked from MapComplete/MapComplete
		
	Refactoring: introduction of global state to simplify getting common objects
This commit is contained in:
		
							parent
							
								
									afaaaaadb1
								
							
						
					
					
						commit
						004eead4ee
					
				
					 34 changed files with 532 additions and 506 deletions
				
			
		|  | @ -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}" | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 <b class='alert'>" + r(props.targetWidth - props.width) + "m</b> 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 <b>{targetWidth}m</b><br>" + | ||||
|                             "{short}", | ||||
|                         template: "$$$", | ||||
|                     } | ||||
|                     mappings:[ | ||||
|                         {k: new Tag("short","*"), txt:  "De totale nodige ruimte voor vlot en veilig verkeer is dus <b>{targetWidth}m</b><br>" + | ||||
|                                 "Er is dus <span class='alert'>{short}m</span> te weinig", substitute: true}, | ||||
|                         {k: new Tag("short",""), txt:  "De totale nodige ruimte voor vlot en veilig verkeer is dus <span class='thanks'>{targetWidth}m</span>"} | ||||
|                     ] | ||||
|                 } | ||||
|             ).OnlyShowIf(this._notCarFree), | ||||
|              | ||||
|  |  | |||
|  | @ -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<UserDetails>; | ||||
|     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 + "<br/>" | ||||
|         } | ||||
| 
 | ||||
|         return "<span>" + | ||||
|             this.description.Render() + | ||||
|             "<br/>" + | ||||
|             loginStatus + | ||||
|             this.tail.Render() + | ||||
|             "<br/>" + | ||||
|             this.languagePicker.Render() + | ||||
|             "</span>"; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerUpdate(htmlElement: HTMLElement) { | ||||
|         this.osmConnection?.registerActivateOsmAUthenticationClass() | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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"), | ||||
|  |  | |||
|  | @ -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<any>, 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<UserDetails>; | ||||
|     private _priority: number; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -189,7 +189,7 @@ class TagRendering extends UIElement implements TagDependantUIElement { | |||
|     private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, changes: Changes, options: { | ||||
|     constructor(tags: UIEventSource<any>, 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(); | ||||
|             } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,9 +3,13 @@ import {Changes} from "../Logic/Osm/Changes"; | |||
| import {UIElement} from "../UI/UIElement"; | ||||
| 
 | ||||
| 
 | ||||
| export interface Dependencies { | ||||
|     tags: UIEventSource<any> | ||||
| } | ||||
| 
 | ||||
| export interface TagDependantUIElementConstructor { | ||||
| 
 | ||||
|     construct(dependencies: {tags: UIEventSource<any>, changes: Changes}): TagDependantUIElement; | ||||
|     construct(dependencies: Dependencies): TagDependantUIElement; | ||||
|     IsKnown(properties: any): boolean; | ||||
|     IsQuestioning(properties: any): boolean; | ||||
|     Priority(): number; | ||||
|  |  | |||
							
								
								
									
										10
									
								
								Helpers.ts
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								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<number>, 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) { | ||||
|  |  | |||
|  | @ -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: `<img src='${layoutToUse.icon}'>`, content: welcome}, | ||||
|             {header: `<img src='${'./assets/osm-logo.svg'}'>`, content: Translations.t.general.openStreetMapIntro}, | ||||
|             {header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen(layoutToUse, bm.Location)}, | ||||
|             {header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen(layoutToUse.name, bm.Location)} | ||||
|             {header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen()}, | ||||
|             {header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen()} | ||||
|         ]) | ||||
| 
 | ||||
|         return fullOptions; | ||||
|  | @ -54,10 +55,9 @@ export class InitUiElements { | |||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     static InitWelcomeMessage(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap, | ||||
|                               fullScreenMessage: UIEventSource<UIElement>) { | ||||
|     static InitWelcomeMessage() { | ||||
| 
 | ||||
|         const fullOptions = this.CreateWelcomePane(layoutToUse, osmConnection, bm); | ||||
|         const fullOptions = this.CreateWelcomePane(); | ||||
| 
 | ||||
|         const help = new FixedUiElement(`<div class='collapse-button-img'><img src='assets/help.svg'  alt='help'></div>`); | ||||
|         const close = new FixedUiElement(`<div class='collapse-button-img'><img src='assets/close.svg'  alt='close'></div>`); | ||||
|  | @ -70,31 +70,27 @@ 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(`<div class='collapse-button-img' class="shadow"><img src='assets/help.svg'  alt='help'></div>`).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<UIElement>, | ||||
|                       selectedElement: UIEventSource<any>): { | ||||
|     static InitLayers(): { | ||||
|         minZoom: number | ||||
|         flayers: FilteredLayer[], | ||||
|         presets: Preset[] | ||||
|  | @ -105,8 +101,8 @@ export class InitUiElements { | |||
|         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 ?? []) { | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<boolean> = 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<any>, feature: any) => UIElement; | ||||
| 
 | ||||
|     private static readonly grid = codegrid.CodeGrid(); | ||||
| 
 | ||||
|     constructor( | ||||
|         layerDef: LayerDefinition, | ||||
|         map: Basemap, storage: ElementStorage, | ||||
|         changes: Changes, | ||||
|         selectedElement: UIEventSource<any>, | ||||
|         showOnPopup: ((tags: UIEventSource<any>, 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,17 +61,16 @@ 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); | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|  | @ -85,15 +78,10 @@ export class FilteredLayer { | |||
|      | ||||
|     static fromDefinition( | ||||
|         definition,  | ||||
|         basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>, | ||||
|                  selectedElement: UIEventSource<{feature: any}>, | ||||
|                  showOnPopup: (tags: UIEventSource<any>, 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); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<string[]> { | |||
|     private readonly _wdItem = new UIEventSource<string>(""); | ||||
|     private readonly _commons = new UIEventSource<string>(""); | ||||
|     private _activated: boolean = false; | ||||
|     private _changes: Changes; | ||||
|     public _deletedImages = new UIEventSource<string[]>([]); | ||||
| 
 | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, | ||||
|                 changes: Changes) { | ||||
|     constructor(tags: UIEventSource<any>) { | ||||
|         super([]); | ||||
| 
 | ||||
|         this._tags = tags; | ||||
|         this._changes = changes; | ||||
| 
 | ||||
|         const self = this; | ||||
|         this._wdItem.addCallback(() => { | ||||
|  | @ -119,7 +117,7 @@ export class ImageSearcher extends UIEventSource<string[]> { | |||
|             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(); | ||||
|     } | ||||
|  |  | |||
|  | @ -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; | ||||
|         } | ||||
|  |  | |||
|  | @ -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<boolean> = new UIEventSource<boolean>(false); | ||||
|     private _permission: UIEventSource<string> = new UIEventSource<string>(""); | ||||
|     private _map: Basemap; | ||||
|     private _marker: any; | ||||
|     private _hasLocation: UIEventSource<boolean>; | ||||
| 
 | ||||
|     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,13 +24,13 @@ 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) { | ||||
|  | @ -43,9 +38,10 @@ export class GeoLocationHandler extends UIElement { | |||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         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 "<img src='assets/crosshair-blue.png' alt='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
 | ||||
|         }); | ||||
|  | @ -119,7 +115,7 @@ export class GeoLocationHandler extends UIElement { | |||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 self._map.map.findAccuratePosition({ | ||||
|                 map.findAccuratePosition({ | ||||
|                     maxWait: 10000, // defaults to 10000
 | ||||
|                     desiredAccuracy: 50 // defaults to 20
 | ||||
|                 }); | ||||
|  |  | |||
|  | @ -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<UIElement>; | ||||
|     private _uiToShow: (() => UIElement); | ||||
| 
 | ||||
|     constructor( | ||||
|         basemap: Basemap, | ||||
|         selectElement: UIEventSource<{ feature: any }>, | ||||
|         fullScreenMessage: UIEventSource<UIElement>,  | ||||
|         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); | ||||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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 = ""; | ||||
|  |  | |||
|  | @ -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=" +  | ||||
|  |  | |||
|  | @ -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,19 +22,38 @@ export class OsmConnection { | |||
| 
 | ||||
|     constructor(dryRun: boolean, oauth_token: UIEventSource<string>) { | ||||
| 
 | ||||
|         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 // show a login form if the user is not authenticated and
 | ||||
|                        // you try to do a call
 | ||||
| 
 | ||||
|                 auto: true | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         this.userDetails = new UIEventSource<UserDetails>(new UserDetails()); | ||||
|         this.userDetails.data.osmConnection = this; | ||||
|         this.userDetails.data.dryRun = dryRun; | ||||
|         this._dryRun = dryRun; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<any>; | ||||
|     private _changeHandler: Changes; | ||||
|     private _userdetails: UIEventSource<UserDetails>; | ||||
|     private _slideShow: SlideShow; | ||||
|     private _preferedLicense: UIEventSource<string>; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, | ||||
|                 userdetails: UIEventSource<UserDetails>, | ||||
|                 preferedLicense: UIEventSource<string>, | ||||
|                 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) | ||||
|  |  | |||
|  | @ -51,7 +51,10 @@ export class QueryParameters { | |||
|     } | ||||
| 
 | ||||
|     public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> { | ||||
|         if (deflt !== undefined) { | ||||
|             console.log(key, "-->", deflt) | ||||
|             QueryParameters.defaults[key] = deflt; | ||||
|         } | ||||
|         if (QueryParameters.knownSources[key] !== undefined) { | ||||
|             return QueryParameters.knownSources[key]; | ||||
|         } | ||||
|  |  | |||
							
								
								
									
										134
									
								
								State.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								State.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<Layout>(undefined); | ||||
| 
 | ||||
|     /** | ||||
|      The mapping from id -> UIEventSource<properties> | ||||
|      */ | ||||
|     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<string>(""); | ||||
| 
 | ||||
|     /** | ||||
|      * 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<number>(0); | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      This message is shown full screen on mobile devices | ||||
|      */ | ||||
|     public readonly fullScreenMessage = new UIEventSource<UIElement>(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<boolean>; | ||||
|     public readonly featureSwitchSearch: UIEventSource<boolean>; | ||||
|     public readonly featureSwitchLayers: UIEventSource<boolean>; | ||||
|     public readonly featureSwitchAddNew: UIEventSource<boolean>; | ||||
|     public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>; | ||||
|     public readonly featureSwitchIframe: UIEventSource<boolean>; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * 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<number>(30 * 1000); | ||||
| 
 | ||||
| 
 | ||||
|     constructor(layoutToUse: Layout) { | ||||
|         this.layoutToUse = new UIEventSource<Layout>(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<boolean> { | ||||
|             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); | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -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<boolean>(true); | ||||
|     private readonly _centermessage: UIEventSource<string>; | ||||
|     private readonly _osmConnection: OsmConnection; | ||||
|     private readonly _queryRunning: UIEventSource<boolean>; | ||||
|     private startZoom: number; | ||||
| 
 | ||||
|     constructor( | ||||
|         startZoom: number, | ||||
|         centermessage: UIEventSource<string>, | ||||
|         osmConnection: OsmConnection, | ||||
|         location: UIEventSource<{ zoom: number }>, | ||||
|         queryRunning: UIEventSource<boolean> | ||||
|     ) { | ||||
|         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"; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<any>; | ||||
|     private _changes: Changes; | ||||
|     private _userDetails: UIEventSource<UserDetails>; | ||||
| 
 | ||||
| 
 | ||||
|     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<any>, | ||||
|         title: TagRenderingOptions | UIElement | string, | ||||
|         elementsToShow: TagDependantUIElementConstructor[], | ||||
|         changes: Changes, | ||||
|         userDetails: UIEventSource<UserDetails> | ||||
|     ) { | ||||
|         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; | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ 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 | ||||
|  | @ -10,17 +11,20 @@ export class FullScreenMessageBoxHandler { | |||
| 
 | ||||
|     private _uielement: UIEventSource<UIElement>; | ||||
| 
 | ||||
|     constructor(uielement: UIEventSource<UIElement>, | ||||
|                 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<any>) { | ||||
|         const self = this; | ||||
|         uiEventSource.addCallback(function () { | ||||
|             self.update(); | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     update() { | ||||
|         const wrapper = document.getElementById("messagesboxmobilewrapper"); | ||||
|  |  | |||
|  | @ -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<any>, 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<UserDetails>; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, changes: Changes) { | ||||
|     constructor(tags: UIEventSource<any>) { | ||||
|         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
 | ||||
|         }) | ||||
|  |  | |||
|  | @ -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<any>, 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(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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<boolean> = new UIEventSource<boolean>(false); | ||||
|     private _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|     private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) }; | ||||
|     private _userdetails: UIEventSource<UserDetails>; | ||||
| 
 | ||||
|     constructor( | ||||
|         userInfo: UIEventSource<UserDetails>, | ||||
|         preferedLicense: UIEventSource<string>, | ||||
|         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 `<div class='activate-osm-authentication'>${t.pleaseLogin.Render()}</div>`; | ||||
|         } | ||||
| 
 | ||||
|  | @ -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 = "<span class='alert'>" + | ||||
|                     currentStateHtml + | ||||
|                     "</span>"; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return "" + | ||||
|             "<div class='imageflow'>" + | ||||
| 
 | ||||
|  | @ -89,9 +96,9 @@ export class ImageUploadFlow extends UIElement { | |||
|             `<span class='imageflow-add-picture'>${Translations.t.image.addPicture.R()}</span>` + | ||||
|             "<div class='break'></div>" + | ||||
|             "</div>" + | ||||
|             currentStateHtml + | ||||
|             Translations.t.image.respectPrivacy.Render() + "<br/>" + | ||||
|             this._licensePicker.Render() + "<br/>" + | ||||
|             new VerticalCombine(currentState).Render() + | ||||
|             "</label>" + | ||||
|             "<form id='fileselector-form-" + this.id + "'>" + | ||||
|             "<input id='fileselector-" + this.id + "' " + | ||||
|  | @ -106,11 +113,11 @@ export class ImageUploadFlow extends UIElement { | |||
| 
 | ||||
|     InnerUpdate(htmlElement: HTMLElement) { | ||||
|         super.InnerUpdate(htmlElement); | ||||
|         const user = this._userdetails.data; | ||||
|         const user = State.state.osmConnection.userDetails.data; | ||||
| 
 | ||||
|         htmlElement.onclick = function () { | ||||
|             if (!user.loggedIn) { | ||||
|                 user.osmConnection.AttemptLogin(); | ||||
|                 State.state.osmConnection.AttemptLogin(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,16 +9,13 @@ import {UIEventSource} from "./UIEventSource"; | |||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| import Combine from "./Base/Combine"; | ||||
| import {SubtleButton} from "./Base/SubtleButton"; | ||||
| import {State} from "../State"; | ||||
| 
 | ||||
| 
 | ||||
| export class MoreScreen extends UIElement { | ||||
|     private currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>; | ||||
|     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([ | ||||
|  |  | |||
|  | @ -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<number>; | ||||
|     private _countdown: UIEventSource<number>; | ||||
|     private _isSaving: UIEventSource<boolean>; | ||||
| 
 | ||||
|     constructor(changes: Changes, | ||||
|                 countdown: UIEventSource<number>) { | ||||
|         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 += "."; | ||||
|  |  | |||
|  | @ -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("<img class='search-go' src='./assets/search.svg' alt='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); | ||||
|             }, | ||||
|             () => { | ||||
|  |  | |||
|  | @ -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}` | ||||
|  |  | |||
|  | @ -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<boolean>; | ||||
|     private _userDetails: UIEventSource<UserDetails>; | ||||
| 
 | ||||
|     private _confirmPreset: UIEventSource<Preset> | ||||
|         = new UIEventSource<Preset>(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<boolean>, | ||||
|                 userDetails: UIEventSource<UserDetails>, | ||||
|                 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 ? "<span class='alert'>TESTING - changes won't be saved</span>":"", | ||||
|                 userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"", | ||||
|                 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, "<span class='alert'>", | ||||
|                 Translations.t.general.readYourMessages, | ||||
|                 "</span>", | ||||
|  | @ -150,7 +141,7 @@ export class SimpleAddUI extends UIElement { | |||
|             ]).Render(); | ||||
|         } | ||||
| 
 | ||||
|         if (this._userDetails.data.dryRun) { | ||||
|         if (userDetails.data.dryRun) { | ||||
|             header = new Combine([header, | ||||
|                 "<span class='alert'>", | ||||
|                 "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, "<span class='alert'>", | ||||
|                 Translations.t.general.fewChangesBefore, | ||||
|                 "</span>"]).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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<UserDetails>; | ||||
|     private _pendingChanges: UIElement; | ||||
|     private _logout: UIElement; | ||||
|     private _basemap: Basemap; | ||||
|     private _homeButton: UIElement; | ||||
|     private _languagePicker: UIElement; | ||||
| 
 | ||||
| 
 | ||||
|     constructor(userDetails: UIEventSource<UserDetails>, | ||||
|                 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("<img src='assets/logout.svg' class='small-userbadge-icon' alt='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 "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'> "; | ||||
|                 } | ||||
|                 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 = | ||||
|  |  | |||
							
								
								
									
										60
									
								
								UI/WelcomeMessage.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								UI/WelcomeMessage.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 + "<br/>" | ||||
|         } | ||||
| 
 | ||||
|         return "<span>" + | ||||
|             this.description.Render() + | ||||
|             "<br/>" + | ||||
|             loginStatus + | ||||
|             this.tail.Render() + | ||||
|             "<br/>" + | ||||
|             this.languagePicker.Render() + | ||||
|             "</span>"; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerUpdate(htmlElement: HTMLElement) { | ||||
|         this.osmConnection?.registerActivateOsmAUthenticationClass() | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -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<string> = 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); | ||||
|  |  | |||
							
								
								
									
										146
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										146
									
								
								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 -----------------
 | ||||
|  | @ -83,66 +84,19 @@ 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<string>(""); | ||||
| 
 | ||||
| // 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<number>(0); | ||||
| 
 | ||||
| // const leftMessage = new UIEventSource<() => UIElement>(undefined);
 | ||||
| 
 | ||||
| // This message is shown full screen on mobile devices
 | ||||
| const fullScreenMessage = new UIEventSource<UIElement>(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<boolean> { | ||||
|     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 = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " + | ||||
|             " " + | ||||
|             "<a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'><img src='./assets/bug.svg' alt='Report bug'  class='small-userbadge-icon'></a>"; | ||||
|  | @ -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,19 +149,21 @@ const closedFilterButton = `<button id="filter__button" class="filter__button sh | |||
| const openFilterButton = ` | ||||
| <button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
 | ||||
| 
 | ||||
| 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([`<p class="filter__label">Maplayers</p>`, 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(`<a href='${window.location}' target='_blank'><span class='iframe-escape'><img src='assets/pop-out.svg'></span></a>`).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() | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue