forked from MapComplete/MapComplete
		
	UX: update gps-label indication if GPS is not available physically
This commit is contained in:
		
							parent
							
								
									0c3cfdc034
								
							
						
					
					
						commit
						d41afe7688
					
				
					 6 changed files with 79 additions and 29 deletions
				
			
		|  | @ -274,6 +274,7 @@ | |||
|             "background": "Change background", | ||||
|             "filter": "Filter data", | ||||
|             "jumpToLocation": "Go to your current location", | ||||
|             "locationNotAvailable": "GPS location not available. Does this device have location or are you in a tunnel?", | ||||
|             "menu": "Menu", | ||||
|             "zoomIn": "Zoom in", | ||||
|             "zoomOut": "Zoom out" | ||||
|  |  | |||
|  | @ -99,7 +99,8 @@ export default class InitialMapPositioning { | |||
|             Utils.downloadJson<{ latitude: number; longitude: number }>( | ||||
|                 Constants.GeoIpServer + "ip" | ||||
|             ).then(({ longitude, latitude }) => { | ||||
|                 if (geolocationState.currentGPSLocation.data !== undefined) { | ||||
|                 const gpsLoc = geolocationState.currentGPSLocation.data | ||||
|                 if (gpsLoc !== undefined) { | ||||
|                     return // We got a geolocation by now, abort
 | ||||
|                 } | ||||
|                 console.log("Setting location based on geoip", longitude, latitude) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { UIEventSource } from "../UIEventSource" | ||||
| import { Store, UIEventSource } from "../UIEventSource" | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import { QueryParameters } from "../Web/QueryParameters" | ||||
| 
 | ||||
|  | @ -25,6 +25,13 @@ export class GeoLocationState { | |||
|         "prompt" | ||||
|     ) | ||||
| 
 | ||||
|     /** | ||||
|      * If an error occurs with a code indicating "gps unavailable", this will be set to "false". | ||||
|      * This is about the physical availability of the GPS-signal; this might e.g. become false if the user is in a tunnel | ||||
|      */ | ||||
|     private readonly _gpsAvailable: UIEventSource<boolean> = new UIEventSource<boolean>(true) | ||||
|     public readonly gpsAvailable: Store<boolean> = this._gpsAvailable | ||||
| 
 | ||||
|     /** | ||||
|      * Important to determine e.g. if we move automatically on fix or not | ||||
|      */ | ||||
|  | @ -48,9 +55,7 @@ export class GeoLocationState { | |||
|      * If the user denies the geolocation this time, we unset this flag | ||||
|      * @private | ||||
|      */ | ||||
|     private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>( | ||||
|         LocalStorageSource.Get("geolocation-permissions") | ||||
|     ) | ||||
|     private readonly _previousLocationGrant: UIEventSource<boolean> = LocalStorageSource.GetParsed<boolean>("geolocation-permissions", false) | ||||
| 
 | ||||
|     /** | ||||
|      * Used to detect a permission retraction | ||||
|  | @ -62,27 +67,27 @@ export class GeoLocationState { | |||
| 
 | ||||
|         this.permission.addCallbackAndRunD(async (state) => { | ||||
|             if (state === "granted") { | ||||
|                 self._previousLocationGrant.setData("true") | ||||
|                 self._previousLocationGrant.setData(true) | ||||
|                 self._grantedThisSession.setData(true) | ||||
|             } | ||||
|             if (state === "prompt" && self._grantedThisSession.data) { | ||||
|                 // This is _really_ weird: we had a grant earlier, but it's 'prompt' now?
 | ||||
|                 // This means that the rights have been revoked again!
 | ||||
|                 self._previousLocationGrant.setData("false") | ||||
|                 self._previousLocationGrant.setData(false) | ||||
|                 self.permission.setData("denied") | ||||
|                 self.currentGPSLocation.setData(undefined) | ||||
|                 console.warn("Detected a downgrade in permissions!") | ||||
|             } | ||||
|             if (state === "denied") { | ||||
|                 self._previousLocationGrant.setData("false") | ||||
|                 self._previousLocationGrant.setData(false) | ||||
|             } | ||||
|         }) | ||||
|         console.log("Previous location grant:", this._previousLocationGrant.data) | ||||
|         if (this._previousLocationGrant.data === "true") { | ||||
|         if (this._previousLocationGrant.data) { | ||||
|             // A previous visit successfully granted permission. Chance is high that we are allowed to use it again!
 | ||||
| 
 | ||||
|             // We set the flag to false again. If the user only wanted to share their location once, we are not gonna keep bothering them
 | ||||
|             this._previousLocationGrant.setData("false") | ||||
|             this._previousLocationGrant.setData(false) | ||||
|             console.log("Requesting access to GPS as this was previously granted") | ||||
|             const latLonGivenViaUrl = | ||||
|                 QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") | ||||
|  | @ -124,9 +129,11 @@ export class GeoLocationState { | |||
|         } | ||||
| 
 | ||||
|         if (GeoLocationState.isSafari()) { | ||||
|             // This is probably safari
 | ||||
|             // Safari does not support the 'permissions'-API for geolocation,
 | ||||
|             // so we just start watching right away
 | ||||
|             /* | ||||
|              This is probably safari | ||||
|              Safari does not support the 'permissions'-API for geolocation, | ||||
|              so we just start watching right away | ||||
|             */ | ||||
| 
 | ||||
|             this.permission.setData("requested") | ||||
|             this.startWatching() | ||||
|  | @ -143,7 +150,7 @@ export class GeoLocationState { | |||
|                 self.startWatching() | ||||
|                 return | ||||
|             } | ||||
|             status.addEventListener("change", (e) => { | ||||
|             status.addEventListener("change", () => { | ||||
|                 self.permission.setData(status.state) | ||||
|             }) | ||||
|             // The code above might have reset it to 'prompt', but we _did_ request permission!
 | ||||
|  | @ -163,10 +170,22 @@ export class GeoLocationState { | |||
|         const self = this | ||||
|         navigator.geolocation.watchPosition( | ||||
|             function (position) { | ||||
|                 self._gpsAvailable.set(true) | ||||
|                 self.currentGPSLocation.setData(position.coords) | ||||
|                 self._previousLocationGrant.setData("true") | ||||
|                 self._previousLocationGrant.setData(true) | ||||
|             }, | ||||
|             function (e) { | ||||
|                 if(e.code === 2 || e.code === 3){ | ||||
|                     console.log("Could not get location with navigator.geolocation due to unavailable or timeout", e) | ||||
|                     self._gpsAvailable.set(false) | ||||
|                     return | ||||
|                 } | ||||
|                 self._gpsAvailable.set(true) // We go back to the default assumption that the location is physically available
 | ||||
|                 if(e.code === 1) { | ||||
|                     self.permission.set("denied") | ||||
|                     self._grantedThisSession.setData(false) | ||||
|                     return | ||||
|                 } | ||||
|                 console.warn("Could not get location with navigator.geolocation due to", e) | ||||
|             }, | ||||
|             { | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ | |||
|   import { createEventDispatcher } from "svelte" | ||||
|   import { twJoin } from "tailwind-merge" | ||||
|   import { Translation } from "../i18n/Translation" | ||||
|   import { ariaLabel } from "../../Utils/ariaLabel" | ||||
|   import { ariaLabel, ariaLabelStore } from "../../Utils/ariaLabel" | ||||
|   import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
| 
 | ||||
|   /** | ||||
|  | @ -12,18 +12,21 @@ | |||
|   export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1" | ||||
|   export let enabled: Store<boolean> = new ImmutableStore(true) | ||||
|   export let arialabel: Translation = undefined | ||||
|   export let arialabelDynamic : Store<Translation> = new ImmutableStore(arialabel) | ||||
|   let arialabelString = arialabelDynamic.bind(tr => tr?.current) | ||||
|   export let htmlElem: UIEventSource<HTMLElement> = undefined | ||||
|   let _htmlElem: HTMLElement | ||||
|   $: { | ||||
|     htmlElem?.setData(_htmlElem) | ||||
|   } | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <button | ||||
|   bind:this={_htmlElem} | ||||
|   on:click={(e) => dispatch("click", e)} | ||||
|   on:keydown | ||||
|   use:ariaLabel={arialabel} | ||||
|   use:ariaLabelStore={arialabelString} | ||||
|   class={twJoin( | ||||
|     "pointer-events-auto relative h-fit w-fit rounded-full", | ||||
|     cls, | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ | |||
|   let allowMoving = geolocationstate.allowMoving | ||||
|   let currentGPSLocation = state.geolocation.geolocationState.currentGPSLocation | ||||
|   let geolocationControlState = state.geolocationControl | ||||
|   let isAvailable = state.geolocation.geolocationState.gpsAvailable | ||||
|   let lastClickWasRecent = geolocationControlState.lastClickWithinThreeSecs | ||||
| </script> | ||||
| 
 | ||||
|  | @ -31,7 +32,7 @@ | |||
| {:else if $geopermission === "requested"} | ||||
|   <!-- Even though disabled, when clicking we request the location again in case the contributor dismissed the location popup --> | ||||
|   <Location class="h-8 w-8" style="animation: 3s linear 0s infinite normal none running spin;" /> | ||||
| {:else if $geopermission === "denied"} | ||||
| {:else if $geopermission === "denied" || !$isAvailable} | ||||
|   <Location_refused class="h-8 w-8" /> | ||||
| {:else} | ||||
|   <Location class="h-8 w-8" style="animation: 3s linear 0s infinite normal none running spin;" /> | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ | |||
|     EyeIcon, | ||||
|     HeartIcon, | ||||
|     MenuIcon, | ||||
|     XCircleIcon | ||||
|     XCircleIcon, | ||||
|   } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import Tr from "./Base/Tr.svelte" | ||||
|   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" | ||||
|  | @ -120,15 +120,15 @@ | |||
|   let visualFeedback = state.visualFeedback | ||||
|   let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined) | ||||
|   let mapproperties: MapProperties = state.mapProperties | ||||
|   let usersettingslayer = new LayerConfig(<LayerConfigJson> usersettings, "usersettings", true) | ||||
|   let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true) | ||||
|   state.mapProperties.installCustomKeyboardHandler(viewport) | ||||
|   let canZoomIn = mapproperties.maxzoom.map( | ||||
|     (mz) => mapproperties.zoom.data < mz, | ||||
|     [mapproperties.zoom] | ||||
|     [mapproperties.zoom], | ||||
|   ) | ||||
|   let canZoomOut = mapproperties.minzoom.map( | ||||
|     (mz) => mapproperties.zoom.data > mz, | ||||
|     [mapproperties.zoom] | ||||
|     [mapproperties.zoom], | ||||
|   ) | ||||
| 
 | ||||
|   function updateViewport() { | ||||
|  | @ -144,7 +144,7 @@ | |||
|     const bottomRight = mlmap.unproject([rect.right, rect.bottom]) | ||||
|     const bbox = new BBox([ | ||||
|       [topLeft.lng, topLeft.lat], | ||||
|       [bottomRight.lng, bottomRight.lat] | ||||
|       [bottomRight.lng, bottomRight.lat], | ||||
|     ]) | ||||
|     state.visualFeedbackViewportBounds.setData(bbox) | ||||
|   } | ||||
|  | @ -165,7 +165,7 @@ | |||
|   onDestroy( | ||||
|     rasterLayer.addCallbackAndRunD((l) => { | ||||
|       rasterLayerName = l.properties.name | ||||
|     }) | ||||
|     }), | ||||
|   ) | ||||
|   let previewedImage = state.previewedImage | ||||
| 
 | ||||
|  | @ -196,7 +196,7 @@ | |||
|   let openMapButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined) | ||||
|   let openMenuButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined) | ||||
|   let openCurrentViewLayerButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>( | ||||
|     undefined | ||||
|     undefined, | ||||
|   ) | ||||
|   let _openNewElementButton: HTMLButtonElement | ||||
|   let openNewElementButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined) | ||||
|  | @ -207,6 +207,31 @@ | |||
|   let openFilterButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined) | ||||
|   let openBackgroundButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined) | ||||
|   let addNewFeatureMode = state.userRelatedState.addNewFeatureMode | ||||
| 
 | ||||
|   let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsAvailable.map(available => { | ||||
|     if (!available) { | ||||
|       return Translations.t.general.labels.locationNotAvailable | ||||
|     } | ||||
|     if (state.geolocation.geolocationState.permission.data === "denied") { | ||||
|       return Translations.t.general.geopermissionDenied | ||||
|     } | ||||
| 
 | ||||
|     if (state.geolocation.geolocationState.permission.data === "requested") { | ||||
|       return Translations.t.general.waitingForGeopermission | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     if (!state.geolocation.geolocationState.allowMoving.data) { | ||||
|       return Translations.t.general.visualFeedback.islocked | ||||
|     } | ||||
| 
 | ||||
|     if (state.geolocation.geolocationState.currentGPSLocation.data === undefined) { | ||||
|       return Translations.t.general.waitingForLocation | ||||
|     } | ||||
| 
 | ||||
|     return Translations.t.general.labels.jumpToLocation | ||||
|   }, [state.geolocation.geolocationState.allowMoving, state.geolocation.geolocationState.permission]) | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <main> | ||||
|  | @ -408,7 +433,7 @@ | |||
|         <If condition={featureSwitches.featureSwitchGeolocation}> | ||||
|           <div class="relative m-0"> | ||||
|             <MapControlButton | ||||
|               arialabel={Translations.t.general.labels.jumpToLocation} | ||||
|               arialabelDynamic={gpsButtonAriaLabel} | ||||
|               on:click={() => state.geolocationControl.handleClick()} | ||||
|               on:keydown={forwardEventToMap} | ||||
|             > | ||||
|  | @ -465,7 +490,7 @@ | |||
|       }} | ||||
|       > | ||||
|         <span slot="close-button" /> | ||||
|          <SelectedElementPanel absolute={false} {state} selected={$selectedElement} /> | ||||
|         <SelectedElementPanel absolute={false} {state} selected={$selectedElement} /> | ||||
|       </FloatOver> | ||||
|     {:else} | ||||
|       <FloatOver | ||||
|  | @ -525,7 +550,7 @@ | |||
|         </div> | ||||
| 
 | ||||
|         <div slot="content2" class="m-2 flex flex-col"> | ||||
|           <CopyrightPanel {state}/> | ||||
|           <CopyrightPanel {state} /> | ||||
|         </div> | ||||
| 
 | ||||
|         <div class="flex" slot="title3"> | ||||
|  | @ -659,7 +684,7 @@ | |||
|             <Tr t={Translations.t.general.menu.aboutMapComplete} /> | ||||
|           </h2> | ||||
|           <AboutMapComplete {state} /> | ||||
|           <CopyrightPanel {state}/> | ||||
|           <CopyrightPanel {state} /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </FloatOver> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue