2024-10-17 04:06:03 +02:00
|
|
|
import ThemeConfig, { MinimalThemeInformation } from "../../Models/ThemeConfig/ThemeConfig"
|
2023-09-28 23:50:27 +02:00
|
|
|
import { OsmConnection } from "../Osm/OsmConnection"
|
|
|
|
import { MangroveIdentity } from "../Web/MangroveReviews"
|
|
|
|
import { Store, Stores, UIEventSource } from "../UIEventSource"
|
|
|
|
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
|
|
|
|
import { FeatureSource } from "../FeatureSource/FeatureSource"
|
|
|
|
import { Feature } from "geojson"
|
|
|
|
import { Utils } from "../../Utils"
|
|
|
|
import translators from "../../assets/translators.json"
|
|
|
|
import codeContributors from "../../assets/contributors.json"
|
|
|
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
|
|
|
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
|
|
|
import usersettings from "../../../src/assets/generated/layers/usersettings.json"
|
|
|
|
import Locale from "../../UI/i18n/Locale"
|
|
|
|
import LinkToWeblate from "../../UI/Base/LinkToWeblate"
|
|
|
|
import FeatureSwitchState from "./FeatureSwitchState"
|
|
|
|
import Constants from "../../Models/Constants"
|
|
|
|
import { QueryParameters } from "../Web/QueryParameters"
|
|
|
|
import { ThemeMetaTagging } from "./UserSettingsMetaTagging"
|
|
|
|
import { MapProperties } from "../../Models/MapProperties"
|
2024-08-10 12:09:55 +02:00
|
|
|
import Showdown from "showdown"
|
2024-09-10 02:19:55 +02:00
|
|
|
import { LocalStorageSource } from "../Web/LocalStorageSource"
|
2024-09-11 01:46:55 +02:00
|
|
|
import { GeocodeResult } from "../Search/GeocodingProvider"
|
2024-09-10 02:19:55 +02:00
|
|
|
|
|
|
|
export class OptionallySyncedHistory<T> {
|
|
|
|
public readonly syncPreference: UIEventSource<"sync" | "local" | "no">
|
|
|
|
public readonly value: Store<T[]>
|
|
|
|
private readonly synced: UIEventSource<T[]>
|
|
|
|
private readonly local: UIEventSource<T[]>
|
|
|
|
private readonly thisSession: UIEventSource<T[]>
|
|
|
|
private readonly _maxHistory: number
|
|
|
|
private readonly _isSame: (a: T, b: T) => boolean
|
|
|
|
private osmconnection: OsmConnection
|
|
|
|
|
2024-10-19 14:44:55 +02:00
|
|
|
constructor(
|
|
|
|
key: string,
|
|
|
|
osmconnection: OsmConnection,
|
|
|
|
maxHistory: number = 20,
|
|
|
|
isSame?: (a: T, b: T) => boolean
|
|
|
|
) {
|
2024-09-10 02:19:55 +02:00
|
|
|
this.osmconnection = osmconnection
|
|
|
|
this._maxHistory = maxHistory
|
|
|
|
this._isSame = isSame
|
2024-10-19 14:44:55 +02:00
|
|
|
this.syncPreference = osmconnection.getPreference("preference-" + key + "-history", "sync")
|
|
|
|
const synced = (this.synced = UIEventSource.asObject<T[]>(
|
|
|
|
osmconnection.getPreference(key + "-history"),
|
|
|
|
[]
|
|
|
|
))
|
|
|
|
const local = (this.local = LocalStorageSource.getParsed<T[]>(key + "-history", []))
|
|
|
|
const thisSession = (this.thisSession = new UIEventSource<T[]>(
|
|
|
|
[],
|
|
|
|
"optionally-synced:" + key + "(session only)"
|
|
|
|
))
|
|
|
|
this.syncPreference.addCallback((syncmode) => {
|
2024-09-10 02:19:55 +02:00
|
|
|
if (syncmode === "sync") {
|
|
|
|
let list = [...thisSession.data, ...synced.data].slice(0, maxHistory)
|
|
|
|
if (this._isSame) {
|
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
|
for (let j = i + 1; j < list.length; j++) {
|
|
|
|
if (this._isSame(list[i], list[j])) {
|
|
|
|
list.splice(j, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
synced.set(list)
|
|
|
|
} else if (syncmode === "local") {
|
|
|
|
local.set(synced.data?.slice(0, maxHistory))
|
|
|
|
synced.set([])
|
|
|
|
} else {
|
|
|
|
synced.set([])
|
|
|
|
local.set([])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-10-19 14:44:55 +02:00
|
|
|
this.value = this.syncPreference.bind((syncPref) => this.getAppropriateStore(syncPref))
|
2024-09-10 02:19:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private getAppropriateStore(syncPref?: string) {
|
|
|
|
syncPref ??= this.syncPreference.data
|
|
|
|
if (syncPref === "sync") {
|
|
|
|
return this.synced
|
|
|
|
}
|
|
|
|
if (syncPref === "local") {
|
|
|
|
return this.local
|
|
|
|
}
|
|
|
|
return this.thisSession
|
|
|
|
}
|
|
|
|
|
|
|
|
public add(t: T) {
|
|
|
|
const store = this.getAppropriateStore()
|
|
|
|
let oldList = store.data ?? []
|
|
|
|
if (this._isSame) {
|
2024-10-19 14:44:55 +02:00
|
|
|
oldList = oldList.filter((x) => !this._isSame(t, x))
|
2024-09-10 02:19:55 +02:00
|
|
|
}
|
|
|
|
store.set([t, ...oldList].slice(0, this._maxHistory))
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds the value when the user is actually logged in
|
|
|
|
* @param t
|
|
|
|
*/
|
|
|
|
public addDefferred(t: T) {
|
|
|
|
if (t === undefined) {
|
|
|
|
return
|
|
|
|
}
|
2024-10-19 14:44:55 +02:00
|
|
|
this.osmconnection.isLoggedIn.addCallbackAndRun((loggedIn) => {
|
2024-09-10 02:19:55 +02:00
|
|
|
if (!loggedIn) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
this.add(t)
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
clear() {
|
|
|
|
this.getAppropriateStore().set([])
|
|
|
|
}
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2021-10-15 05:20:02 +02:00
|
|
|
/**
|
|
|
|
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
|
|
|
|
* which layers they enabled, ...
|
|
|
|
*/
|
2023-03-24 19:21:15 +01:00
|
|
|
export default class UserRelatedState {
|
2023-12-26 12:09:48 +01:00
|
|
|
public static readonly usersettingsConfig = UserRelatedState.initUserSettingsState()
|
2023-05-19 11:22:25 +02:00
|
|
|
public static readonly availableUserSettingsIds: string[] =
|
|
|
|
UserRelatedState.usersettingsConfig?.tagRenderings?.map((tr) => tr.id) ?? []
|
2023-07-17 01:07:01 +02:00
|
|
|
public static readonly SHOW_TAGS_VALUES = ["always", "yes", "full"] as const
|
2021-10-15 05:20:02 +02:00
|
|
|
/**
|
|
|
|
The user credentials
|
|
|
|
*/
|
|
|
|
public osmConnection: OsmConnection
|
|
|
|
/**
|
|
|
|
* The key for mangrove
|
|
|
|
*/
|
2023-03-28 05:13:48 +02:00
|
|
|
public readonly mangroveIdentity: MangroveIdentity
|
2022-06-22 20:18:17 +02:00
|
|
|
public readonly installedUserThemes: Store<string[]>
|
2023-03-08 01:36:27 +01:00
|
|
|
public readonly showAllQuestionsAtOnce: UIEventSource<boolean>
|
2023-06-14 20:39:36 +02:00
|
|
|
public readonly showTags: UIEventSource<"no" | undefined | "always" | "yes" | "full">
|
2023-12-07 21:57:20 +01:00
|
|
|
public readonly showCrosshair: UIEventSource<"yes" | "always" | "no" | undefined>
|
2024-08-26 17:23:04 +02:00
|
|
|
public readonly translationMode: UIEventSource<"false" | "true" | "mobile" | undefined | string>
|
|
|
|
|
2023-07-18 01:26:04 +02:00
|
|
|
public readonly fixateNorth: UIEventSource<undefined | "yes">
|
2024-01-01 03:29:57 +01:00
|
|
|
public readonly a11y: UIEventSource<undefined | "always" | "never" | "default">
|
2023-03-24 19:21:15 +01:00
|
|
|
public readonly homeLocation: FeatureSource
|
2024-05-06 18:58:19 +02:00
|
|
|
public readonly morePrivacy: UIEventSource<undefined | "yes" | "no">
|
2023-11-19 01:05:15 +01:00
|
|
|
/**
|
|
|
|
* The language as saved into the preferences of the user, if logged in.
|
|
|
|
* Note that this is _different_ from the languages a user can set via the osm.org interface here: https://www.openstreetmap.org/preferences
|
|
|
|
*/
|
2023-06-06 00:03:50 +02:00
|
|
|
public readonly language: UIEventSource<string>
|
2023-09-28 23:50:27 +02:00
|
|
|
public readonly preferredBackgroundLayer: UIEventSource<
|
|
|
|
string | "photo" | "map" | "osmbasedmap" | undefined
|
|
|
|
>
|
|
|
|
public readonly imageLicense: UIEventSource<string>
|
2023-03-24 19:21:15 +01:00
|
|
|
/**
|
|
|
|
* The number of seconds that the GPS-locations are stored in memory.
|
|
|
|
* Time in seconds
|
|
|
|
*/
|
2023-04-07 02:13:57 +02:00
|
|
|
public readonly gpsLocationHistoryRetentionTime = new UIEventSource(
|
2023-03-24 19:21:15 +01:00
|
|
|
7 * 24 * 60 * 60,
|
2024-10-19 14:44:55 +02:00
|
|
|
"gps_location_retention"
|
2023-03-24 19:21:15 +01:00
|
|
|
)
|
2024-06-21 02:36:36 +02:00
|
|
|
|
2024-06-24 13:11:35 +02:00
|
|
|
public readonly addNewFeatureMode = new UIEventSource<
|
|
|
|
"button" | "button_click_right" | "button_click" | "click" | "click_right"
|
|
|
|
>("button_click_right")
|
2024-06-21 02:36:36 +02:00
|
|
|
|
2024-10-17 02:10:25 +02:00
|
|
|
public readonly showScale: UIEventSource<boolean>
|
2024-09-12 01:53:47 +02:00
|
|
|
|
2023-04-07 02:13:57 +02:00
|
|
|
/**
|
|
|
|
* Preferences as tags exposes many preferences and state properties as record.
|
|
|
|
* This is used to bridge the internal state with the usersettings.json layerconfig file
|
2023-09-24 16:34:36 +02:00
|
|
|
*
|
|
|
|
* Some metainformation that should not be edited starts with a single underscore
|
|
|
|
* Constants and query parameters start with two underscores
|
|
|
|
* Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource
|
2023-04-07 02:13:57 +02:00
|
|
|
*/
|
|
|
|
public readonly preferencesAsTags: UIEventSource<Record<string, string>>
|
2023-09-28 23:50:27 +02:00
|
|
|
private readonly _mapProperties: MapProperties
|
2023-04-07 02:13:57 +02:00
|
|
|
|
2024-09-10 02:19:55 +02:00
|
|
|
public readonly recentlyVisitedThemes: OptionallySyncedHistory<string>
|
|
|
|
public readonly recentlyVisitedSearch: OptionallySyncedHistory<GeocodeResult>
|
2024-08-22 02:54:46 +02:00
|
|
|
|
2023-04-07 02:13:57 +02:00
|
|
|
constructor(
|
|
|
|
osmConnection: OsmConnection,
|
2024-10-17 04:06:03 +02:00
|
|
|
layout?: ThemeConfig,
|
2023-09-24 16:34:36 +02:00
|
|
|
featureSwitches?: FeatureSwitchState,
|
2024-10-19 14:44:55 +02:00
|
|
|
mapProperties?: MapProperties
|
2023-04-07 02:13:57 +02:00
|
|
|
) {
|
2023-03-24 19:21:15 +01:00
|
|
|
this.osmConnection = osmConnection
|
2023-09-28 23:50:27 +02:00
|
|
|
this._mapProperties = mapProperties
|
2022-02-16 01:46:55 +01:00
|
|
|
|
2023-03-08 01:36:27 +01:00
|
|
|
this.showAllQuestionsAtOnce = UIEventSource.asBoolean(
|
2024-10-19 14:44:55 +02:00
|
|
|
this.osmConnection.getPreference("show-all-questions", "false")
|
2023-03-08 01:36:27 +01:00
|
|
|
)
|
2024-09-16 23:36:42 +02:00
|
|
|
this.language = this.osmConnection.getPreference("language")
|
|
|
|
this.showTags = this.osmConnection.getPreference("show_tags")
|
|
|
|
this.showCrosshair = this.osmConnection.getPreference("show_crosshair")
|
|
|
|
this.fixateNorth = this.osmConnection.getPreference("fixate-north")
|
|
|
|
this.morePrivacy = this.osmConnection.getPreference("more_privacy", "no")
|
2024-05-06 18:58:19 +02:00
|
|
|
|
2024-09-16 23:36:42 +02:00
|
|
|
this.a11y = this.osmConnection.getPreference("a11y")
|
2024-01-01 03:29:57 +01:00
|
|
|
|
2021-10-15 05:20:02 +02:00
|
|
|
this.mangroveIdentity = new MangroveIdentity(
|
2024-10-17 02:10:25 +02:00
|
|
|
this.osmConnection.getPreference("identity", undefined, "mangrove"),
|
2024-10-19 14:44:55 +02:00
|
|
|
this.osmConnection.getPreference("identity-creation-date", undefined, "mangrove")
|
|
|
|
)
|
|
|
|
this.preferredBackgroundLayer = this.osmConnection.getPreference(
|
|
|
|
"preferred-background-layer"
|
2023-09-28 23:50:27 +02:00
|
|
|
)
|
2021-10-15 05:20:02 +02:00
|
|
|
|
2024-09-16 23:36:42 +02:00
|
|
|
this.addNewFeatureMode = this.osmConnection.getPreference(
|
2024-06-21 02:36:36 +02:00
|
|
|
"preferences-add-new-mode",
|
2024-10-19 14:44:55 +02:00
|
|
|
"button_click_right"
|
|
|
|
)
|
|
|
|
this.showScale = UIEventSource.asBoolean(
|
|
|
|
this.osmConnection.GetPreference("preference-show-scale", "false")
|
2024-06-21 02:36:36 +02:00
|
|
|
)
|
|
|
|
|
2024-09-16 23:36:42 +02:00
|
|
|
this.imageLicense = this.osmConnection.getPreference("pictures-license", "CC0")
|
2024-09-10 02:19:55 +02:00
|
|
|
this.installedUserThemes = UserRelatedState.initInstalledUserThemes(osmConnection)
|
2024-08-26 17:23:04 +02:00
|
|
|
this.translationMode = this.initTranslationMode()
|
2023-03-24 19:21:15 +01:00
|
|
|
this.homeLocation = this.initHomeLocation()
|
2023-04-07 02:13:57 +02:00
|
|
|
|
2023-04-14 17:53:08 +02:00
|
|
|
this.preferencesAsTags = this.initAmendedPrefs(layout, featureSwitches)
|
2023-07-27 01:50:23 +02:00
|
|
|
|
2024-09-10 02:19:55 +02:00
|
|
|
this.recentlyVisitedThemes = new OptionallySyncedHistory<string>(
|
|
|
|
"theme",
|
|
|
|
this.osmConnection,
|
|
|
|
10,
|
2024-10-19 14:44:55 +02:00
|
|
|
(a, b) => a === b
|
2024-09-10 02:19:55 +02:00
|
|
|
)
|
2024-10-19 14:44:55 +02:00
|
|
|
this.recentlyVisitedSearch = new OptionallySyncedHistory<GeocodeResult>(
|
|
|
|
"places",
|
2024-09-10 02:19:55 +02:00
|
|
|
this.osmConnection,
|
|
|
|
15,
|
2024-10-19 14:44:55 +02:00
|
|
|
(a, b) => a.osm_id === b.osm_id && a.osm_type === b.osm_type
|
2024-09-10 02:19:55 +02:00
|
|
|
)
|
2023-07-27 01:50:23 +02:00
|
|
|
this.syncLanguage()
|
2024-09-10 02:19:55 +02:00
|
|
|
this.recentlyVisitedThemes.addDefferred(layout?.id)
|
2023-07-27 01:50:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private syncLanguage() {
|
|
|
|
if (QueryParameters.wasInitialized("language")) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-11-19 01:05:15 +01:00
|
|
|
this.language.syncWith(Locale.language)
|
2021-10-15 05:20:02 +02:00
|
|
|
}
|
|
|
|
|
2024-08-26 17:23:04 +02:00
|
|
|
private initTranslationMode(): UIEventSource<"false" | "true" | "mobile" | undefined | string> {
|
|
|
|
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
|
2024-09-16 23:36:42 +02:00
|
|
|
this.osmConnection.getPreference("translation-mode", "false")
|
2024-08-26 17:23:04 +02:00
|
|
|
translationMode.addCallbackAndRunD((mode) => {
|
|
|
|
mode = mode.toLowerCase()
|
|
|
|
if (mode === "true" || mode === "yes") {
|
|
|
|
Locale.showLinkOnMobile.setData(false)
|
|
|
|
Locale.showLinkToWeblate.setData(true)
|
|
|
|
} else if (mode === "false" || mode === "no") {
|
|
|
|
Locale.showLinkToWeblate.setData(false)
|
|
|
|
} else if (mode === "mobile") {
|
|
|
|
Locale.showLinkOnMobile.setData(true)
|
|
|
|
Locale.showLinkToWeblate.setData(true)
|
|
|
|
} else {
|
|
|
|
Locale.showLinkOnMobile.setData(false)
|
|
|
|
Locale.showLinkToWeblate.setData(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return translationMode
|
|
|
|
}
|
|
|
|
|
2023-12-26 12:09:48 +01:00
|
|
|
private static initUserSettingsState(): LayerConfig {
|
2023-06-14 20:39:36 +02:00
|
|
|
try {
|
|
|
|
return new LayerConfig(<LayerConfigJson>usersettings, "userinformationpanel")
|
|
|
|
} catch (e) {
|
2023-05-19 11:22:25 +02:00
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-17 02:10:25 +02:00
|
|
|
/**
|
|
|
|
* Adds a newly visited unofficial theme (or update the info).
|
|
|
|
*
|
|
|
|
* @param themeInfo note that themeInfo.id should be the URL where it was found
|
|
|
|
*/
|
2024-10-17 04:06:03 +02:00
|
|
|
public addUnofficialTheme(themeInfo: MinimalThemeInformation) {
|
2024-10-17 02:10:25 +02:00
|
|
|
const pref = this.osmConnection.getPreference("unofficial-theme-" + themeInfo.id)
|
2024-10-19 14:44:55 +02:00
|
|
|
this.osmConnection.isLoggedIn.when(() => pref.set(JSON.stringify(themeInfo)))
|
2024-10-17 02:10:25 +02:00
|
|
|
}
|
|
|
|
|
2024-10-17 04:06:03 +02:00
|
|
|
public getUnofficialTheme(id: string): MinimalThemeInformation | undefined {
|
2024-09-24 17:52:46 +02:00
|
|
|
const pref = this.osmConnection.getPreference("unofficial-theme-" + id)
|
2022-06-21 18:22:09 +02:00
|
|
|
const str = pref.data
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-06-21 18:22:09 +02:00
|
|
|
if (str === undefined || str === "undefined" || str === "") {
|
|
|
|
pref.setData(null)
|
|
|
|
return undefined
|
|
|
|
}
|
2022-09-08 21:40:48 +02:00
|
|
|
|
2022-06-21 18:22:09 +02:00
|
|
|
try {
|
2024-10-17 02:10:25 +02:00
|
|
|
return JSON.parse(str)
|
2022-06-21 18:22:09 +02:00
|
|
|
} catch (e) {
|
|
|
|
console.warn(
|
|
|
|
"Removing theme " +
|
2024-10-19 14:44:55 +02:00
|
|
|
id +
|
|
|
|
" as it could not be parsed from the preferences; the content is:",
|
|
|
|
str
|
2022-06-21 18:22:09 +02:00
|
|
|
)
|
|
|
|
pref.setData(null)
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
}
|
2023-02-09 02:45:19 +01:00
|
|
|
|
2024-10-17 04:06:03 +02:00
|
|
|
public markLayoutAsVisited(layout: ThemeConfig) {
|
2023-03-24 19:21:15 +01:00
|
|
|
if (!layout) {
|
|
|
|
console.error("Trying to mark a layout as visited, but ", layout, " got passed")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (layout.hideFromOverview) {
|
|
|
|
this.osmConnection.isLoggedIn.addCallbackAndRunD((loggedIn) => {
|
|
|
|
if (loggedIn) {
|
|
|
|
this.osmConnection
|
2024-09-16 23:36:42 +02:00
|
|
|
.getPreference("hidden-theme-" + layout?.id + "-enabled")
|
2023-03-24 19:21:15 +01:00
|
|
|
.setData("true")
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if (!layout.official) {
|
2024-09-24 17:52:46 +02:00
|
|
|
this.osmConnection.getPreference("unofficial-theme-" + layout.id).setData(
|
2023-03-24 19:21:15 +01:00
|
|
|
JSON.stringify({
|
|
|
|
id: layout.id,
|
|
|
|
icon: layout.icon,
|
|
|
|
title: layout.title.translations,
|
|
|
|
shortDescription: layout.shortDescription.translations,
|
2024-09-02 12:48:15 +02:00
|
|
|
definition: layout["definition"],
|
2024-10-19 14:44:55 +02:00
|
|
|
})
|
2023-03-24 19:21:15 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2023-07-17 01:07:01 +02:00
|
|
|
|
2024-09-10 02:19:55 +02:00
|
|
|
public static initInstalledUserThemes(osmConnection: OsmConnection): Store<string[]> {
|
2023-02-09 02:45:19 +01:00
|
|
|
const prefix = "mapcomplete-unofficial-theme-"
|
2024-09-10 02:19:55 +02:00
|
|
|
return osmConnection.preferencesHandler.allPreferences.map((prefs) =>
|
2023-02-09 02:45:19 +01:00
|
|
|
Object.keys(prefs)
|
2024-09-10 02:19:55 +02:00
|
|
|
.filter((k) => k.startsWith(prefix))
|
2024-10-19 14:44:55 +02:00
|
|
|
.map((k) => k.substring(prefix.length))
|
2024-09-10 02:19:55 +02:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of all hidden themes that have been seen before
|
|
|
|
* @param osmConnection
|
|
|
|
*/
|
|
|
|
public static initDiscoveredHiddenThemes(osmConnection: OsmConnection): Store<string[]> {
|
|
|
|
const prefix = "mapcomplete-hidden-theme-"
|
|
|
|
const userPreferences = osmConnection.preferencesHandler.allPreferences
|
|
|
|
return userPreferences.map((preferences) =>
|
|
|
|
Object.keys(preferences)
|
|
|
|
.filter((key) => key.startsWith(prefix))
|
2024-10-19 14:44:55 +02:00
|
|
|
.map((key) => key.substring(prefix.length, key.length - "-enabled".length))
|
2023-02-09 02:45:19 +01:00
|
|
|
)
|
|
|
|
}
|
2023-03-24 19:21:15 +01:00
|
|
|
|
|
|
|
private initHomeLocation(): FeatureSource {
|
|
|
|
const empty = []
|
2023-03-25 02:48:24 +01:00
|
|
|
const feature: Store<Feature[]> = Stores.ListStabilized(
|
2023-03-24 19:21:15 +01:00
|
|
|
this.osmConnection.userDetails.map((userDetails) => {
|
|
|
|
if (userDetails === undefined) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
const home = userDetails.home
|
|
|
|
if (home === undefined) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
return [home.lon, home.lat]
|
2024-10-19 14:44:55 +02:00
|
|
|
})
|
2023-03-24 19:21:15 +01:00
|
|
|
).map((homeLonLat) => {
|
|
|
|
if (homeLonLat === undefined) {
|
|
|
|
return empty
|
|
|
|
}
|
|
|
|
return [
|
2023-03-25 02:48:24 +01:00
|
|
|
<Feature>{
|
|
|
|
type: "Feature",
|
|
|
|
properties: {
|
|
|
|
id: "home",
|
|
|
|
"user:home": "yes",
|
|
|
|
_lon: homeLonLat[0],
|
2024-09-02 12:48:15 +02:00
|
|
|
_lat: homeLonLat[1],
|
2023-03-25 02:48:24 +01:00
|
|
|
},
|
|
|
|
geometry: {
|
|
|
|
type: "Point",
|
2024-09-02 12:48:15 +02:00
|
|
|
coordinates: homeLonLat,
|
|
|
|
},
|
|
|
|
},
|
2023-03-24 19:21:15 +01:00
|
|
|
]
|
|
|
|
})
|
|
|
|
return new StaticFeatureSource(feature)
|
|
|
|
}
|
2023-04-07 02:13:57 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the 'amended preferences'.
|
2023-12-15 01:46:01 +01:00
|
|
|
* This is inherently a dirty and chaotic method, as it shoves many properties into this EventSource
|
2023-04-07 02:13:57 +02:00
|
|
|
* */
|
2023-04-14 17:53:08 +02:00
|
|
|
private initAmendedPrefs(
|
2024-10-17 04:06:03 +02:00
|
|
|
layout?: ThemeConfig,
|
2024-10-19 14:44:55 +02:00
|
|
|
featureSwitches?: FeatureSwitchState
|
2023-04-14 17:53:08 +02:00
|
|
|
): UIEventSource<Record<string, string>> {
|
2023-04-07 02:13:57 +02:00
|
|
|
const amendedPrefs = new UIEventSource<Record<string, string>>({
|
|
|
|
_theme: layout?.id,
|
2023-09-24 18:24:10 +02:00
|
|
|
"_theme:backgroundLayer": layout?.defaultBackgroundId,
|
2023-04-07 02:13:57 +02:00
|
|
|
_backend: this.osmConnection.Backend(),
|
2023-04-20 17:42:07 +02:00
|
|
|
_applicationOpened: new Date().toISOString(),
|
2023-06-14 20:39:36 +02:00
|
|
|
_supports_sharing:
|
|
|
|
typeof window === "undefined" ? "no" : window.navigator.share ? "yes" : "no",
|
2024-09-02 12:48:15 +02:00
|
|
|
_iframe: Utils.isIframe ? "yes" : "no",
|
2023-04-07 02:13:57 +02:00
|
|
|
})
|
2025-01-22 18:22:03 +01:00
|
|
|
if(!Utils.runningFromConsole){
|
|
|
|
amendedPrefs.data["_host"] = window.location.host
|
|
|
|
amendedPrefs.data["_path"] = window.location.pathname
|
|
|
|
amendedPrefs.data["_userAgent"] = navigator.userAgent
|
|
|
|
}
|
2023-04-07 02:13:57 +02:00
|
|
|
|
2023-05-11 02:17:41 +02:00
|
|
|
for (const key in Constants.userJourney) {
|
2023-05-19 11:22:25 +02:00
|
|
|
amendedPrefs.data["__userjourney_" + key] = Constants.userJourney[key]
|
2023-05-11 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
2023-07-27 01:50:23 +02:00
|
|
|
for (const key of QueryParameters.initializedParameters()) {
|
|
|
|
amendedPrefs.data["__url_parameter_initialized:" + key] = "yes"
|
|
|
|
}
|
|
|
|
|
2023-04-07 02:13:57 +02:00
|
|
|
const osmConnection = this.osmConnection
|
2024-09-10 02:19:55 +02:00
|
|
|
osmConnection.preferencesHandler.allPreferences.addCallback((newPrefs) => {
|
2023-04-07 02:13:57 +02:00
|
|
|
for (const k in newPrefs) {
|
2023-07-17 01:07:01 +02:00
|
|
|
const v = newPrefs[k]
|
2024-09-10 02:19:55 +02:00
|
|
|
if (v === "undefined" || v === "null" || !v) {
|
2023-11-23 17:06:30 +01:00
|
|
|
continue
|
|
|
|
}
|
2024-09-10 02:19:55 +02:00
|
|
|
amendedPrefs.data[k] = newPrefs[k] ?? ""
|
2023-04-07 02:13:57 +02:00
|
|
|
}
|
2023-07-17 01:07:01 +02:00
|
|
|
|
2023-04-07 02:13:57 +02:00
|
|
|
amendedPrefs.ping()
|
|
|
|
})
|
|
|
|
Locale.language.mapD(
|
|
|
|
(language) => {
|
|
|
|
amendedPrefs.data["_language"] = language
|
2024-08-26 17:23:04 +02:00
|
|
|
const trmode = this.translationMode.data
|
2023-04-07 02:13:57 +02:00
|
|
|
if ((trmode === "true" || trmode === "mobile") && layout !== undefined) {
|
2023-12-26 12:09:48 +01:00
|
|
|
const extraInspection = UserRelatedState.usersettingsConfig
|
|
|
|
const missing = layout.missingTranslations(extraInspection)
|
2023-04-07 02:13:57 +02:00
|
|
|
const total = missing.total
|
|
|
|
|
|
|
|
const untranslated = missing.untranslated.get(language) ?? []
|
|
|
|
const hasMissingTheme = untranslated.some((k) => k.startsWith("themes:"))
|
|
|
|
const missingLayers = Utils.Dedup(
|
|
|
|
untranslated
|
|
|
|
.filter((k) => k.startsWith("layers:"))
|
2024-10-19 14:44:55 +02:00
|
|
|
.map((k) => k.slice("layers:".length).split(".")[0])
|
2023-04-07 02:13:57 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const zenLinks: { link: string; id: string }[] = Utils.NoNull([
|
|
|
|
hasMissingTheme
|
|
|
|
? {
|
2024-10-19 14:44:55 +02:00
|
|
|
id: "theme:" + layout.id,
|
|
|
|
link: LinkToWeblate.hrefToWeblateZen(
|
|
|
|
language,
|
|
|
|
"themes",
|
|
|
|
layout.id
|
|
|
|
),
|
|
|
|
}
|
2023-04-07 02:13:57 +02:00
|
|
|
: undefined,
|
|
|
|
...missingLayers.map((id) => ({
|
|
|
|
id: "layer:" + id,
|
2024-09-02 12:48:15 +02:00
|
|
|
link: LinkToWeblate.hrefToWeblateZen(language, "layers", id),
|
|
|
|
})),
|
2023-04-07 02:13:57 +02:00
|
|
|
])
|
|
|
|
const untranslated_count = untranslated.length
|
|
|
|
amendedPrefs.data["_translation_total"] = "" + total
|
|
|
|
amendedPrefs.data["_translation_translated_count"] =
|
|
|
|
"" + (total - untranslated_count)
|
|
|
|
amendedPrefs.data["_translation_percentage"] =
|
|
|
|
"" + Math.floor((100 * (total - untranslated_count)) / total)
|
|
|
|
amendedPrefs.data["_translation_links"] = JSON.stringify(zenLinks)
|
|
|
|
}
|
|
|
|
amendedPrefs.ping()
|
|
|
|
},
|
2024-10-19 14:44:55 +02:00
|
|
|
[this.translationMode]
|
2023-04-07 02:13:57 +02:00
|
|
|
)
|
2023-09-22 11:20:22 +02:00
|
|
|
|
2024-02-20 13:33:38 +01:00
|
|
|
this.mangroveIdentity.getKeyId().addCallbackAndRun((kid) => {
|
2024-02-15 03:11:10 +01:00
|
|
|
amendedPrefs.data["mangrove_kid"] = kid
|
|
|
|
amendedPrefs.ping()
|
|
|
|
})
|
|
|
|
|
2023-09-22 11:20:22 +02:00
|
|
|
const usersettingMetaTagging = new ThemeMetaTagging()
|
2023-04-07 02:13:57 +02:00
|
|
|
osmConnection.userDetails.addCallback((userDetails) => {
|
|
|
|
for (const k in userDetails) {
|
|
|
|
amendedPrefs.data["_" + k] = "" + userDetails[k]
|
|
|
|
}
|
2024-08-22 02:54:46 +02:00
|
|
|
if (userDetails.description) {
|
2024-08-14 13:53:56 +02:00
|
|
|
amendedPrefs.data["_description_html"] = Utils.purify(
|
|
|
|
new Showdown.Converter()
|
|
|
|
.makeHtml(userDetails.description)
|
|
|
|
?.replace(/>/g, ">")
|
|
|
|
?.replace(/</g, "<")
|
2024-10-19 14:44:55 +02:00
|
|
|
?.replace(/\n/g, "")
|
2024-08-14 13:53:56 +02:00
|
|
|
)
|
2024-08-10 12:09:55 +02:00
|
|
|
}
|
2023-04-07 02:13:57 +02:00
|
|
|
|
2023-09-22 11:20:22 +02:00
|
|
|
usersettingMetaTagging.metaTaggging_for_usersettings({ properties: amendedPrefs.data })
|
2023-04-07 02:13:57 +02:00
|
|
|
|
|
|
|
const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "")
|
|
|
|
const isTranslator = translators.contributors.find(
|
|
|
|
(c: { contributor: string; commits: number }) => {
|
|
|
|
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
|
|
|
|
return replaced === simplifiedName
|
2024-10-19 14:44:55 +02:00
|
|
|
}
|
2023-04-07 02:13:57 +02:00
|
|
|
)
|
|
|
|
if (isTranslator) {
|
|
|
|
amendedPrefs.data["_translation_contributions"] = "" + isTranslator.commits
|
|
|
|
}
|
|
|
|
const isCodeContributor = codeContributors.contributors.find(
|
|
|
|
(c: { contributor: string; commits: number }) => {
|
|
|
|
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
|
|
|
|
return replaced === simplifiedName
|
2024-10-19 14:44:55 +02:00
|
|
|
}
|
2023-04-07 02:13:57 +02:00
|
|
|
)
|
|
|
|
if (isCodeContributor) {
|
|
|
|
amendedPrefs.data["_code_contributions"] = "" + isCodeContributor.commits
|
|
|
|
}
|
|
|
|
amendedPrefs.ping()
|
|
|
|
})
|
|
|
|
|
|
|
|
amendedPrefs.addCallbackD((tags) => {
|
|
|
|
for (const key in tags) {
|
2023-04-20 17:42:07 +02:00
|
|
|
if (key.startsWith("_") || key === "mapcomplete-language") {
|
2024-08-23 02:16:24 +02:00
|
|
|
// Language is managed separately
|
2023-04-07 02:13:57 +02:00
|
|
|
continue
|
|
|
|
}
|
2024-10-17 02:10:25 +02:00
|
|
|
if (tags[key] === null) {
|
2024-09-10 02:19:55 +02:00
|
|
|
continue
|
|
|
|
}
|
2024-10-17 02:10:25 +02:00
|
|
|
let pref = this.osmConnection.GetPreference(key, undefined, { prefix: "" })
|
2024-09-16 23:36:42 +02:00
|
|
|
|
2024-09-10 02:19:55 +02:00
|
|
|
pref.set(tags[key])
|
2023-04-07 02:13:57 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-04-14 17:53:08 +02:00
|
|
|
for (const key in featureSwitches) {
|
|
|
|
if (featureSwitches[key].addCallbackAndRun) {
|
|
|
|
featureSwitches[key].addCallbackAndRun((v) => {
|
|
|
|
const oldV = amendedPrefs.data["__" + key]
|
|
|
|
if (oldV === v) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
amendedPrefs.data["__" + key] = "" + v
|
|
|
|
amendedPrefs.ping()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-28 23:50:27 +02:00
|
|
|
this._mapProperties?.rasterLayer?.addCallbackAndRun((l) => {
|
2023-09-24 16:34:36 +02:00
|
|
|
amendedPrefs.data["__current_background"] = l?.properties?.id
|
|
|
|
amendedPrefs.ping()
|
|
|
|
})
|
|
|
|
|
2023-04-07 02:13:57 +02:00
|
|
|
return amendedPrefs
|
|
|
|
}
|
2024-10-08 22:37:11 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The disabled questions for this theme and layer
|
|
|
|
*/
|
|
|
|
public getThemeDisabled(themeId: string, layerId: string): UIEventSource<string[]> {
|
2024-11-28 12:00:23 +01:00
|
|
|
const flatSource = this.osmConnection.getPreference(
|
|
|
|
"disabled-questions-" + themeId + "-" + layerId,
|
|
|
|
"[]"
|
|
|
|
)
|
2024-10-08 22:37:11 +02:00
|
|
|
return UIEventSource.asObject<string[]>(flatSource, [])
|
|
|
|
}
|
2021-10-15 05:20:02 +02:00
|
|
|
}
|