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