forked from MapComplete/MapComplete
Reformat all files with prettier
This commit is contained in:
parent
e22d189376
commit
b541d3eab4
382 changed files with 50893 additions and 35566 deletions
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue