forked from MapComplete/MapComplete
		
	Formatting
This commit is contained in:
		
							parent
							
								
									4539a3b21c
								
							
						
					
					
						commit
						decbf462d3
					
				
					 1 changed files with 248 additions and 248 deletions
				
			
		|  | @ -1,266 +1,266 @@ | ||||||
| import * as L from "leaflet"; | import * as L from "leaflet"; | ||||||
| import { UIEventSource } from "../UIEventSource"; | import {UIEventSource} from "../UIEventSource"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import Img from "../../UI/Base/Img"; | import Img from "../../UI/Base/Img"; | ||||||
| import { LocalStorageSource } from "../Web/LocalStorageSource"; | import {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||||
| import { VariableUiElement } from "../../UI/Base/VariableUIElement"; | import {VariableUiElement} from "../../UI/Base/VariableUIElement"; | ||||||
| import { CenterFlexedElement } from "../../UI/Base/CenterFlexedElement"; | import {CenterFlexedElement} from "../../UI/Base/CenterFlexedElement"; | ||||||
| 
 | 
 | ||||||
| export default class GeoLocationHandler extends VariableUiElement { | export default class GeoLocationHandler extends VariableUiElement { | ||||||
|   /** |     /** | ||||||
|    * Wether or not the geolocation is active, aka the user requested the current location |      * Wether or not the geolocation is active, aka the user requested the current location | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private readonly _isActive: UIEventSource<boolean>; |     private readonly _isActive: UIEventSource<boolean>; | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user |      * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private readonly _isLocked: UIEventSource<boolean>; |     private readonly _isLocked: UIEventSource<boolean>; | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * The callback over the permission API |      * The callback over the permission API | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private readonly _permission: UIEventSource<string>; |     private readonly _permission: UIEventSource<string>; | ||||||
| 
 | 
 | ||||||
|   /*** |     /*** | ||||||
|    * The marker on the map, in order to update it |      * The marker on the map, in order to update it | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private _marker: L.Marker; |     private _marker: L.Marker; | ||||||
|   /** |     /** | ||||||
|    * Literally: _currentGPSLocation.data != undefined |      * Literally: _currentGPSLocation.data != undefined | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private readonly _hasLocation: UIEventSource<boolean>; |     private readonly _hasLocation: UIEventSource<boolean>; | ||||||
|   private readonly _currentGPSLocation: UIEventSource<{ |     private readonly _currentGPSLocation: UIEventSource<{ | ||||||
|     latlng: any; |         latlng: any; | ||||||
|     accuracy: number; |         accuracy: number; | ||||||
|   }>; |     }>; | ||||||
|   /** |     /** | ||||||
|    * Kept in order to update the marker |      * Kept in order to update the marker | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private readonly _leafletMap: UIEventSource<L.Map>; |     private readonly _leafletMap: UIEventSource<L.Map>; | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs |      * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private _lastUserRequest: Date; |     private _lastUserRequest: Date; | ||||||
| 
 | 
 | ||||||
|   /** |     /** | ||||||
|    * A small flag on localstorage. If the user previously granted the geolocation, it will be set. |      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. | ||||||
|    * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. |      * On firefox, the permissions api is broken (probably fingerprint resistiance) and "granted + don't ask again" doesn't stick between sessions. | ||||||
|    * |      * | ||||||
|    * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. |      * Instead, we set this flag. If this flag is set upon loading the page, we start geolocating immediately. | ||||||
|    * If the user denies the geolocation this time, we unset this flag |      * If the user denies the geolocation this time, we unset this flag | ||||||
|    * @private |      * @private | ||||||
|    */ |      */ | ||||||
|   private readonly _previousLocationGrant: UIEventSource<string>; |     private readonly _previousLocationGrant: UIEventSource<string>; | ||||||
|   private readonly _layoutToUse: UIEventSource<LayoutConfig>; |     private readonly _layoutToUse: UIEventSource<LayoutConfig>; | ||||||
| 
 | 
 | ||||||
|   constructor( |     constructor( | ||||||
|     currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, |         currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, | ||||||
|     leafletMap: UIEventSource<L.Map>, |         leafletMap: UIEventSource<L.Map>, | ||||||
|     layoutToUse: UIEventSource<LayoutConfig> |         layoutToUse: UIEventSource<LayoutConfig> | ||||||
|   ) { |  | ||||||
|     const hasLocation = currentGPSLocation.map( |  | ||||||
|       (location) => location !== undefined |  | ||||||
|     ); |  | ||||||
|     const previousLocationGrant = LocalStorageSource.Get( |  | ||||||
|       "geolocation-permissions" |  | ||||||
|     ); |  | ||||||
|     const isActive = new UIEventSource<boolean>(false); |  | ||||||
|     const isLocked = new UIEventSource<boolean>(false); |  | ||||||
|     super( |  | ||||||
|       hasLocation.map( |  | ||||||
|         (hasLocationData) => { |  | ||||||
|           let icon: string; |  | ||||||
| 
 |  | ||||||
|           if (isLocked.data) { |  | ||||||
|             icon = Svg.location; |  | ||||||
|           } else if (hasLocationData) { |  | ||||||
|             icon = Svg.location_empty; |  | ||||||
|           } else if (isActive.data) { |  | ||||||
|             icon = Svg.location_empty; |  | ||||||
|           } else { |  | ||||||
|             icon = Svg.location_circle; |  | ||||||
|           } |  | ||||||
| 
 |  | ||||||
|           return new CenterFlexedElement( |  | ||||||
|             Img.AsImageElement(icon, "", "width:1.25rem;height:1.25rem") |  | ||||||
|           ); |  | ||||||
|         }, |  | ||||||
|         [isActive, isLocked] |  | ||||||
|       ) |  | ||||||
|     ); |  | ||||||
|     this._isActive = isActive; |  | ||||||
|     this._isLocked = isLocked; |  | ||||||
|     this._permission = new UIEventSource<string>(""); |  | ||||||
|     this._previousLocationGrant = previousLocationGrant; |  | ||||||
|     this._currentGPSLocation = currentGPSLocation; |  | ||||||
|     this._leafletMap = leafletMap; |  | ||||||
|     this._layoutToUse = layoutToUse; |  | ||||||
|     this._hasLocation = hasLocation; |  | ||||||
|     const self = this; |  | ||||||
| 
 |  | ||||||
|     const currentPointer = this._isActive.map( |  | ||||||
|       (isActive) => { |  | ||||||
|         if (isActive && !self._hasLocation.data) { |  | ||||||
|           return "cursor-wait"; |  | ||||||
|         } |  | ||||||
|         return "cursor-pointer"; |  | ||||||
|       }, |  | ||||||
|       [this._hasLocation] |  | ||||||
|     ); |  | ||||||
|     currentPointer.addCallbackAndRun((pointerClass) => { |  | ||||||
|       self.SetClass(pointerClass); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     this.onClick(() => { |  | ||||||
|       if (self._hasLocation.data) { |  | ||||||
|         self._isLocked.setData(!self._isLocked.data); |  | ||||||
|       } |  | ||||||
|       self.init(true); |  | ||||||
|     }); |  | ||||||
|     this.init(false); |  | ||||||
| 
 |  | ||||||
|     this._currentGPSLocation.addCallback((location) => { |  | ||||||
|       self._previousLocationGrant.setData("granted"); |  | ||||||
| 
 |  | ||||||
|       const timeSinceRequest = |  | ||||||
|         (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; |  | ||||||
|       if (timeSinceRequest < 30) { |  | ||||||
|         self.MoveToCurrentLoction(16); |  | ||||||
|       } else if (self._isLocked.data) { |  | ||||||
|         self.MoveToCurrentLoction(); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       let color = "#1111cc"; |  | ||||||
|       try { |  | ||||||
|         color = getComputedStyle(document.body).getPropertyValue( |  | ||||||
|           "--catch-detail-color" |  | ||||||
|         ); |  | ||||||
|       } catch (e) { |  | ||||||
|         console.error(e); |  | ||||||
|       } |  | ||||||
|       const icon = L.icon({ |  | ||||||
|         iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), |  | ||||||
|         iconSize: [40, 40], // size of the icon
 |  | ||||||
|         iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
 |  | ||||||
|       }); |  | ||||||
| 
 |  | ||||||
|       const map = self._leafletMap.data; |  | ||||||
| 
 |  | ||||||
|       const newMarker = L.marker(location.latlng, { icon: icon }); |  | ||||||
|       newMarker.addTo(map); |  | ||||||
| 
 |  | ||||||
|       if (self._marker !== undefined) { |  | ||||||
|         map.removeLayer(self._marker); |  | ||||||
|       } |  | ||||||
|       self._marker = newMarker; |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private init(askPermission: boolean) { |  | ||||||
|     const self = this; |  | ||||||
|     if (self._isActive.data) { |  | ||||||
|       self.MoveToCurrentLoction(16); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     try { |  | ||||||
|       navigator?.permissions |  | ||||||
|         ?.query({ name: "geolocation" }) |  | ||||||
|         ?.then(function (status) { |  | ||||||
|           console.log("Geolocation is already", status); |  | ||||||
|           if (status.state === "granted") { |  | ||||||
|             self.StartGeolocating(false); |  | ||||||
|           } |  | ||||||
|           self._permission.setData(status.state); |  | ||||||
|           status.onchange = function () { |  | ||||||
|             self._permission.setData(status.state); |  | ||||||
|           }; |  | ||||||
|         }); |  | ||||||
|     } catch (e) { |  | ||||||
|       console.error(e); |  | ||||||
|     } |  | ||||||
|     if (askPermission) { |  | ||||||
|       self.StartGeolocating(true); |  | ||||||
|     } else if (this._previousLocationGrant.data === "granted") { |  | ||||||
|       this._previousLocationGrant.setData(""); |  | ||||||
|       self.StartGeolocating(false); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private MoveToCurrentLoction(targetZoom = 16) { |  | ||||||
|     const location = this._currentGPSLocation.data; |  | ||||||
|     this._lastUserRequest = undefined; |  | ||||||
| 
 |  | ||||||
|     if ( |  | ||||||
|       this._currentGPSLocation.data.latlng[0] === 0 && |  | ||||||
|       this._currentGPSLocation.data.latlng[1] === 0 |  | ||||||
|     ) { |     ) { | ||||||
|       console.debug("Not moving to GPS-location: it is null island"); |         const hasLocation = currentGPSLocation.map( | ||||||
|       return; |             (location) => location !== undefined | ||||||
|     } |         ); | ||||||
|  |         const previousLocationGrant = LocalStorageSource.Get( | ||||||
|  |             "geolocation-permissions" | ||||||
|  |         ); | ||||||
|  |         const isActive = new UIEventSource<boolean>(false); | ||||||
|  |         const isLocked = new UIEventSource<boolean>(false); | ||||||
|  |         super( | ||||||
|  |             hasLocation.map( | ||||||
|  |                 (hasLocationData) => { | ||||||
|  |                     let icon: string; | ||||||
| 
 | 
 | ||||||
|     // We check that the GPS location is not out of bounds
 |                     if (isLocked.data) { | ||||||
|     const b = this._layoutToUse.data.lockLocation; |                         icon = Svg.location; | ||||||
|     let inRange = true; |                     } else if (hasLocationData) { | ||||||
|     if (b) { |                         icon = Svg.location_empty; | ||||||
|       if (b !== true) { |                     } else if (isActive.data) { | ||||||
|         // B is an array with our locklocation
 |                         icon = Svg.location_empty; | ||||||
|         inRange = |                     } else { | ||||||
|           b[0][0] <= location.latlng[0] && |                         icon = Svg.location_circle; | ||||||
|           location.latlng[0] <= b[1][0] && |                     } | ||||||
|           b[0][1] <= location.latlng[1] && |  | ||||||
|           location.latlng[1] <= b[1][1]; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     if (!inRange) { |  | ||||||
|       console.log( |  | ||||||
|         "Not zooming to GPS location: out of bounds", |  | ||||||
|         b, |  | ||||||
|         location.latlng |  | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       this._leafletMap.data.setView(location.latlng, targetZoom); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   private StartGeolocating(zoomToGPS = true) { |                     return new CenterFlexedElement( | ||||||
|     const self = this; |                         Img.AsImageElement(icon, "", "width:1.25rem;height:1.25rem") | ||||||
|     console.log("Starting geolocation"); |                     ); | ||||||
|  |                 }, | ||||||
|  |                 [isActive, isLocked] | ||||||
|  |             ) | ||||||
|  |         ); | ||||||
|  |         this._isActive = isActive; | ||||||
|  |         this._isLocked = isLocked; | ||||||
|  |         this._permission = new UIEventSource<string>(""); | ||||||
|  |         this._previousLocationGrant = previousLocationGrant; | ||||||
|  |         this._currentGPSLocation = currentGPSLocation; | ||||||
|  |         this._leafletMap = leafletMap; | ||||||
|  |         this._layoutToUse = layoutToUse; | ||||||
|  |         this._hasLocation = hasLocation; | ||||||
|  |         const self = this; | ||||||
| 
 | 
 | ||||||
|     this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); |         const currentPointer = this._isActive.map( | ||||||
|     if (self._permission.data === "denied") { |             (isActive) => { | ||||||
|       self._previousLocationGrant.setData(""); |                 if (isActive && !self._hasLocation.data) { | ||||||
|       return ""; |                     return "cursor-wait"; | ||||||
|     } |                 } | ||||||
|     if (this._currentGPSLocation.data !== undefined) { |                 return "cursor-pointer"; | ||||||
|       this.MoveToCurrentLoction(16); |             }, | ||||||
|     } |             [this._hasLocation] | ||||||
| 
 |         ); | ||||||
|     console.log("Searching location using GPS"); |         currentPointer.addCallbackAndRun((pointerClass) => { | ||||||
| 
 |             self.SetClass(pointerClass); | ||||||
|     if (self._isActive.data) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     self._isActive.setData(true); |  | ||||||
|     navigator.geolocation.watchPosition( |  | ||||||
|       function (position) { |  | ||||||
|         self._currentGPSLocation.setData({ |  | ||||||
|           latlng: [position.coords.latitude, position.coords.longitude], |  | ||||||
|           accuracy: position.coords.accuracy, |  | ||||||
|         }); |         }); | ||||||
|       }, | 
 | ||||||
|       function () { |         this.onClick(() => { | ||||||
|         console.warn("Could not get location with navigator.geolocation"); |             if (self._hasLocation.data) { | ||||||
|       } |                 self._isLocked.setData(!self._isLocked.data); | ||||||
|     ); |             } | ||||||
|   } |             self.init(true); | ||||||
|  |         }); | ||||||
|  |         this.init(false); | ||||||
|  | 
 | ||||||
|  |         this._currentGPSLocation.addCallback((location) => { | ||||||
|  |             self._previousLocationGrant.setData("granted"); | ||||||
|  | 
 | ||||||
|  |             const timeSinceRequest = | ||||||
|  |                 (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; | ||||||
|  |             if (timeSinceRequest < 30) { | ||||||
|  |                 self.MoveToCurrentLoction(16); | ||||||
|  |             } else if (self._isLocked.data) { | ||||||
|  |                 self.MoveToCurrentLoction(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             let color = "#1111cc"; | ||||||
|  |             try { | ||||||
|  |                 color = getComputedStyle(document.body).getPropertyValue( | ||||||
|  |                     "--catch-detail-color" | ||||||
|  |                 ); | ||||||
|  |             } catch (e) { | ||||||
|  |                 console.error(e); | ||||||
|  |             } | ||||||
|  |             const icon = L.icon({ | ||||||
|  |                 iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)), | ||||||
|  |                 iconSize: [40, 40], // size of the icon
 | ||||||
|  |                 iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
 | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             const map = self._leafletMap.data; | ||||||
|  | 
 | ||||||
|  |             const newMarker = L.marker(location.latlng, {icon: icon}); | ||||||
|  |             newMarker.addTo(map); | ||||||
|  | 
 | ||||||
|  |             if (self._marker !== undefined) { | ||||||
|  |                 map.removeLayer(self._marker); | ||||||
|  |             } | ||||||
|  |             self._marker = newMarker; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private init(askPermission: boolean) { | ||||||
|  |         const self = this; | ||||||
|  |         if (self._isActive.data) { | ||||||
|  |             self.MoveToCurrentLoction(16); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|  |             navigator?.permissions | ||||||
|  |                 ?.query({name: "geolocation"}) | ||||||
|  |                 ?.then(function (status) { | ||||||
|  |                     console.log("Geolocation is already", status); | ||||||
|  |                     if (status.state === "granted") { | ||||||
|  |                         self.StartGeolocating(false); | ||||||
|  |                     } | ||||||
|  |                     self._permission.setData(status.state); | ||||||
|  |                     status.onchange = function () { | ||||||
|  |                         self._permission.setData(status.state); | ||||||
|  |                     }; | ||||||
|  |                 }); | ||||||
|  |         } catch (e) { | ||||||
|  |             console.error(e); | ||||||
|  |         } | ||||||
|  |         if (askPermission) { | ||||||
|  |             self.StartGeolocating(true); | ||||||
|  |         } else if (this._previousLocationGrant.data === "granted") { | ||||||
|  |             this._previousLocationGrant.setData(""); | ||||||
|  |             self.StartGeolocating(false); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private MoveToCurrentLoction(targetZoom = 16) { | ||||||
|  |         const location = this._currentGPSLocation.data; | ||||||
|  |         this._lastUserRequest = undefined; | ||||||
|  | 
 | ||||||
|  |         if ( | ||||||
|  |             this._currentGPSLocation.data.latlng[0] === 0 && | ||||||
|  |             this._currentGPSLocation.data.latlng[1] === 0 | ||||||
|  |         ) { | ||||||
|  |             console.debug("Not moving to GPS-location: it is null island"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // We check that the GPS location is not out of bounds
 | ||||||
|  |         const b = this._layoutToUse.data.lockLocation; | ||||||
|  |         let inRange = true; | ||||||
|  |         if (b) { | ||||||
|  |             if (b !== true) { | ||||||
|  |                 // B is an array with our locklocation
 | ||||||
|  |                 inRange = | ||||||
|  |                     b[0][0] <= location.latlng[0] && | ||||||
|  |                     location.latlng[0] <= b[1][0] && | ||||||
|  |                     b[0][1] <= location.latlng[1] && | ||||||
|  |                     location.latlng[1] <= b[1][1]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (!inRange) { | ||||||
|  |             console.log( | ||||||
|  |                 "Not zooming to GPS location: out of bounds", | ||||||
|  |                 b, | ||||||
|  |                 location.latlng | ||||||
|  |             ); | ||||||
|  |         } else { | ||||||
|  |             this._leafletMap.data.setView(location.latlng, targetZoom); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private StartGeolocating(zoomToGPS = true) { | ||||||
|  |         const self = this; | ||||||
|  |         console.log("Starting geolocation"); | ||||||
|  | 
 | ||||||
|  |         this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); | ||||||
|  |         if (self._permission.data === "denied") { | ||||||
|  |             self._previousLocationGrant.setData(""); | ||||||
|  |             return ""; | ||||||
|  |         } | ||||||
|  |         if (this._currentGPSLocation.data !== undefined) { | ||||||
|  |             this.MoveToCurrentLoction(16); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         console.log("Searching location using GPS"); | ||||||
|  | 
 | ||||||
|  |         if (self._isActive.data) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         self._isActive.setData(true); | ||||||
|  |         navigator.geolocation.watchPosition( | ||||||
|  |             function (position) { | ||||||
|  |                 self._currentGPSLocation.setData({ | ||||||
|  |                     latlng: [position.coords.latitude, position.coords.longitude], | ||||||
|  |                     accuracy: position.coords.accuracy, | ||||||
|  |                 }); | ||||||
|  |             }, | ||||||
|  |             function () { | ||||||
|  |                 console.warn("Could not get location with navigator.geolocation"); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue