diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 64437c831..ebb5175ae 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -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 = new UIEventSource(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 - - /** - * 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 - - /** - * The callback over the permission API - * @private - */ - private readonly _permission: UIEventSource - /** - * Literally: _currentGPSLocation.data != undefined - * @private - */ - private readonly _hasLocation: Store - private readonly _currentGPSLocation: UIEventSource - /** - * Kept in order to update the marker - * @private - */ - private readonly _leafletMap: UIEventSource - - /** - * 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 - - /** - * 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 - private readonly _layoutToUse: LayoutConfig - - constructor(state: { - selectedElement: UIEventSource - currentUserLocation?: SimpleFeatureSource - leafletMap: UIEventSource - layoutToUse: LayoutConfig - featureSwitchGeolocation: UIEventSource - }) { - const currentGPSLocation = new UIEventSource( - 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(false) - const isLocked = new UIEventSource(false) - const permission = new UIEventSource("") - const lastClick = new UIEventSource(undefined) - const lastClickWithinThreeSecs = lastClick.map((lastClick) => { - if (lastClick === undefined) { - return false + constructor( + geolocationState: GeoLocationState, + state: State // { locationControl: UIEventSource, selectedElement: UIEventSource, leafletMap?: UIEventSource }) + ) { + 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: { @@ -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(undefined); - * currentUserLocation: undefined , - * leafletMap: new UIEventSource({getZoom: () => resultingzoom; setView: (loc, zoom) => {resultingLocation = loc; resultingzoom = zoom}), - * layoutToUse: new LayoutConfig({ - * id: 'test', - * title: {"en":"test"} - * description: "A testing theme", - * layers: [] - * }), - * featureSwitchGeolocation : new UIEventSource(true) - * } - * const handler = new GeoLocationHandler(state) - * handler._currentGPSLocation.setData( {latitude : 51.3, longitude: 4.1}) - * handler.MoveToCurrentLocation() - * resultingLocation // => [51.3, 4.1] - * handler._currentGPSLocation.setData( {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(undefined); - * currentUserLocation: undefined , - * leafletMap: new UIEventSource({getZoom: () => resultingzoom; setView: (loc, zoom) => {resultingLocation = loc; resultingzoom = zoom}), - * layoutToUse: new LayoutConfig({ - * id: 'test', - * title: {"en":"test"} - * "lockLocation": [ [ 2.1, 50.4], [6.4, 51.54 ]], - * description: "A testing theme", - * layers: [] - * }), - * featureSwitchGeolocation : new UIEventSource(true) - * } - * const handler = new GeoLocationHandler(state) - * handler._currentGPSLocation.setData( {latitude : 51.3, longitude: 4.1}) - * handler.MoveToCurrentLocation() - * resultingLocation // => [51.3, 4.1] - * handler._currentGPSLocation.setData( {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, - } - ) - } } diff --git a/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 977df5e91..c2568def4 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -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" diff --git a/Logic/State/GeoLocationState.ts b/Logic/State/GeoLocationState.ts new file mode 100644 index 000000000..f70e7e212 --- /dev/null +++ b/Logic/State/GeoLocationState.ts @@ -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 = new UIEventSource("prompt") + + public readonly requestMoment: UIEventSource = new UIEventSource(undefined) + /** + * If true: the map will center (and re-center) to this location + */ + public readonly isLocked: UIEventSource = new UIEventSource(false) + + public readonly currentGPSLocation: UIEventSource = + new UIEventSource(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"> = ( + LocalStorageSource.Get("geolocation-permissions") + ) + + /** + * Used to detect a permission retraction + */ + private readonly _grantedThisSession: UIEventSource = new UIEventSource(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) + } + } +} diff --git a/UI/BigComponents/GeolocationControl.ts b/UI/BigComponents/GeolocationControl.ts new file mode 100644 index 000000000..75a7e92e6 --- /dev/null +++ b/UI/BigComponents/GeolocationControl.ts @@ -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(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) + }) + } +} diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts index d45bba768..046d5b3c8 100644 --- a/UI/BigComponents/RightControls.ts +++ b/UI/BigComponents/RightControls.ts @@ -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, diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 4071797fa..211fdec7e 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -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") diff --git a/UI/Popup/UploadToOsmViz.ts b/UI/Popup/UploadToOsmViz.ts index c592bab02..9fbd54760 100644 --- a/UI/Popup/UploadToOsmViz.ts +++ b/UI/Popup/UploadToOsmViz.ts @@ -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"