forked from MapComplete/MapComplete
refactoring(maplibre): WIP
This commit is contained in:
parent
231d67361e
commit
4d48b1cf2b
89 changed files with 1166 additions and 3973 deletions
|
@ -1,91 +0,0 @@
|
|||
import FeatureSwitchState from "./FeatureSwitchState"
|
||||
import { ElementStorage } from "../ElementStorage"
|
||||
import { Changes } from "../Osm/Changes"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { UIEventSource } from "../UIEventSource"
|
||||
import Loc from "../../Models/Loc"
|
||||
import { BBox } from "../BBox"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import { Utils } from "../../Utils"
|
||||
import ChangeToElementsActor from "../Actors/ChangeToElementsActor"
|
||||
import PendingChangesUploader from "../Actors/PendingChangesUploader"
|
||||
|
||||
/**
|
||||
* The part of the state keeping track of where the elements, loading them, configuring the feature pipeline etc
|
||||
*/
|
||||
export default class ElementsState extends FeatureSwitchState {
|
||||
/**
|
||||
The mapping from id -> UIEventSource<properties>
|
||||
*/
|
||||
public allElements: ElementStorage = new ElementStorage()
|
||||
|
||||
/**
|
||||
The latest element that was selected
|
||||
*/
|
||||
public readonly selectedElement = new UIEventSource<any>(undefined, "Selected element")
|
||||
|
||||
/**
|
||||
* The map location: currently centered lat, lon and zoom
|
||||
*/
|
||||
public readonly locationControl = new UIEventSource<Loc>(undefined, "locationControl")
|
||||
|
||||
/**
|
||||
* The current visible extent of the screen
|
||||
*/
|
||||
public readonly currentBounds = new UIEventSource<BBox>(undefined)
|
||||
|
||||
constructor(layoutToUse: LayoutConfig) {
|
||||
super(layoutToUse)
|
||||
|
||||
function localStorageSynced(
|
||||
key: string,
|
||||
deflt: number,
|
||||
docs: string
|
||||
): UIEventSource<number> {
|
||||
const localStorage = LocalStorageSource.Get(key)
|
||||
const previousValue = localStorage.data
|
||||
const src = UIEventSource.asFloat(
|
||||
QueryParameters.GetQueryParameter(key, "" + deflt, docs).syncWith(localStorage)
|
||||
)
|
||||
|
||||
if (src.data === deflt) {
|
||||
const prev = Number(previousValue)
|
||||
if (!isNaN(prev)) {
|
||||
src.setData(prev)
|
||||
}
|
||||
}
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
// -- Location control initialization
|
||||
const zoom = localStorageSynced(
|
||||
"z",
|
||||
layoutToUse?.startZoom ?? 1,
|
||||
"The initial/current zoom level"
|
||||
)
|
||||
const lat = localStorageSynced(
|
||||
"lat",
|
||||
layoutToUse?.startLat ?? 0,
|
||||
"The initial/current latitude"
|
||||
)
|
||||
const lon = localStorageSynced(
|
||||
"lon",
|
||||
layoutToUse?.startLon ?? 0,
|
||||
"The initial/current longitude of the app"
|
||||
)
|
||||
|
||||
this.locationControl.setData({
|
||||
zoom: Utils.asFloat(zoom.data),
|
||||
lat: Utils.asFloat(lat.data),
|
||||
lon: Utils.asFloat(lon.data),
|
||||
})
|
||||
this.locationControl.addCallback((latlonz) => {
|
||||
// Sync the location controls
|
||||
zoom.setData(latlonz.zoom)
|
||||
lat.setData(latlonz.lat)
|
||||
lon.setData(latlonz.lon)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import FeaturePipeline from "../FeatureSource/FeaturePipeline"
|
||||
import { Tiles } from "../../Models/TileRange"
|
||||
import ShowDataLayer from "../../UI/ShowDataLayer/ShowDataLayer"
|
||||
import { TileHierarchyAggregator } from "../../UI/ShowDataLayer/TileHierarchyAggregator"
|
||||
import ShowTileInfo from "../../UI/ShowDataLayer/ShowTileInfo"
|
||||
import { UIEventSource } from "../UIEventSource"
|
||||
import MapState from "./MapState"
|
||||
import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler"
|
||||
|
@ -14,6 +12,7 @@ import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
|
|||
import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator"
|
||||
import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
|
||||
|
||||
export default class FeaturePipelineState extends MapState {
|
||||
/**
|
||||
|
@ -116,14 +115,12 @@ export default class FeaturePipelineState extends MapState {
|
|||
[self.currentBounds, source.layer.isDisplayed, sourceBBox]
|
||||
)
|
||||
|
||||
new ShowDataLayer({
|
||||
new ShowDataLayer(self.maplibreMap, {
|
||||
features: source,
|
||||
leafletMap: self.leafletMap,
|
||||
layerToShow: source.layer.layerDef,
|
||||
layer: source.layer.layerDef,
|
||||
doShowLayer: doShowFeatures,
|
||||
selectedElement: self.selectedElement,
|
||||
state: self,
|
||||
popup: (tags, layer) => self.CreatePopup(tags, layer),
|
||||
buildPopup: (tags, layer) => self.CreatePopup(tags, layer),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -136,8 +133,6 @@ export default class FeaturePipelineState extends MapState {
|
|||
sourcesToRegister.forEach((source) => self.metatagRecalculator.registerSource(source))
|
||||
|
||||
new SelectedFeatureHandler(Hash.hash, this)
|
||||
|
||||
this.AddClusteringToMap(this.leafletMap)
|
||||
}
|
||||
|
||||
public CreatePopup(tags: UIEventSource<any>, layer: LayerConfig): ScrollableFullScreen {
|
||||
|
@ -148,27 +143,4 @@ export default class FeaturePipelineState extends MapState {
|
|||
this.popups.set(tags.data.id, popup)
|
||||
return popup
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the cluster-tiles to the given map
|
||||
* @param leafletMap: a UIEventSource possible having a leaflet map
|
||||
* @constructor
|
||||
*/
|
||||
public AddClusteringToMap(leafletMap: UIEventSource<any>) {
|
||||
const clustering = this.layoutToUse.clustering
|
||||
const self = this
|
||||
new ShowDataLayer({
|
||||
features: this.featureAggregator.getCountsForZoom(
|
||||
clustering,
|
||||
this.locationControl,
|
||||
clustering.minNeededElements
|
||||
),
|
||||
leafletMap: leafletMap,
|
||||
layerToShow: ShowTileInfo.styling,
|
||||
popup: this.featureSwitchIsDebugging.data
|
||||
? (tags, layer) => new FeatureInfoBox(tags, layer, self)
|
||||
: undefined,
|
||||
state: this,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export class GeoLocationState {
|
|||
/**
|
||||
* If true: the map will center (and re-center) to this location
|
||||
*/
|
||||
public readonly isLocked: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
public readonly allowMoving: UIEventSource<boolean> = new UIEventSource<boolean>(true)
|
||||
|
||||
public readonly currentGPSLocation: UIEventSource<GeolocationCoordinates | undefined> =
|
||||
new UIEventSource<GeolocationCoordinates | undefined>(undefined)
|
||||
|
@ -72,7 +72,6 @@ export class GeoLocationState {
|
|||
self._previousLocationGrant.setData("false")
|
||||
}
|
||||
})
|
||||
|
||||
console.log("Previous location grant:", this._previousLocationGrant.data)
|
||||
if (this._previousLocationGrant.data === "true") {
|
||||
// A previous visit successfully granted permission. Chance is high that we are allowed to use it again!
|
||||
|
@ -87,7 +86,6 @@ export class GeoLocationState {
|
|||
}
|
||||
this.requestPermission()
|
||||
}
|
||||
window["geolocation_state"] = this
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,51 +1,31 @@
|
|||
import UserRelatedState from "./UserRelatedState"
|
||||
import { Store, Stores, UIEventSource } from "../UIEventSource"
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
import Attribution from "../../UI/BigComponents/Attribution"
|
||||
import Minimap, { MinimapObj } from "../../UI/Base/Minimap"
|
||||
import { Tiles } from "../../Models/TileRange"
|
||||
import BaseUIElement from "../../UI/BaseUIElement"
|
||||
import FilteredLayer, { FilterState } from "../../Models/FilteredLayer"
|
||||
import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
import ShowOverlayLayer from "../../UI/ShowDataLayer/ShowOverlayLayer"
|
||||
import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
|
||||
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"
|
||||
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
||||
import { GeoOperations } from "../GeoOperations"
|
||||
import TitleHandler from "../Actors/TitleHandler"
|
||||
import { BBox } from "../BBox"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { TiledStaticFeatureSource } from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import { Translation, TypedTranslation } from "../../UI/i18n/Translation"
|
||||
import { Tag } from "../Tags/Tag"
|
||||
import StaticFeatureSource, {
|
||||
TiledStaticFeatureSource,
|
||||
} from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
import { Feature, LineString } from "geojson"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
|
||||
export interface GlobalFilter {
|
||||
filter: FilterState
|
||||
id: string
|
||||
onNewPoint: {
|
||||
safetyCheck: Translation
|
||||
confirmAddNew: TypedTranslation<{ preset: Translation }>
|
||||
tags: Tag[]
|
||||
}
|
||||
}
|
||||
import { Feature } from "geojson"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { GlobalFilter } from "../../Models/GlobalFilter"
|
||||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
|
||||
|
||||
/**
|
||||
* Contains all the leaflet-map related state
|
||||
*/
|
||||
export default class MapState extends UserRelatedState {
|
||||
/**
|
||||
The leaflet instance of the big basemap
|
||||
*/
|
||||
public leafletMap = new UIEventSource<any /*L.Map*/>(undefined, "leafletmap")
|
||||
export default class MapState {
|
||||
|
||||
|
||||
/**
|
||||
* The current background layer
|
||||
*/
|
||||
public backgroundLayer: UIEventSource<BaseLayer>
|
||||
/**
|
||||
* Last location where a click was registered
|
||||
*/
|
||||
|
@ -58,34 +38,6 @@ export default class MapState extends UserRelatedState {
|
|||
* The bounds of the current map view
|
||||
*/
|
||||
public currentView: FeatureSourceForLayer & Tiled
|
||||
/**
|
||||
* The location as delivered by the GPS
|
||||
*/
|
||||
public currentUserLocation: SimpleFeatureSource
|
||||
|
||||
/**
|
||||
* All previously visited points, with their metadata
|
||||
*/
|
||||
public historicalUserLocations: SimpleFeatureSource
|
||||
/**
|
||||
* The number of seconds that the GPS-locations are stored in memory.
|
||||
* Time in seconds
|
||||
*/
|
||||
public gpsLocationHistoryRetentionTime = new UIEventSource(
|
||||
7 * 24 * 60 * 60,
|
||||
"gps_location_retention"
|
||||
)
|
||||
/**
|
||||
* A featureSource containing a single linestring which has the GPS-history of the user.
|
||||
* However, metadata (such as when every single point was visited) is lost here (but is kept in `historicalUserLocations`.
|
||||
* Note that this featureSource is _derived_ from 'historicalUserLocations'
|
||||
*/
|
||||
public historicalUserLocationsTrack: FeatureSourceForLayer & Tiled
|
||||
|
||||
/**
|
||||
* A feature source containing the current home location of the user
|
||||
*/
|
||||
public homeLocation: FeatureSourceForLayer & Tiled
|
||||
|
||||
/**
|
||||
* A builtin layer which contains the selected element.
|
||||
|
@ -94,7 +46,7 @@ export default class MapState extends UserRelatedState {
|
|||
*/
|
||||
public selectedElementsLayer: FeatureSourceForLayer & Tiled
|
||||
|
||||
public readonly mainMapObject: BaseUIElement & MinimapObj
|
||||
public readonly mainMapObject: BaseUIElement
|
||||
|
||||
/**
|
||||
* Which layers are enabled in the current theme and what filters are applied onto them
|
||||
|
@ -114,9 +66,7 @@ export default class MapState extends UserRelatedState {
|
|||
*/
|
||||
public overlayToggles: { config: TilesourceConfig; isDisplayed: UIEventSource<boolean> }[]
|
||||
|
||||
constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) {
|
||||
super(layoutToUse, options)
|
||||
|
||||
constructor() {
|
||||
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl)
|
||||
|
||||
let defaultLayer = AvailableBaseLayers.osmCarto
|
||||
|
@ -130,13 +80,6 @@ export default class MapState extends UserRelatedState {
|
|||
this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer)
|
||||
this.backgroundLayer.addCallbackAndRunD((layer) => self.backgroundLayerId.setData(layer.id))
|
||||
|
||||
const attr = new Attribution(
|
||||
this.locationControl,
|
||||
this.osmConnection.userDetails,
|
||||
this.layoutToUse,
|
||||
this.currentBounds
|
||||
)
|
||||
|
||||
// Will write into this.leafletMap
|
||||
this.mainMapObject = Minimap.createMiniMap({
|
||||
background: this.backgroundLayer,
|
||||
|
@ -162,12 +105,8 @@ export default class MapState extends UserRelatedState {
|
|||
MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection)
|
||||
)
|
||||
|
||||
this.lockBounds()
|
||||
this.AddAllOverlaysToMap(this.leafletMap)
|
||||
|
||||
this.initHomeLocation()
|
||||
this.initGpsLocation()
|
||||
this.initUserLocationTrail()
|
||||
this.initCurrentView()
|
||||
this.initSelectedElement()
|
||||
|
||||
|
@ -189,17 +128,6 @@ export default class MapState extends UserRelatedState {
|
|||
}
|
||||
}
|
||||
|
||||
private lockBounds() {
|
||||
const layout = this.layoutToUse
|
||||
if (!layout?.lockLocation) {
|
||||
return
|
||||
}
|
||||
console.warn("Locking the bounds to ", layout.lockLocation)
|
||||
this.mainMapObject.installBounds(
|
||||
new BBox(layout.lockLocation),
|
||||
this.featureSwitchIsTesting.data
|
||||
)
|
||||
}
|
||||
|
||||
private initCurrentView() {
|
||||
let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter(
|
||||
|
@ -244,17 +172,6 @@ export default class MapState extends UserRelatedState {
|
|||
this.currentView = new TiledStaticFeatureSource(features, currentViewLayer)
|
||||
}
|
||||
|
||||
private initGpsLocation() {
|
||||
// Initialize the gps layer data. This is emtpy for now, the actual writing happens in the Geolocationhandler
|
||||
const gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(
|
||||
(l) => l.layerDef.id === "gps_location"
|
||||
)[0]
|
||||
if (gpsLayerDef === undefined) {
|
||||
return
|
||||
}
|
||||
this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0))
|
||||
}
|
||||
|
||||
private initSelectedElement() {
|
||||
const layerDef: FilteredLayer = this.filteredLayers.data.filter(
|
||||
(l) => l.layerDef.id === "selected_element"
|
||||
|
@ -281,145 +198,6 @@ export default class MapState extends UserRelatedState {
|
|||
this.selectedElementsLayer = new TiledStaticFeatureSource(store, layerDef)
|
||||
}
|
||||
|
||||
private initUserLocationTrail() {
|
||||
const features = LocalStorageSource.GetParsed<{ feature: any; freshness: Date }[]>(
|
||||
"gps_location_history",
|
||||
[]
|
||||
)
|
||||
const now = new Date().getTime()
|
||||
features.data = features.data
|
||||
.map((ff) => ({ feature: ff.feature, freshness: new Date(ff.freshness) }))
|
||||
.filter(
|
||||
(ff) =>
|
||||
now - ff.freshness.getTime() < 1000 * this.gpsLocationHistoryRetentionTime.data
|
||||
)
|
||||
features.ping()
|
||||
const self = this
|
||||
let i = 0
|
||||
this.currentUserLocation?.features?.addCallbackAndRunD(([location]) => {
|
||||
if (location === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const previousLocation = features.data[features.data.length - 1]
|
||||
if (previousLocation !== undefined) {
|
||||
const d = GeoOperations.distanceBetween(
|
||||
previousLocation.feature.geometry.coordinates,
|
||||
location.feature.geometry.coordinates
|
||||
)
|
||||
let timeDiff = Number.MAX_VALUE // in seconds
|
||||
const olderLocation = features.data[features.data.length - 2]
|
||||
if (olderLocation !== undefined) {
|
||||
timeDiff =
|
||||
(new Date(previousLocation.freshness).getTime() -
|
||||
new Date(olderLocation.freshness).getTime()) /
|
||||
1000
|
||||
}
|
||||
if (d < 20 && timeDiff < 60) {
|
||||
// Do not append changes less then 20m - it's probably noise anyway
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const feature = JSON.parse(JSON.stringify(location.feature))
|
||||
feature.properties.id = "gps/" + features.data.length
|
||||
i++
|
||||
features.data.push({ feature, freshness: new Date() })
|
||||
features.ping()
|
||||
})
|
||||
|
||||
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(
|
||||
(l) => l.layerDef.id === "gps_location_history"
|
||||
)[0]
|
||||
if (gpsLayerDef !== undefined) {
|
||||
this.historicalUserLocations = new SimpleFeatureSource(
|
||||
gpsLayerDef,
|
||||
Tiles.tile_index(0, 0, 0),
|
||||
features
|
||||
)
|
||||
this.changes.setHistoricalUserLocations(this.historicalUserLocations)
|
||||
}
|
||||
|
||||
const asLine = features.map((allPoints) => {
|
||||
if (allPoints === undefined || allPoints.length < 2) {
|
||||
return []
|
||||
}
|
||||
|
||||
const feature: Feature<LineString, OsmTags> = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "location_track",
|
||||
"_date:now": new Date().toISOString(),
|
||||
},
|
||||
geometry: {
|
||||
type: "LineString",
|
||||
coordinates: allPoints.map((ff) => ff.feature.geometry.coordinates),
|
||||
},
|
||||
}
|
||||
|
||||
self.allElements.ContainingFeatures.set(feature.properties.id, feature)
|
||||
|
||||
return [
|
||||
{
|
||||
feature,
|
||||
freshness: new Date(),
|
||||
},
|
||||
]
|
||||
})
|
||||
let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(
|
||||
(l) => l.layerDef.id === "gps_track"
|
||||
)[0]
|
||||
if (gpsLineLayerDef !== undefined) {
|
||||
this.historicalUserLocationsTrack = new TiledStaticFeatureSource(
|
||||
asLine,
|
||||
gpsLineLayerDef
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private initHomeLocation() {
|
||||
const empty = []
|
||||
const feature = Stores.ListStabilized(
|
||||
this.osmConnection.userDetails.map((userDetails) => {
|
||||
if (userDetails === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const home = userDetails.home
|
||||
if (home === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return [home.lon, home.lat]
|
||||
})
|
||||
).map((homeLonLat) => {
|
||||
if (homeLonLat === undefined) {
|
||||
return empty
|
||||
}
|
||||
return [
|
||||
{
|
||||
feature: {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "home",
|
||||
"user:home": "yes",
|
||||
_lon: homeLonLat[0],
|
||||
_lat: homeLonLat[1],
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: homeLonLat,
|
||||
},
|
||||
},
|
||||
freshness: new Date(),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
const flayer = this.filteredLayers.data.filter((l) => l.layerDef.id === "home_location")[0]
|
||||
if (flayer !== undefined) {
|
||||
this.homeLocation = new TiledStaticFeatureSource(feature, flayer)
|
||||
}
|
||||
}
|
||||
|
||||
private static getPref(
|
||||
osmConnection: OsmConnection,
|
||||
key: string,
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
import { MangroveIdentity } from "../Web/MangroveReviews"
|
||||
import { Store, UIEventSource } from "../UIEventSource"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
import { Store, Stores, UIEventSource } from "../UIEventSource"
|
||||
import Locale from "../../UI/i18n/Locale"
|
||||
import ElementsState from "./ElementsState"
|
||||
import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"
|
||||
import { Changes } from "../Osm/Changes"
|
||||
import ChangeToElementsActor from "../Actors/ChangeToElementsActor"
|
||||
import PendingChangesUploader from "../Actors/PendingChangesUploader"
|
||||
import Maproulette from "../Maproulette"
|
||||
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
|
||||
import FeatureSource from "../FeatureSource/FeatureSource"
|
||||
|
||||
/**
|
||||
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
|
||||
* which layers they enabled, ...
|
||||
*/
|
||||
export default class UserRelatedState extends ElementsState {
|
||||
export default class UserRelatedState {
|
||||
/**
|
||||
The user credentials
|
||||
*/
|
||||
|
@ -29,29 +25,22 @@ export default class UserRelatedState extends ElementsState {
|
|||
*/
|
||||
public mangroveIdentity: MangroveIdentity
|
||||
|
||||
/**
|
||||
* Maproulette connection
|
||||
*/
|
||||
public maprouletteConnection: Maproulette
|
||||
|
||||
public readonly installedUserThemes: Store<string[]>
|
||||
|
||||
public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
||||
public readonly homeLocation: FeatureSource
|
||||
|
||||
constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) {
|
||||
super(layoutToUse)
|
||||
/**
|
||||
* The number of seconds that the GPS-locations are stored in memory.
|
||||
* Time in seconds
|
||||
*/
|
||||
public gpsLocationHistoryRetentionTime = new UIEventSource(
|
||||
7 * 24 * 60 * 60,
|
||||
"gps_location_retention"
|
||||
)
|
||||
|
||||
this.osmConnection = new OsmConnection({
|
||||
dryRun: this.featureSwitchIsTesting,
|
||||
fakeUser: this.featureSwitchFakeUser.data,
|
||||
oauth_token: QueryParameters.GetQueryParameter(
|
||||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
),
|
||||
osmConfiguration: <"osm" | "osm-test">this.featureSwitchApiURL.data,
|
||||
attemptLogin: options?.attemptLogin,
|
||||
})
|
||||
constructor(osmConnection: OsmConnection, availableLanguages?: string[]) {
|
||||
this.osmConnection = osmConnection
|
||||
{
|
||||
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
|
||||
this.osmConnection.GetPreference("translation-mode")
|
||||
|
@ -72,49 +61,22 @@ export default class UserRelatedState extends ElementsState {
|
|||
})
|
||||
}
|
||||
|
||||
this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false)
|
||||
this.showAllQuestionsAtOnce = UIEventSource.asBoolean(
|
||||
this.osmConnection.GetPreference("show-all-questions", "false", {
|
||||
documentation:
|
||||
"Either 'true' or 'false'. If set, all questions will be shown all at once",
|
||||
})
|
||||
)
|
||||
new ChangeToElementsActor(this.changes, this.allElements)
|
||||
new PendingChangesUploader(this.changes, this.selectedElement)
|
||||
|
||||
this.mangroveIdentity = new MangroveIdentity(
|
||||
this.osmConnection.GetLongPreference("identity", "mangrove")
|
||||
)
|
||||
|
||||
this.maprouletteConnection = new Maproulette()
|
||||
this.InitializeLanguage(availableLanguages)
|
||||
|
||||
if (layoutToUse?.hideFromOverview) {
|
||||
this.osmConnection.isLoggedIn.addCallbackAndRunD((loggedIn) => {
|
||||
if (loggedIn) {
|
||||
this.osmConnection
|
||||
.GetPreference("hidden-theme-" + layoutToUse?.id + "-enabled")
|
||||
.setData("true")
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (this.layoutToUse !== undefined && !this.layoutToUse.official) {
|
||||
console.log("Marking unofficial theme as visited")
|
||||
this.osmConnection.GetLongPreference("unofficial-theme-" + this.layoutToUse.id).setData(
|
||||
JSON.stringify({
|
||||
id: this.layoutToUse.id,
|
||||
icon: this.layoutToUse.icon,
|
||||
title: this.layoutToUse.title.translations,
|
||||
shortDescription: this.layoutToUse.shortDescription.translations,
|
||||
definition: this.layoutToUse["definition"],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
this.InitializeLanguage()
|
||||
new SelectedElementTagsUpdater(this)
|
||||
this.installedUserThemes = this.InitInstalledUserThemes()
|
||||
|
||||
this.homeLocation = this.initHomeLocation()
|
||||
}
|
||||
|
||||
public GetUnofficialTheme(id: string):
|
||||
|
@ -159,26 +121,50 @@ export default class UserRelatedState extends ElementsState {
|
|||
}
|
||||
}
|
||||
|
||||
private InitializeLanguage() {
|
||||
const layoutToUse = this.layoutToUse
|
||||
public markLayoutAsVisited(layout: LayoutConfig) {
|
||||
if (!layout) {
|
||||
console.error("Trying to mark a layout as visited, but ", layout, " got passed")
|
||||
return
|
||||
}
|
||||
if (layout.hideFromOverview) {
|
||||
this.osmConnection.isLoggedIn.addCallbackAndRunD((loggedIn) => {
|
||||
if (loggedIn) {
|
||||
this.osmConnection
|
||||
.GetPreference("hidden-theme-" + layout?.id + "-enabled")
|
||||
.setData("true")
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!layout.official) {
|
||||
this.osmConnection.GetLongPreference("unofficial-theme-" + layout.id).setData(
|
||||
JSON.stringify({
|
||||
id: layout.id,
|
||||
icon: layout.icon,
|
||||
title: layout.title.translations,
|
||||
shortDescription: layout.shortDescription.translations,
|
||||
definition: layout["definition"],
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private InitializeLanguage(availableLanguages?: string[]) {
|
||||
Locale.language.syncWith(this.osmConnection.GetPreference("language"))
|
||||
Locale.language.addCallback((currentLanguage) => {
|
||||
if (layoutToUse === undefined) {
|
||||
return
|
||||
}
|
||||
if (Locale.showLinkToWeblate.data) {
|
||||
return true // Disable auto switching as we are in translators mode
|
||||
}
|
||||
if (this.layoutToUse.language.indexOf(currentLanguage) < 0) {
|
||||
if (availableLanguages?.indexOf(currentLanguage) < 0) {
|
||||
console.log(
|
||||
"Resetting language to",
|
||||
layoutToUse.language[0],
|
||||
availableLanguages[0],
|
||||
"as",
|
||||
currentLanguage,
|
||||
" is unsupported"
|
||||
)
|
||||
// The current language is not supported -> switch to a supported one
|
||||
Locale.language.setData(layoutToUse.language[0])
|
||||
Locale.language.setData(availableLanguages[0])
|
||||
}
|
||||
})
|
||||
Locale.language.ping()
|
||||
|
@ -193,4 +179,43 @@ export default class UserRelatedState extends ElementsState {
|
|||
.map((k) => k.substring(prefix.length, k.length - postfix.length))
|
||||
)
|
||||
}
|
||||
|
||||
private initHomeLocation(): FeatureSource {
|
||||
const empty = []
|
||||
const feature = Stores.ListStabilized(
|
||||
this.osmConnection.userDetails.map((userDetails) => {
|
||||
if (userDetails === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const home = userDetails.home
|
||||
if (home === undefined) {
|
||||
return undefined
|
||||
}
|
||||
return [home.lon, home.lat]
|
||||
})
|
||||
).map((homeLonLat) => {
|
||||
if (homeLonLat === undefined) {
|
||||
return empty
|
||||
}
|
||||
return [
|
||||
{
|
||||
feature: {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
id: "home",
|
||||
"user:home": "yes",
|
||||
_lon: homeLonLat[0],
|
||||
_lat: homeLonLat[1],
|
||||
},
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: homeLonLat,
|
||||
},
|
||||
},
|
||||
freshness: new Date(),
|
||||
},
|
||||
]
|
||||
})
|
||||
return new StaticFeatureSource(feature)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue