Refactor geolocation handler into multiple parts, fix lock behaviour

This commit is contained in:
Pieter Vander Vennet 2022-12-22 04:13:52 +01:00
parent e10c9e87ca
commit 4de76136de
7 changed files with 362 additions and 372 deletions

View file

@ -1,218 +1,123 @@
import { Store, UIEventSource } from "../UIEventSource"
import Svg from "../../Svg"
import { LocalStorageSource } from "../Web/LocalStorageSource"
import { VariableUiElement } from "../../UI/Base/VariableUIElement"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { QueryParameters } from "../Web/QueryParameters"
import { BBox } from "../BBox"
import Constants from "../../Models/Constants"
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"
import { GeoLocationPointProperties, GeoLocationState } from "../State/GeoLocationState"
import State from "../../State"
import { UIEventSource } from "../UIEventSource"
export interface GeoLocationPointProperties {
id: "gps"
"user:location": "yes"
date: string
latitude: number
longitude: number
speed: number
accuracy: number
heading: number
altitude: number
}
/**
* The geolocation-handler takes a map-location and a geolocation state.
* It'll move the map as appropriate given the state of the geolocation-API
*/
export default class GeoLocationHandler {
public readonly geolocationState: GeoLocationState
private readonly _state: State
public readonly mapHasMoved: UIEventSource<boolean> = new UIEventSource<boolean>(false)
export default class GeoLocationHandler extends VariableUiElement {
private readonly currentLocation?: SimpleFeatureSource
/**
* Wether or not the geolocation is active, aka the user requested the current location
*/
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
*/
private readonly _isLocked: UIEventSource<boolean>
/**
* The callback over the permission API
* @private
*/
private readonly _permission: UIEventSource<string>
/**
* Literally: _currentGPSLocation.data != undefined
* @private
*/
private readonly _hasLocation: Store<boolean>
private readonly _currentGPSLocation: UIEventSource<GeolocationCoordinates>
/**
* Kept in order to update the marker
* @private
*/
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
*/
private _lastUserRequest: UIEventSource<Date>
/**
* 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.
*
* 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
* @private
*/
private readonly _previousLocationGrant: UIEventSource<string>
private readonly _layoutToUse: LayoutConfig
constructor(state: {
selectedElement: UIEventSource<any>
currentUserLocation?: SimpleFeatureSource
leafletMap: UIEventSource<any>
layoutToUse: LayoutConfig
featureSwitchGeolocation: UIEventSource<boolean>
}) {
const currentGPSLocation = new UIEventSource<GeolocationCoordinates>(
undefined,
"GPS-coordinate"
)
const leafletMap = state.leafletMap
const initedAt = new Date()
let autozoomDone = false
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)
const permission = new UIEventSource<string>("")
const lastClick = new UIEventSource<Date>(undefined)
const lastClickWithinThreeSecs = lastClick.map((lastClick) => {
if (lastClick === undefined) {
return false
constructor(
geolocationState: GeoLocationState,
state: State // { locationControl: UIEventSource<Loc>, selectedElement: UIEventSource<any>, leafletMap?: UIEventSource<any> })
) {
this.geolocationState = geolocationState
this._state = state
const mapLocation = state.locationControl
// Did an interaction move the map?
let self = this
let initTime = new Date()
mapLocation.addCallbackD((_) => {
if (new Date().getTime() - initTime.getTime() < 250) {
return
}
const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000
return timeDiff <= 3
self.mapHasMoved.setData(true)
return true // Unsubscribe
})
const latLonGiven =
QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon")
const willFocus = lastClick.map((lastUserRequest) => {
const timeDiffInited = (new Date().getTime() - initedAt.getTime()) / 1000
if (!latLonGiven && !autozoomDone && timeDiffInited < Constants.zoomToLocationTimeout) {
return true
}
if (lastUserRequest === undefined) {
return false
}
const timeDiff = (new Date().getTime() - lastUserRequest.getTime()) / 1000
return timeDiff <= Constants.zoomToLocationTimeout
})
const latLonGivenViaUrl =
QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon")
if (latLonGivenViaUrl) {
// The URL counts as a 'user interaction'
this.mapHasMoved.setData(true)
}
lastClick.addCallbackAndRunD((_) => {
window.setTimeout(() => {
if (lastClickWithinThreeSecs.data || willFocus.data) {
lastClick.ping()
this.geolocationState.currentGPSLocation.addCallbackAndRunD((newLocation) => {
const timeSinceLastRequest =
(new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000
if (!this.mapHasMoved.data) {
// The map hasn't moved yet; we received our first coordinates, so let's move there!
console.log(
"Moving the map to an initial location; time since last request is",
timeSinceLastRequest
)
if (timeSinceLastRequest < Constants.zoomToLocationTimeout) {
self.MoveMapToCurrentLocation()
}
}, 500)
}
if (this.geolocationState.isLocked.data) {
// Jup, the map is locked to the bound location: move automatically
self.MoveMapToCurrentLocation()
return
}
})
super(
hasLocation.map(
(hasLocationData) => {
if (permission.data === "denied") {
return Svg.location_refused_svg()
}
if (!isActive.data) {
return Svg.location_empty_svg()
}
if (!hasLocationData) {
// Position not yet found but we are active: we spin to indicate activity
// If will focus is active too, we indicate this differently
const icon = willFocus.data ? Svg.location_svg() : Svg.location_empty_svg()
icon.SetStyle("animation: spin 4s linear infinite;")
return icon
}
if (isLocked.data) {
return Svg.location_locked_svg()
}
if (lastClickWithinThreeSecs.data) {
return Svg.location_unlocked_svg()
}
// We have a location, so we show a dot in the center
return Svg.location_svg()
},
[isActive, isLocked, permission, lastClickWithinThreeSecs, willFocus]
)
)
this.SetClass("mapcontrol")
this._isActive = isActive
this._isLocked = isLocked
this._permission = permission
this._previousLocationGrant = previousLocationGrant
this._currentGPSLocation = currentGPSLocation
this._leafletMap = leafletMap
this._layoutToUse = state.layoutToUse
this._hasLocation = hasLocation
this._lastUserRequest = lastClick
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.RemoveClass("cursor-wait")
self.RemoveClass("cursor-pointer")
self.SetClass(pointerClass)
})
this.onClick(() => {
/*
* If the previous click was within 3 seconds (and we have an active location), then we lock to the location
*/
if (self._hasLocation.data) {
if (isLocked.data) {
isLocked.setData(false)
} else if (lastClick.data !== undefined) {
const timeDiff = (new Date().getTime() - lastClick.data.getTime()) / 1000
if (timeDiff <= 3) {
isLocked.setData(true)
lastClick.setData(undefined)
} else {
lastClick.setData(new Date())
}
geolocationState.isLocked.map(
(isLocked) => {
if (isLocked) {
state.leafletMap?.data?.dragging?.disable()
} else {
lastClick.setData(new Date())
state.leafletMap?.data?.dragging?.enable()
}
},
[state.leafletMap]
)
this.CopyGeolocationIntoMapstate()
}
/**
* Move the map to the GPS-location, except:
* - If there is a selected element
* - The location is out of the locked bound
* - The GPS-location iss NULL-island
* @constructor
*/
public MoveMapToCurrentLocation() {
const newLocation = this.geolocationState.currentGPSLocation.data
const mapLocation = this._state.locationControl
const state = this._state
// We got a new location.
// Do we move the map to it?
if (state.selectedElement.data !== undefined) {
// Nope, there is something selected, so we don't move to the current GPS-location
return
}
if (newLocation.latitude === 0 && newLocation.longitude === 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 bounds = state.layoutToUse.lockLocation
if (bounds && bounds !== true) {
// B is an array with our lock-location
const inRange = new BBox(bounds).contains([newLocation.longitude, newLocation.latitude])
if (!inRange) {
return
}
}
self.init(true, true)
mapLocation.setData({
zoom: mapLocation.data.zoom,
lon: newLocation.longitude,
lat: newLocation.latitude,
})
this.mapHasMoved.setData(true)
}
const doAutoZoomToLocation =
!latLonGiven &&
state.featureSwitchGeolocation.data &&
state.selectedElement.data !== undefined
this.init(false, doAutoZoomToLocation)
isLocked.addCallbackAndRunD((isLocked) => {
if (isLocked) {
leafletMap.data?.dragging?.disable()
} else {
leafletMap.data?.dragging?.enable()
}
})
this.currentLocation = state.currentUserLocation
this._currentGPSLocation.addCallback((location) => {
self._previousLocationGrant.setData("granted")
private CopyGeolocationIntoMapstate() {
const state = this._state
this.geolocationState.currentGPSLocation.addCallback((location) => {
const feature = {
type: "Feature",
properties: <GeoLocationPointProperties>{
@ -232,164 +137,7 @@ export default class GeoLocationHandler extends VariableUiElement {
},
}
self.currentLocation?.features?.setData([{ feature, freshness: new Date() }])
if (willFocus.data) {
console.log("Zooming to user location: willFocus is set")
lastClick.setData(undefined)
autozoomDone = true
self.MoveToCurrentLocation(16)
} else if (self._isLocked.data) {
self.MoveToCurrentLocation()
}
state.currentUserLocation?.features?.setData([{ feature, freshness: new Date() }])
})
}
private init(askPermission: boolean, zoomToLocation: boolean) {
const self = this
if (self._isActive.data) {
self.MoveToCurrentLocation(16)
return
}
if (typeof navigator === "undefined") {
return
}
try {
navigator?.permissions?.query({ name: "geolocation" })?.then(function (status) {
console.log("Geolocation permission is ", status.state)
if (status.state === "granted") {
self.StartGeolocating(zoomToLocation)
}
self._permission.setData(status.state)
status.onchange = function () {
self._permission.setData(status.state)
}
})
} catch (e) {
console.error(e)
}
if (askPermission) {
self.StartGeolocating(zoomToLocation)
} else if (this._previousLocationGrant.data === "granted") {
this._previousLocationGrant.setData("")
self.StartGeolocating(zoomToLocation)
}
}
/**
* Moves to the currently loaded location.
*
* // Should move to any location
* let resultingLocation = undefined
* let resultingzoom = 1
* const state = {
* selectedElement: new UIEventSource<any>(undefined);
* currentUserLocation: undefined ,
* leafletMap: new UIEventSource<any>({getZoom: () => resultingzoom; setView: (loc, zoom) => {resultingLocation = loc; resultingzoom = zoom}),
* layoutToUse: new LayoutConfig(<any>{
* id: 'test',
* title: {"en":"test"}
* description: "A testing theme",
* layers: []
* }),
* featureSwitchGeolocation : new UIEventSource<boolean>(true)
* }
* const handler = new GeoLocationHandler(state)
* handler._currentGPSLocation.setData(<any> {latitude : 51.3, longitude: 4.1})
* handler.MoveToCurrentLocation()
* resultingLocation // => [51.3, 4.1]
* handler._currentGPSLocation.setData(<any> {latitude : 60, longitude: 60) // out of bounds
* handler.MoveToCurrentLocation()
* resultingLocation // => [60, 60]
*
* // should refuse to move if out of bounds
* let resultingLocation = undefined
* let resultingzoom = 1
* const state = {
* selectedElement: new UIEventSource<any>(undefined);
* currentUserLocation: undefined ,
* leafletMap: new UIEventSource<any>({getZoom: () => resultingzoom; setView: (loc, zoom) => {resultingLocation = loc; resultingzoom = zoom}),
* layoutToUse: new LayoutConfig(<any>{
* id: 'test',
* title: {"en":"test"}
* "lockLocation": [ [ 2.1, 50.4], [6.4, 51.54 ]],
* description: "A testing theme",
* layers: []
* }),
* featureSwitchGeolocation : new UIEventSource<boolean>(true)
* }
* const handler = new GeoLocationHandler(state)
* handler._currentGPSLocation.setData(<any> {latitude : 51.3, longitude: 4.1})
* handler.MoveToCurrentLocation()
* resultingLocation // => [51.3, 4.1]
* handler._currentGPSLocation.setData(<any> {latitude : 60, longitude: 60) // out of bounds
* handler.MoveToCurrentLocation()
* resultingLocation // => [51.3, 4.1]
*/
private MoveToCurrentLocation(targetZoom?: number) {
const location = this._currentGPSLocation.data
this._lastUserRequest.setData(undefined)
if (
this._currentGPSLocation.data.latitude === 0 &&
this._currentGPSLocation.data.longitude === 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.lockLocation
let inRange = true
if (b) {
if (b !== true) {
// B is an array with our locklocation
inRange = new BBox(b).contains([location.longitude, location.latitude])
}
}
if (!inRange) {
console.log("Not zooming to GPS location: out of bounds", b, location)
} else {
const currentZoom = this._leafletMap.data.getZoom()
this._leafletMap.data.setView(
[location.latitude, location.longitude],
Math.max(targetZoom ?? 0, currentZoom)
)
}
}
private StartGeolocating(zoomToGPS = true) {
const self = this
this._lastUserRequest.setData(zoomToGPS ? new Date() : new Date(0))
if (self._permission.data === "denied") {
self._previousLocationGrant.setData("")
self._isActive.setData(false)
return ""
}
if (this._currentGPSLocation.data !== undefined) {
this.MoveToCurrentLocation(16)
}
if (self._isActive.data) {
return
}
self._isActive.setData(true)
navigator.geolocation.watchPosition(
function (position) {
self._currentGPSLocation.setData(position.coords)
},
function () {
console.warn("Could not get location with navigator.geolocation")
},
{
enableHighAccuracy: true,
}
)
}
}

View file

@ -8,7 +8,7 @@ import { LocalStorageSource } from "../Web/LocalStorageSource"
import SimpleMetaTagger from "../SimpleMetaTagger"
import FeatureSource from "../FeatureSource/FeatureSource"
import { ElementStorage } from "../ElementStorage"
import { GeoLocationPointProperties } from "../Actors/GeoLocationHandler"
import { GeoLocationPointProperties } from "../State/GeoLocationState"
import { GeoOperations } from "../GeoOperations"
import { ChangesetHandler, ChangesetTag } from "./ChangesetHandler"
import { OsmConnection } from "./OsmConnection"

View file

@ -0,0 +1,138 @@
import { UIEventSource } from "../UIEventSource"
import { LocalStorageSource } from "../Web/LocalStorageSource"
type GeolocationState = "prompt" | "requested" | "granted" | "denied"
export interface GeoLocationPointProperties extends GeolocationCoordinates {
id: "gps"
"user:location": "yes"
date: string
}
/**
* An abstract representation of the current state of the geolocation.
*
*
*/
export class GeoLocationState {
/**
* What do we know about the current state of having access to the GPS?
* If 'prompt', then we just started and didn't request access yet
* 'requested' means the user tapped the 'locate me' button at least once
* 'granted' means that it is granted
* 'denied' means that we don't have access
*
*/
public readonly permission: UIEventSource<GeolocationState> = new UIEventSource("prompt")
public readonly requestMoment: UIEventSource<Date | undefined> = new UIEventSource(undefined)
/**
* If true: the map will center (and re-center) to this location
*/
public readonly isLocked: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> =
new UIEventSource<GeolocationCoordinates | undefined>(undefined)
/**
* 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.
*
* 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
* @private
*/
private readonly _previousLocationGrant: UIEventSource<"true" | "false"> = <any>(
LocalStorageSource.Get("geolocation-permissions")
)
/**
* Used to detect a permission retraction
*/
private readonly _grantedThisSession: UIEventSource<boolean> = new UIEventSource<boolean>(false)
constructor() {
const self = this
this.permission.addCallbackAndRunD(async (state) => {
if (state === "granted") {
self._previousLocationGrant.setData("true")
self._grantedThisSession.setData(true)
await self.startWatching()
}
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.permission.setData("denied")
self._previousLocationGrant.setData("false")
self.permission.setData("denied")
console.warn("Detected a downgrade in permissions!")
}
if (state === "denied") {
self._previousLocationGrant.setData("false")
}
})
if (this._previousLocationGrant.data === "true") {
// 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")
console.log("Requesting access to GPS as this was previously granted")
this.requestPermission()
}
}
/**
* Installs the listener for updates
* @private
*/
private async startWatching() {
const self = this
navigator.geolocation.watchPosition(
function (position) {
self.currentGPSLocation.setData(position.coords)
},
function () {
console.warn("Could not get location with navigator.geolocation")
},
{
enableHighAccuracy: true,
}
)
}
/**
* Requests the user to allow access to their position.
* When granted, will be written to the 'geolocationState'.
* This class will start watching
*/
public requestPermission() {
if (typeof navigator === "undefined") {
// Not compatible with this browser
this.permission.setData("denied")
return
}
if (this.permission.data !== "prompt") {
return
}
this.requestMoment.setData(new Date())
this.permission.setData("requested")
try {
navigator?.permissions
?.query({ name: "geolocation" })
.then((status) => {
console.log("Geolocation permission is ", status.state)
this.permission.setData(status.state)
const self = this
status.onchange = function () {
self.permission.setData(status.state)
}
// We _must_ call 'startWatching', as that is the actual trigger for the popup...
self.startWatching()
})
.catch((e) => console.error("Could not get geopermission", e))
} catch (e) {
console.error("Could not get permission:", e)
}
}
}

View file

@ -0,0 +1,99 @@
import { VariableUiElement } from "../Base/VariableUIElement"
import Svg from "../../Svg"
import { UIEventSource } from "../../Logic/UIEventSource"
import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"
/**
* Displays an icon depending on the state of the geolocation.
* Will set the 'lock' if clicked twice
*/
export class GeolocationControl extends VariableUiElement {
constructor(geolocationHandler: GeoLocationHandler) {
const lastClick = new UIEventSource<Date>(undefined)
const lastClickWithinThreeSecs = lastClick.map((lastClick) => {
if (lastClick === undefined) {
return false
}
const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000
return timeDiff <= 3
})
const geolocationState = geolocationHandler.geolocationState
super(
geolocationState.permission.map(
(permission) => {
if (permission === "denied") {
return Svg.location_refused_svg()
}
if (geolocationState.isLocked.data) {
return Svg.location_locked_svg()
}
if (permission === "prompt") {
return Svg.location_empty_svg()
}
if (geolocationState.currentGPSLocation === undefined) {
// Position not yet found, but permission is either requested or granted: we spin to indicate activity
const icon = !geolocationHandler.mapHasMoved.data
? Svg.location_svg()
: Svg.location_empty_svg()
return icon
.SetClass("cursor-wait")
.SetStyle("animation: spin 4s linear infinite;")
}
if (
lastClickWithinThreeSecs.data &&
geolocationState.permission.data === "granted"
) {
return Svg.location_unlocked_svg()
}
// We have a location, so we show a dot in the center
return Svg.location_svg()
},
[
geolocationState.currentGPSLocation,
geolocationState.isLocked,
geolocationHandler.mapHasMoved,
lastClickWithinThreeSecs,
]
)
)
this.onClick(async () => {
if (geolocationState.permission.data !== "granted") {
await geolocationState.requestPermission()
}
if (geolocationState.isLocked.data === true) {
// Unlock
geolocationState.isLocked.setData(false)
return
}
if (geolocationState.currentGPSLocation.data === undefined) {
// No location is known yet, not much we can do
return
}
// A location _is_ known! Let's zoom to this location
geolocationHandler.MoveMapToCurrentLocation()
if (lastClickWithinThreeSecs.data && geolocationState.permission.data === "granted") {
geolocationState.isLocked.setData(true)
lastClick.setData(undefined)
return
}
lastClick.setData(new Date())
})
lastClick.addCallbackAndRunD((_) => {
window.setTimeout(() => {
if (lastClickWithinThreeSecs.data) {
lastClick.ping()
}
}, 500)
})
}
}

View file

@ -5,18 +5,16 @@ import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"
import Svg from "../../Svg"
import MapState from "../../Logic/State/MapState"
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"
import { Utils } from "../../Utils"
import { TagUtils } from "../../Logic/Tags/TagUtils"
import { BBox } from "../../Logic/BBox"
import { OsmFeature } from "../../Models/OsmFeature"
import LevelSelector from "./LevelSelector"
import { GeolocationControl } from "./GeolocationControl"
export default class RightControls extends Combine {
constructor(state: MapState & { featurePipeline: FeaturePipeline }) {
const geolocatioHandler = new GeoLocationHandler(state)
constructor(
state: MapState & { featurePipeline: FeaturePipeline },
geolocationHandler: GeoLocationHandler
) {
const geolocationButton = new Toggle(
new MapControlButton(geolocatioHandler, {
new MapControlButton(new GeolocationControl(geolocationHandler), {
dontStyle: true,
}).SetClass("p-1"),
undefined,

View file

@ -29,6 +29,8 @@ import Img from "./Base/Img"
import UserInformationPanel from "./BigComponents/UserInformation"
import { LoginToggle } from "./Popup/LoginButton"
import { FixedUiElement } from "./Base/FixedUiElement"
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import { GeoLocationState } from "../Logic/State/GeoLocationState"
/**
* The default MapComplete GUI initializer
@ -38,10 +40,14 @@ import { FixedUiElement } from "./Base/FixedUiElement"
export default class DefaultGUI {
private readonly guiState: DefaultGuiState
private readonly state: FeaturePipelineState
private readonly geolocationHandler: GeoLocationHandler | undefined
constructor(state: FeaturePipelineState, guiState: DefaultGuiState) {
this.state = state
this.guiState = guiState
if (this.state.featureSwitchGeolocation.data) {
this.geolocationHandler = new GeoLocationHandler(new GeoLocationState(), state)
}
}
public setup() {
@ -231,15 +237,16 @@ export default class DefaultGUI {
.SetClass("flex items-center justify-center normal-background h-full")
.AttachTo("on-small-screen")
new Combine([Toggle.If(state.featureSwitchSearch, () =>
new SearchAndGo(state).SetClass(
"shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto"
)
)])
.AttachTo("top-right")
new Combine([
Toggle.If(state.featureSwitchSearch, () =>
new SearchAndGo(state).SetClass(
"shadow rounded-full h-min w-full overflow-hidden sm:max-w-sm pointer-events-auto"
)
),
]).AttachTo("top-right")
new LeftControls(state, guiState).AttachTo("bottom-left")
new RightControls(state).AttachTo("bottom-right")
new RightControls(state, this.geolocationHandler).AttachTo("bottom-right")
new CenterMessageBox(state).AttachTo("centermessage")
document.getElementById("centermessage").classList.add("pointer-events-none")

View file

@ -1,7 +1,7 @@
import { Utils } from "../../Utils"
import { Feature } from "geojson"
import { Point } from "@turf/turf"
import { GeoLocationPointProperties } from "../../Logic/Actors/GeoLocationHandler"
import { GeoLocationPointProperties } from "../../Logic/State/GeoLocationState"
import UploadTraceToOsmUI from "../BigComponents/UploadTraceToOsmUI"
import { SpecialVisualization } from "../SpecialVisualization"