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…
Reference in a new issue