Reformat all files with prettier

This commit is contained in:
Pieter Vander Vennet 2022-09-08 21:40:48 +02:00
parent e22d189376
commit b541d3eab4
382 changed files with 50893 additions and 35566 deletions

View file

@ -1,89 +1,91 @@
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";
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();
public allElements: ElementStorage = new ElementStorage()
/**
The latest element that was selected
*/
public readonly selectedElement = new UIEventSource<any>(
undefined,
"Selected element"
);
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");
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)
}
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")
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);
});
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)
})
}
}
}

View file

@ -1,37 +1,39 @@
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";
import Hash from "../Web/Hash";
import {BBox} from "../BBox";
import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox";
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 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"
import Hash from "../Web/Hash"
import { BBox } from "../BBox"
import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox"
import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator"
import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
export default class FeaturePipelineState extends MapState {
/**
* The piece of code which fetches data from various sources and shows it on the background map
*/
public readonly featurePipeline: FeaturePipeline;
private readonly featureAggregator: TileHierarchyAggregator;
public readonly featurePipeline: FeaturePipeline
private readonly featureAggregator: TileHierarchyAggregator
private readonly metatagRecalculator: MetaTagRecalculator
private readonly popups : Map<string, ScrollableFullScreen> = new Map<string, ScrollableFullScreen>();
private readonly popups: Map<string, ScrollableFullScreen> = new Map<
string,
ScrollableFullScreen
>()
constructor(layoutToUse: LayoutConfig) {
super(layoutToUse);
super(layoutToUse)
const clustering = layoutToUse?.clustering
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this);
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this)
const clusterCounter = this.featureAggregator
const self = this;
const self = this
/**
* We are a bit in a bind:
@ -51,26 +53,26 @@ export default class FeaturePipelineState extends MapState {
self.metatagRecalculator.registerSource(source)
}
}
function registerSource(source: FeatureSourceForLayer & Tiled) {
function registerSource(source: FeatureSourceForLayer & Tiled) {
clusterCounter.addTile(source)
const sourceBBox = source.features.map(allFeatures => BBox.bboxAroundAll(allFeatures.map(f => BBox.get(f.feature))))
const sourceBBox = source.features.map((allFeatures) =>
BBox.bboxAroundAll(allFeatures.map((f) => BBox.get(f.feature)))
)
// Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering
const doShowFeatures = source.features.map(
f => {
(f) => {
const z = self.locationControl.data.zoom
if (!source.layer.isDisplayed.data) {
return false;
return false
}
const bounds = self.currentBounds.data
if (bounds === undefined) {
// Map is not yet displayed
return false;
return false
}
if (!sourceBBox.data.overlapsWith(bounds)) {
@ -78,10 +80,9 @@ export default class FeaturePipelineState extends MapState {
return false
}
if (z < source.layer.layerDef.minzoom) {
// Layer is always hidden for this zoom level
return false;
return false
}
if (z > clustering.maxZoom) {
@ -93,55 +94,55 @@ export default class FeaturePipelineState extends MapState {
return false
}
let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex);
let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex)
if (tileZ >= z) {
while (tileZ > z) {
tileZ--
tileX = Math.floor(tileX / 2)
tileY = Math.floor(tileY / 2)
}
if (clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))?.totalValue > clustering.minNeededElements) {
if (
clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))
?.totalValue > clustering.minNeededElements
) {
// To much elements
return false
}
}
return true
}, [self.currentBounds, source.layer.isDisplayed, sourceBBox]
},
[self.currentBounds, source.layer.isDisplayed, sourceBBox]
)
new ShowDataLayer(
{
features: source,
leafletMap: self.leafletMap,
layerToShow: source.layer.layerDef,
doShowLayer: doShowFeatures,
selectedElement: self.selectedElement,
state: self,
popup: (tags, layer) => self.CreatePopup(tags, layer)
}
)
new ShowDataLayer({
features: source,
leafletMap: self.leafletMap,
layerToShow: source.layer.layerDef,
doShowLayer: doShowFeatures,
selectedElement: self.selectedElement,
state: self,
popup: (tags, layer) => self.CreatePopup(tags, layer),
})
}
this.featurePipeline = new FeaturePipeline(registerSource, this, {handleRawFeatureSource: registerRaw});
this.featurePipeline = new FeaturePipeline(registerSource, this, {
handleRawFeatureSource: registerRaw,
})
this.metatagRecalculator = new MetaTagRecalculator(this, this.featurePipeline)
this.metatagRecalculator.registerSource(this.currentView, true)
sourcesToRegister.forEach(source => self.metatagRecalculator.registerSource(source))
sourcesToRegister.forEach((source) => self.metatagRecalculator.registerSource(source))
new SelectedFeatureHandler(Hash.hash, this)
this.AddClusteringToMap(this.leafletMap)
}
public CreatePopup(tags:UIEventSource<any> , layer: LayerConfig): ScrollableFullScreen{
if(this.popups.has(tags.data.id)){
return this.popups.get(tags.data.id)
public CreatePopup(tags: UIEventSource<any>, layer: LayerConfig): ScrollableFullScreen {
if (this.popups.has(tags.data.id)) {
return this.popups.get(tags.data.id)
}
const popup = new FeatureInfoBox(tags, layer, this)
this.popups.set(tags.data.id, popup)
@ -155,15 +156,19 @@ export default class FeaturePipelineState extends MapState {
*/
public AddClusteringToMap(leafletMap: UIEventSource<any>) {
const clustering = this.layoutToUse.clustering
const self = this;
const self = this
new ShowDataLayer({
features: this.featureAggregator.getCountsForZoom(clustering, this.locationControl, clustering.minNeededElements),
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
popup: this.featureSwitchIsDebugging.data
? (tags, layer) => new FeatureInfoBox(tags, layer, self)
: undefined,
state: this,
})
}
}
}

View file

@ -1,45 +1,43 @@
/**
* The part of the global state which initializes the feature switches, based on default values and on the layoutToUse
*/
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {UIEventSource} from "../UIEventSource";
import {QueryParameters} from "../Web/QueryParameters";
import Constants from "../../Models/Constants";
import {Utils} from "../../Utils";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { UIEventSource } from "../UIEventSource"
import { QueryParameters } from "../Web/QueryParameters"
import Constants from "../../Models/Constants"
import { Utils } from "../../Utils"
export default class FeatureSwitchState {
/**
* The layout that is being used in this run
*/
public readonly layoutToUse: LayoutConfig;
public readonly layoutToUse: LayoutConfig
public readonly featureSwitchUserbadge: UIEventSource<boolean>;
public readonly featureSwitchSearch: UIEventSource<boolean>;
public readonly featureSwitchBackgroundSelection: UIEventSource<boolean>;
public readonly featureSwitchAddNew: UIEventSource<boolean>;
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>;
public readonly featureSwitchExtraLinkEnabled: UIEventSource<boolean>;
public readonly featureSwitchMoreQuests: UIEventSource<boolean>;
public readonly featureSwitchShareScreen: UIEventSource<boolean>;
public readonly featureSwitchGeolocation: UIEventSource<boolean>;
public readonly featureSwitchIsTesting: UIEventSource<boolean>;
public readonly featureSwitchIsDebugging: UIEventSource<boolean>;
public readonly featureSwitchShowAllQuestions: UIEventSource<boolean>;
public readonly featureSwitchApiURL: UIEventSource<string>;
public readonly featureSwitchFilter: UIEventSource<boolean>;
public readonly featureSwitchEnableExport: UIEventSource<boolean>;
public readonly featureSwitchFakeUser: UIEventSource<boolean>;
public readonly featureSwitchExportAsPdf: UIEventSource<boolean>;
public readonly overpassUrl: UIEventSource<string[]>;
public readonly overpassTimeout: UIEventSource<number>;
public readonly overpassMaxZoom: UIEventSource<number>;
public readonly osmApiTileSize: UIEventSource<number>;
public readonly backgroundLayerId: UIEventSource<string>;
public readonly featureSwitchUserbadge: UIEventSource<boolean>
public readonly featureSwitchSearch: UIEventSource<boolean>
public readonly featureSwitchBackgroundSelection: UIEventSource<boolean>
public readonly featureSwitchAddNew: UIEventSource<boolean>
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>
public readonly featureSwitchExtraLinkEnabled: UIEventSource<boolean>
public readonly featureSwitchMoreQuests: UIEventSource<boolean>
public readonly featureSwitchShareScreen: UIEventSource<boolean>
public readonly featureSwitchGeolocation: UIEventSource<boolean>
public readonly featureSwitchIsTesting: UIEventSource<boolean>
public readonly featureSwitchIsDebugging: UIEventSource<boolean>
public readonly featureSwitchShowAllQuestions: UIEventSource<boolean>
public readonly featureSwitchApiURL: UIEventSource<string>
public readonly featureSwitchFilter: UIEventSource<boolean>
public readonly featureSwitchEnableExport: UIEventSource<boolean>
public readonly featureSwitchFakeUser: UIEventSource<boolean>
public readonly featureSwitchExportAsPdf: UIEventSource<boolean>
public readonly overpassUrl: UIEventSource<string[]>
public readonly overpassTimeout: UIEventSource<number>
public readonly overpassMaxZoom: UIEventSource<number>
public readonly osmApiTileSize: UIEventSource<number>
public readonly backgroundLayerId: UIEventSource<string>
public constructor(layoutToUse: LayoutConfig) {
this.layoutToUse = layoutToUse;
this.layoutToUse = layoutToUse
// Helper function to initialize feature switches
function featSw(
@ -47,104 +45,104 @@ export default class FeatureSwitchState {
deflt: (layout: LayoutConfig) => boolean,
documentation: string
): UIEventSource<boolean> {
const defaultValue = deflt(layoutToUse);
const defaultValue = deflt(layoutToUse)
const queryParam = QueryParameters.GetQueryParameter(
key,
"" + defaultValue,
documentation
);
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
return queryParam.sync((str) =>
str === undefined ? defaultValue : str !== "false", [],
b => b == defaultValue ? undefined : (""+b)
)
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
return queryParam.sync(
(str) => (str === undefined ? defaultValue : str !== "false"),
[],
(b) => (b == defaultValue ? undefined : "" + b)
)
}
this.featureSwitchUserbadge = featSw(
"fs-userbadge",
(layoutToUse) => layoutToUse?.enableUserBadge ?? true,
"Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode."
);
)
this.featureSwitchSearch = featSw(
"fs-search",
(layoutToUse) => layoutToUse?.enableSearch ?? true,
"Disables/Enables the search bar"
);
)
this.featureSwitchBackgroundSelection = featSw(
"fs-background",
(layoutToUse) => layoutToUse?.enableBackgroundLayerSelection ?? true,
"Disables/Enables the background layer control"
);
)
this.featureSwitchFilter = featSw(
"fs-filter",
(layoutToUse) => layoutToUse?.enableLayers ?? true,
"Disables/Enables the filter view"
);
)
this.featureSwitchAddNew = featSw(
"fs-add-new",
(layoutToUse) => layoutToUse?.enableAddNewPoints ?? true,
"Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)"
);
)
this.featureSwitchWelcomeMessage = featSw(
"fs-welcome-message",
() => true,
"Disables/enables the help menu or welcome message"
);
)
this.featureSwitchExtraLinkEnabled = featSw(
"fs-iframe-popout",
_ => true,
(_) => true,
"Disables/Enables the extraLink button. By default, if in iframe mode and the welcome message is hidden, a popout button to the full mapcomplete instance is shown instead (unless disabled with this switch or another extraLink button is enabled)"
);
)
this.featureSwitchMoreQuests = featSw(
"fs-more-quests",
(layoutToUse) => layoutToUse?.enableMoreQuests ?? true,
"Disables/Enables the 'More Quests'-tab in the welcome message"
);
)
this.featureSwitchShareScreen = featSw(
"fs-share-screen",
(layoutToUse) => layoutToUse?.enableShareScreen ?? true,
"Disables/Enables the 'Share-screen'-tab in the welcome message"
);
)
this.featureSwitchGeolocation = featSw(
"fs-geolocation",
(layoutToUse) => layoutToUse?.enableGeolocation ?? true,
"Disables/Enables the geolocation button"
);
)
this.featureSwitchShowAllQuestions = featSw(
"fs-all-questions",
(layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false,
"Always show all questions"
);
)
this.featureSwitchEnableExport = featSw(
"fs-export",
(layoutToUse) => layoutToUse?.enableExportButton ?? false,
"Enable the export as GeoJSON and CSV button"
);
)
this.featureSwitchExportAsPdf = featSw(
"fs-pdf",
(layoutToUse) => layoutToUse?.enablePdfDownload ?? false,
"Enable the PDF download button"
);
)
this.featureSwitchApiURL = QueryParameters.GetQueryParameter(
"backend",
"osm",
"The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'"
);
)
let testingDefaultValue = false;
if (this.featureSwitchApiURL.data !== "osm-test" && !Utils.runningFromConsole &&
(location.hostname === "localhost" || location.hostname === "127.0.0.1")) {
let testingDefaultValue = false
if (
this.featureSwitchApiURL.data !== "osm-test" &&
!Utils.runningFromConsole &&
(location.hostname === "localhost" || location.hostname === "127.0.0.1")
) {
testingDefaultValue = true
}
this.featureSwitchIsTesting = QueryParameters.GetBooleanQueryParameter(
"test",
testingDefaultValue,
@ -157,31 +155,47 @@ export default class FeatureSwitchState {
"If true, shows some extra debugging help such as all the available tags on every object"
)
this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter("fake-user", false,
"If true, 'dryrun' mode is activated and a fake user account is loaded")
this.featureSwitchFakeUser = QueryParameters.GetBooleanQueryParameter(
"fake-user",
false,
"If true, 'dryrun' mode is activated and a fake user account is loaded"
)
this.overpassUrl = QueryParameters.GetQueryParameter("overpassUrl",
this.overpassUrl = QueryParameters.GetQueryParameter(
"overpassUrl",
(layoutToUse?.overpassUrl ?? Constants.defaultOverpassUrls).join(","),
"Point mapcomplete to a different overpass-instance. Example: https://overpass-api.de/api/interpreter"
).sync(param => param.split(","), [], urls => urls.join(","))
).sync(
(param) => param.split(","),
[],
(urls) => urls.join(",")
)
this.overpassTimeout = UIEventSource.asFloat(QueryParameters.GetQueryParameter("overpassTimeout",
"" + layoutToUse?.overpassTimeout,
"Set a different timeout (in seconds) for queries in overpass"))
this.overpassTimeout = UIEventSource.asFloat(
QueryParameters.GetQueryParameter(
"overpassTimeout",
"" + layoutToUse?.overpassTimeout,
"Set a different timeout (in seconds) for queries in overpass"
)
)
this.overpassMaxZoom =
UIEventSource.asFloat(QueryParameters.GetQueryParameter("overpassMaxZoom",
this.overpassMaxZoom = UIEventSource.asFloat(
QueryParameters.GetQueryParameter(
"overpassMaxZoom",
"" + layoutToUse?.overpassMaxZoom,
" point to switch between OSM-api and overpass"))
" point to switch between OSM-api and overpass"
)
)
this.osmApiTileSize =
UIEventSource.asFloat(QueryParameters.GetQueryParameter("osmApiTileSize",
this.osmApiTileSize = UIEventSource.asFloat(
QueryParameters.GetQueryParameter(
"osmApiTileSize",
"" + layoutToUse?.osmApiTileSize,
"Tilesize when the OSM-API is used to fetch data within a BBOX"))
"Tilesize when the OSM-API is used to fetch data within a BBOX"
)
)
this.featureSwitchUserbadge.addCallbackAndRun(userbadge => {
this.featureSwitchUserbadge.addCallbackAndRun((userbadge) => {
if (!userbadge) {
this.featureSwitchAddNew.setData(false)
}
@ -191,9 +205,6 @@ export default class FeatureSwitchState {
"background",
layoutToUse?.defaultBackgroundId ?? "osm",
"The id of the background layer to start with"
);
)
}
}
}

View file

@ -1,34 +1,33 @@
import UserRelatedState from "./UserRelatedState";
import {Store, Stores, UIEventSource} from "../UIEventSource";
import BaseLayer from "../../Models/BaseLayer";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
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 {OsmConnection} from "../Osm/OsmConnection";
import UserRelatedState from "./UserRelatedState"
import { Store, Stores, UIEventSource } from "../UIEventSource"
import BaseLayer from "../../Models/BaseLayer"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import AvailableBaseLayers from "../Actors/AvailableBaseLayers"
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 { OsmConnection } from "../Osm/OsmConnection"
export interface GlobalFilter {
filter: FilterState,
id: string,
filter: FilterState
id: string
onNewPoint: {
safetyCheck: Translation,
safetyCheck: Translation
confirmAddNew: TypedTranslation<{ preset: Translation }>
tags: Tag[]
}
@ -38,60 +37,64 @@ export interface GlobalFilter {
* 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");
public leafletMap = new UIEventSource<any /*L.Map*/>(undefined, "leafletmap")
/**
* A list of currently available background layers
*/
public availableBackgroundLayers: Store<BaseLayer[]>;
public availableBackgroundLayers: Store<BaseLayer[]>
/**
* The current background layer
*/
public backgroundLayer: UIEventSource<BaseLayer>;
public backgroundLayer: UIEventSource<BaseLayer>
/**
* Last location where a click was registered
*/
public readonly LastClickLocation: UIEventSource<{
lat: number;
lon: number;
}> = new UIEventSource<{ lat: number; lon: number }>(undefined);
lat: number
lon: number
}> = new UIEventSource<{ lat: number; lon: number }>(undefined)
/**
* The bounds of the current map view
*/
public currentView: FeatureSourceForLayer & Tiled;
public currentView: FeatureSourceForLayer & Tiled
/**
* The location as delivered by the GPS
*/
public currentUserLocation: SimpleFeatureSource;
public currentUserLocation: SimpleFeatureSource
/**
* All previously visited points
*/
public historicalUserLocations: SimpleFeatureSource;
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")
public historicalUserLocationsTrack: FeatureSourceForLayer & Tiled;
public gpsLocationHistoryRetentionTime = new UIEventSource(
7 * 24 * 60 * 60,
"gps_location_retention"
)
public historicalUserLocationsTrack: FeatureSourceForLayer & Tiled
/**
* A feature source containing the current home location of the user
*/
public homeLocation: FeatureSourceForLayer & Tiled
public readonly mainMapObject: BaseUIElement & MinimapObj;
public readonly mainMapObject: BaseUIElement & MinimapObj
/**
* Which layers are enabled in the current theme and what filters are applied onto them
*/
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([], "filteredLayers");
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>(
[],
"filteredLayers"
)
/**
* Filters which apply onto all layers
@ -101,31 +104,30 @@ export default class MapState extends UserRelatedState {
/**
* Which overlays are shown
*/
public overlayToggles: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[]
public overlayToggles: { config: TilesourceConfig; isDisplayed: UIEventSource<boolean> }[]
constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) {
super(layoutToUse, options);
super(layoutToUse, options)
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl);
this.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(this.locationControl)
let defaultLayer = AvailableBaseLayers.osmCarto
const available = this.availableBackgroundLayers.data;
const available = this.availableBackgroundLayers.data
for (const layer of available) {
if (this.backgroundLayerId.data === layer.id) {
defaultLayer = layer;
defaultLayer = layer
}
}
const self = this
this.backgroundLayer = new UIEventSource<BaseLayer>(defaultLayer)
this.backgroundLayer.addCallbackAndRunD(layer => self.backgroundLayerId.setData(layer.id))
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({
@ -134,18 +136,23 @@ export default class MapState extends UserRelatedState {
leafletMap: this.leafletMap,
bounds: this.currentBounds,
attribution: attr,
lastClickLocation: this.LastClickLocation
lastClickLocation: this.LastClickLocation,
})
this.overlayToggles = this.layoutToUse?.tileLayerSources
?.filter(c => c.name !== undefined)
?.map(c => ({
config: c,
isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown")
})) ?? []
this.filteredLayers = new UIEventSource<FilteredLayer[]>( MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection))
this.overlayToggles =
this.layoutToUse?.tileLayerSources
?.filter((c) => c.name !== undefined)
?.map((c) => ({
config: c,
isDisplayed: QueryParameters.GetBooleanQueryParameter(
"overlay-" + c.id,
c.defaultState,
"Wether or not the overlay " + c.id + " is shown"
),
})) ?? []
this.filteredLayers = new UIEventSource<FilteredLayer[]>(
MapState.InitializeFilteredLayers(this.layoutToUse, this.osmConnection)
)
this.lockBounds()
this.AddAllOverlaysToMap(this.leafletMap)
@ -155,7 +162,7 @@ export default class MapState extends UserRelatedState {
this.initUserLocationTrail()
this.initCurrentView()
new TitleHandler(this);
new TitleHandler(this)
}
public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) {
@ -171,15 +178,14 @@ export default class MapState extends UserRelatedState {
}
new ShowOverlayLayer(tileLayerSource, leafletMap)
}
}
private lockBounds() {
const layout = this.layoutToUse;
const layout = this.layoutToUse
if (!layout?.lockLocation) {
return;
return
}
console.warn("Locking the bounds to ", layout.lockLocation);
console.warn("Locking the bounds to ", layout.lockLocation)
this.mainMapObject.installBounds(
new BBox(layout.lockLocation),
this.featureSwitchIsTesting.data
@ -187,69 +193,82 @@ export default class MapState extends UserRelatedState {
}
private initCurrentView() {
let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "current_view")[0]
let currentViewLayer: FilteredLayer = this.filteredLayers.data.filter(
(l) => l.layerDef.id === "current_view"
)[0]
if (currentViewLayer === undefined) {
// This layer is not needed by the theme and thus unloaded
return;
return
}
let i = 0
const self = this;
const features: Store<{ feature: any, freshness: Date }[]> = this.currentBounds.map(bounds => {
if (bounds === undefined) {
return []
}
i++
const feature = {
freshness: new Date(),
feature: {
type: "Feature",
properties: {
id: "current_view-" + i,
"current_view": "yes",
"zoom": "" + self.locationControl.data.zoom
},
geometry: {
type: "Polygon",
coordinates: [[
[bounds.maxLon, bounds.maxLat],
[bounds.minLon, bounds.maxLat],
[bounds.minLon, bounds.minLat],
[bounds.maxLon, bounds.minLat],
[bounds.maxLon, bounds.maxLat],
]]
}
const self = this
const features: Store<{ feature: any; freshness: Date }[]> = this.currentBounds.map(
(bounds) => {
if (bounds === undefined) {
return []
}
i++
const feature = {
freshness: new Date(),
feature: {
type: "Feature",
properties: {
id: "current_view-" + i,
current_view: "yes",
zoom: "" + self.locationControl.data.zoom,
},
geometry: {
type: "Polygon",
coordinates: [
[
[bounds.maxLon, bounds.maxLat],
[bounds.minLon, bounds.maxLat],
[bounds.minLon, bounds.minLat],
[bounds.maxLon, bounds.minLat],
[bounds.maxLon, bounds.maxLat],
],
],
},
},
}
return [feature]
}
return [feature]
})
)
this.currentView = new TiledStaticFeatureSource(features, currentViewLayer);
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
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location")[0]
let 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));
this.currentUserLocation = new SimpleFeatureSource(gpsLayerDef, Tiles.tile_index(0, 0, 0))
}
private initUserLocationTrail() {
const features = LocalStorageSource.GetParsed<{ feature: any, freshness: Date }[]>("gps_location_history", [])
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)
.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;
const self = this
let i = 0
this.currentUserLocation?.features?.addCallbackAndRunD(([location]) => {
if (location === undefined) {
return;
return
}
const previousLocation = features.data[features.data.length - 1]
@ -261,30 +280,37 @@ export default class MapState extends UserRelatedState {
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
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;
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.data.push({ feature, freshness: new Date() })
features.ping()
})
let gpsLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_location_history")[0]
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.historicalUserLocations = new SimpleFeatureSource(
gpsLayerDef,
Tiles.tile_index(0, 0, 0),
features
)
this.changes.setHistoricalUserLocations(this.historicalUserLocations)
}
const asLine = features.map(allPoints => {
const asLine = features.map((allPoints) => {
if (allPoints === undefined || allPoints.length < 2) {
return []
}
@ -292,136 +318,184 @@ export default class MapState extends UserRelatedState {
const feature = {
type: "Feature",
properties: {
"id": "location_track",
id: "location_track",
"_date:now": new Date().toISOString(),
},
geometry: {
type: "LineString",
coordinates: allPoints.map(ff => ff.feature.geometry.coordinates)
}
coordinates: allPoints.map((ff) => ff.feature.geometry.coordinates),
},
}
self.allElements.ContainingFeatures.set(feature.properties.id, feature)
return [{
feature,
freshness: new Date()
}]
return [
{
feature,
freshness: new Date(),
},
]
})
let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(l => l.layerDef.id === "gps_track")[0]
let gpsLineLayerDef: FilteredLayer = this.filteredLayers.data.filter(
(l) => l.layerDef.id === "gps_track"
)[0]
if (gpsLineLayerDef !== undefined) {
this.historicalUserLocationsTrack = new TiledStaticFeatureSource(asLine, gpsLineLayerDef);
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 => {
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]
return [
{
feature: {
type: "Feature",
properties: {
id: "home",
"user:home": "yes",
_lon: homeLonLat[0],
_lat: homeLonLat[1],
},
geometry: {
type: "Point",
coordinates: homeLonLat,
},
},
"geometry": {
"type": "Point",
"coordinates": homeLonLat
}
}, freshness: new Date()
}]
freshness: new Date(),
},
]
})
const flayer = this.filteredLayers.data.filter(l => l.layerDef.id === "home_location")[0]
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, layer: LayerConfig): UIEventSource<boolean> {
return osmConnection
.GetPreference(key, layer.shownByDefault + "")
.sync(v => {
private static getPref(
osmConnection: OsmConnection,
key: string,
layer: LayerConfig
): UIEventSource<boolean> {
return osmConnection.GetPreference(key, layer.shownByDefault + "").sync(
(v) => {
if (v === undefined) {
return undefined
}
return v === "true";
}, [], b => {
return v === "true"
},
[],
(b) => {
if (b === undefined) {
return undefined
}
return "" + b;
})
return "" + b
}
)
}
public static InitializeFilteredLayers(layoutToUse: {layers: LayerConfig[], id: string}, osmConnection: OsmConnection): FilteredLayer[] {
public static InitializeFilteredLayers(
layoutToUse: { layers: LayerConfig[]; id: string },
osmConnection: OsmConnection
): FilteredLayer[] {
if (layoutToUse === undefined) {
return []
}
const flayers: FilteredLayer[] = [];
const flayers: FilteredLayer[] = []
for (const layer of layoutToUse.layers) {
let isDisplayed: UIEventSource<boolean>
if (layer.syncSelection === "local") {
isDisplayed = LocalStorageSource.GetParsed(layoutToUse.id + "-layer-" + layer.id + "-enabled", layer.shownByDefault)
isDisplayed = LocalStorageSource.GetParsed(
layoutToUse.id + "-layer-" + layer.id + "-enabled",
layer.shownByDefault
)
} else if (layer.syncSelection === "theme-only") {
isDisplayed = MapState.getPref(osmConnection, layoutToUse.id + "-layer-" + layer.id + "-enabled", layer)
isDisplayed = MapState.getPref(
osmConnection,
layoutToUse.id + "-layer-" + layer.id + "-enabled",
layer
)
} else if (layer.syncSelection === "global") {
isDisplayed = MapState.getPref(osmConnection,"layer-" + layer.id + "-enabled", layer)
isDisplayed = MapState.getPref(
osmConnection,
"layer-" + layer.id + "-enabled",
layer
)
} else {
isDisplayed = QueryParameters.GetBooleanQueryParameter("layer-" + layer.id, layer.shownByDefault, "Wether or not layer " + layer.id + " is shown")
isDisplayed = QueryParameters.GetBooleanQueryParameter(
"layer-" + layer.id,
layer.shownByDefault,
"Wether or not layer " + layer.id + " is shown"
)
}
const flayer: FilteredLayer = {
isDisplayed,
layerDef: layer,
appliedFilters: new UIEventSource<Map<string, FilterState>>(new Map<string, FilterState>())
};
layer.filters.forEach(filterConfig => {
appliedFilters: new UIEventSource<Map<string, FilterState>>(
new Map<string, FilterState>()
),
}
layer.filters.forEach((filterConfig) => {
const stateSrc = filterConfig.initState()
stateSrc.addCallbackAndRun(state => flayer.appliedFilters.data.set(filterConfig.id, state))
flayer.appliedFilters.map(dict => dict.get(filterConfig.id))
.addCallback(state => stateSrc.setData(state))
stateSrc.addCallbackAndRun((state) =>
flayer.appliedFilters.data.set(filterConfig.id, state)
)
flayer.appliedFilters
.map((dict) => dict.get(filterConfig.id))
.addCallback((state) => stateSrc.setData(state))
})
flayers.push(flayer);
flayers.push(flayer)
}
for (const layer of layoutToUse.layers) {
if (layer.filterIsSameAs === undefined) {
continue
}
const toReuse = flayers.find(l => l.layerDef.id === layer.filterIsSameAs)
const toReuse = flayers.find((l) => l.layerDef.id === layer.filterIsSameAs)
if (toReuse === undefined) {
throw "Error in layer " + layer.id + ": it defines that it should be use the filters of " + layer.filterIsSameAs + ", but this layer was not loaded"
throw (
"Error in layer " +
layer.id +
": it defines that it should be use the filters of " +
layer.filterIsSameAs +
", but this layer was not loaded"
)
}
console.warn("Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs)
const selfLayer = flayers.findIndex(l => l.layerDef.id === layer.id)
console.warn(
"Linking filter and isDisplayed-states of " +
layer.id +
" and " +
layer.filterIsSameAs
)
const selfLayer = flayers.findIndex((l) => l.layerDef.id === layer.id)
flayers[selfLayer] = {
isDisplayed: toReuse.isDisplayed,
layerDef: layer,
appliedFilters: toReuse.appliedFilters
};
appliedFilters: toReuse.appliedFilters,
}
}
return flayers;
return flayers
}
}
}

View file

@ -1,50 +1,48 @@
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 {LocalStorageSource} from "../Web/LocalStorageSource";
import {Utils} from "../../Utils";
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 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 { LocalStorageSource } from "../Web/LocalStorageSource"
import { Utils } from "../../Utils"
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 * as translators from "../../assets/translators.json"
import Maproulette from "../Maproulette";
import Maproulette from "../Maproulette"
/**
* 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 {
/**
The user credentials
*/
public osmConnection: OsmConnection;
public osmConnection: OsmConnection
/**
THe change handler
*/
public changes: Changes;
public changes: Changes
/**
* The key for mangrove
*/
public mangroveIdentity: MangroveIdentity;
public mangroveIdentity: MangroveIdentity
/**
* Maproulette connection
*/
public maprouletteConnection: Maproulette;
public maprouletteConnection: Maproulette
public readonly isTranslator: Store<boolean>
public readonly isTranslator : Store<boolean>;
public readonly installedUserThemes: Store<string[]>
constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) {
super(layoutToUse);
super(layoutToUse)
this.osmConnection = new OsmConnection({
dryRun: this.featureSwitchIsTesting,
@ -54,138 +52,147 @@ export default class UserRelatedState extends ElementsState {
undefined,
"Used to complete the login"
),
osmConfiguration: <'osm' | 'osm-test'>this.featureSwitchApiURL.data,
attemptLogin: options?.attemptLogin
osmConfiguration: <"osm" | "osm-test">this.featureSwitchApiURL.data,
attemptLogin: options?.attemptLogin,
})
const translationMode = this.osmConnection.GetPreference("translation-mode").sync(str => str === undefined ? undefined : str === "true", [], b => b === undefined ? undefined : b+"")
const translationMode = this.osmConnection.GetPreference("translation-mode").sync(
(str) => (str === undefined ? undefined : str === "true"),
[],
(b) => (b === undefined ? undefined : b + "")
)
translationMode.syncWith(Locale.showLinkToWeblate)
this.isTranslator = this.osmConnection.userDetails.map(ud => {
if(!ud.loggedIn){
return false;
this.isTranslator = this.osmConnection.userDetails.map((ud) => {
if (!ud.loggedIn) {
return false
}
const name= ud.name.toLowerCase().replace(/\s+/g, '')
return translators.contributors.some(c => c.contributor.toLowerCase().replace(/\s+/g, '') === name)
const name = ud.name.toLowerCase().replace(/\s+/g, "")
return translators.contributors.some(
(c) => c.contributor.toLowerCase().replace(/\s+/g, "") === name
)
})
this.isTranslator.addCallbackAndRunD(ud => {
if(ud){
this.isTranslator.addCallbackAndRunD((ud) => {
if (ud) {
Locale.showLinkToWeblate.setData(true)
}
});
})
this.changes = new Changes(this, layoutToUse?.isLeftRightSensitive() ?? false)
new ChangeToElementsActor(this.changes, this.allElements)
new PendingChangesUploader(this.changes, this.selectedElement);
new PendingChangesUploader(this.changes, this.selectedElement)
this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove")
);
)
this.maprouletteConnection = new Maproulette();
this.maprouletteConnection = new Maproulette()
if (layoutToUse?.hideFromOverview) {
this.osmConnection.isLoggedIn.addCallbackAndRunD(loggedIn => {
this.osmConnection.isLoggedIn.addCallbackAndRunD((loggedIn) => {
if (loggedIn) {
this.osmConnection
.GetPreference("hidden-theme-" + layoutToUse?.id + "-enabled")
.setData("true");
return true;
.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({
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"]
}))
definition: this.layoutToUse["definition"],
})
)
}
this.InitializeLanguage();
this.InitializeLanguage()
new SelectedElementTagsUpdater(this)
this.installedUserThemes = this.InitInstalledUserThemes();
this.installedUserThemes = this.InitInstalledUserThemes()
}
private InitializeLanguage() {
const layoutToUse = this.layoutToUse;
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) {
console.log(
"Resetting language to",
layoutToUse.language[0],
"as",
currentLanguage,
" is unsupported"
);
// The current language is not supported -> switch to a supported one
Locale.language.setData(layoutToUse.language[0]);
}
})
Locale.language.ping();
const layoutToUse = this.layoutToUse
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) {
console.log(
"Resetting language to",
layoutToUse.language[0],
"as",
currentLanguage,
" is unsupported"
)
// The current language is not supported -> switch to a supported one
Locale.language.setData(layoutToUse.language[0])
}
})
Locale.language.ping()
}
private InitInstalledUserThemes(): Store<string[]>{
const prefix = "mapcomplete-unofficial-theme-";
private InitInstalledUserThemes(): Store<string[]> {
const prefix = "mapcomplete-unofficial-theme-"
const postfix = "-combined-length"
return this.osmConnection.preferencesHandler.preferences.map(prefs =>
return this.osmConnection.preferencesHandler.preferences.map((prefs) =>
Object.keys(prefs)
.filter(k => k.startsWith(prefix) && k.endsWith(postfix))
.map(k => k.substring(prefix.length, k.length - postfix.length))
.filter((k) => k.startsWith(prefix) && k.endsWith(postfix))
.map((k) => k.substring(prefix.length, k.length - postfix.length))
)
}
public GetUnofficialTheme(id: string): {
id: string
icon: string,
title: any,
shortDescription: any,
definition?: any,
isOfficial: boolean
} | undefined {
public GetUnofficialTheme(id: string):
| {
id: string
icon: string
title: any
shortDescription: any
definition?: any
isOfficial: boolean
}
| undefined {
console.log("GETTING UNOFFICIAL THEME")
const pref = this.osmConnection.GetLongPreference("unofficial-theme-"+id)
const pref = this.osmConnection.GetLongPreference("unofficial-theme-" + id)
const str = pref.data
if (str === undefined || str === "undefined" || str === "") {
pref.setData(null)
return undefined
}
try {
const value: {
id: string
icon: string,
title: any,
shortDescription: any,
definition?: any,
icon: string
title: any
shortDescription: any
definition?: any
isOfficial: boolean
} = JSON.parse(str)
value.isOfficial = false
return value;
return value
} catch (e) {
console.warn("Removing theme " + id + " as it could not be parsed from the preferences; the content is:", str)
console.warn(
"Removing theme " +
id +
" as it could not be parsed from the preferences; the content is:",
str
)
pref.setData(null)
return undefined
}
}
}
}