Fix: see #2212: actually save custom themes as visited
This commit is contained in:
parent
91f5c8f166
commit
9427083939
19 changed files with 129 additions and 75 deletions
|
@ -183,7 +183,7 @@ export default class GeoLocationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
private initUserLocationTrail() {
|
private initUserLocationTrail() {
|
||||||
const features = LocalStorageSource.GetParsed<Feature[]>("gps_location_history", [])
|
const features = LocalStorageSource.getParsed<Feature[]>("gps_location_history", [])
|
||||||
const now = new Date().getTime()
|
const now = new Date().getTime()
|
||||||
features.data = features.data.filter((ff) => {
|
features.data = features.data.filter((ff) => {
|
||||||
if (ff.properties === undefined) {
|
if (ff.properties === undefined) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default class InitialMapPositioning {
|
||||||
deflt: number,
|
deflt: number,
|
||||||
docs: string
|
docs: string
|
||||||
): UIEventSource<number> {
|
): UIEventSource<number> {
|
||||||
const localStorage = LocalStorageSource.Get(key)
|
const localStorage = LocalStorageSource.get(key)
|
||||||
const previousValue = localStorage.data
|
const previousValue = localStorage.data
|
||||||
const src = UIEventSource.asFloat(
|
const src = UIEventSource.asFloat(
|
||||||
QueryParameters.GetQueryParameter(key, "" + deflt, docs).syncWith(localStorage)
|
QueryParameters.GetQueryParameter(key, "" + deflt, docs).syncWith(localStorage)
|
||||||
|
|
|
@ -132,14 +132,14 @@ export default class DetermineLayout {
|
||||||
let json: any
|
let json: any
|
||||||
|
|
||||||
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
|
// layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
|
||||||
const dedicatedHashFromLocalStorage = LocalStorageSource.Get(
|
const dedicatedHashFromLocalStorage = LocalStorageSource.get(
|
||||||
"user-layout-" + userLayoutParam.data?.replace(" ", "_")
|
"user-layout-" + userLayoutParam.data?.replace(" ", "_")
|
||||||
)
|
)
|
||||||
if (dedicatedHashFromLocalStorage.data?.length < 10) {
|
if (dedicatedHashFromLocalStorage.data?.length < 10) {
|
||||||
dedicatedHashFromLocalStorage.setData(undefined)
|
dedicatedHashFromLocalStorage.setData(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashFromLocalStorage = LocalStorageSource.Get("last-loaded-user-layout")
|
const hashFromLocalStorage = LocalStorageSource.get("last-loaded-user-layout")
|
||||||
if (hash.length < 10) {
|
if (hash.length < 10) {
|
||||||
hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data
|
hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,7 +24,7 @@ import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesSto
|
||||||
*/
|
*/
|
||||||
export class Changes {
|
export class Changes {
|
||||||
public readonly pendingChanges: UIEventSource<ChangeDescription[]> =
|
public readonly pendingChanges: UIEventSource<ChangeDescription[]> =
|
||||||
LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", [])
|
LocalStorageSource.getParsed<ChangeDescription[]>("pending-changes", [])
|
||||||
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)
|
public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined)
|
||||||
public readonly state: {
|
public readonly state: {
|
||||||
allElements?: IndexedFeatureSource
|
allElements?: IndexedFeatureSource
|
||||||
|
|
|
@ -210,7 +210,7 @@ export class OsmConnection {
|
||||||
console.log("Trying to log in...")
|
console.log("Trying to log in...")
|
||||||
this.updateAuthObject()
|
this.updateAuthObject()
|
||||||
|
|
||||||
LocalStorageSource.Get("location_before_login").setData(
|
LocalStorageSource.get("location_before_login").setData(
|
||||||
Utils.runningFromConsole ? undefined : window.location.href
|
Utils.runningFromConsole ? undefined : window.location.href
|
||||||
)
|
)
|
||||||
this.auth.xhr(
|
this.auth.xhr(
|
||||||
|
@ -521,7 +521,7 @@ export class OsmConnection {
|
||||||
this.auth.authenticate(function () {
|
this.auth.authenticate(function () {
|
||||||
// Fully authed at this point
|
// Fully authed at this point
|
||||||
console.log("Authentication successful!")
|
console.log("Authentication successful!")
|
||||||
const previousLocation = LocalStorageSource.Get("location_before_login")
|
const previousLocation = LocalStorageSource.get("location_before_login")
|
||||||
callback(previousLocation.data)
|
callback(previousLocation.data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ import { Utils } from "../../Utils"
|
||||||
|
|
||||||
export class OsmPreferences {
|
export class OsmPreferences {
|
||||||
|
|
||||||
private preferences: Record<string, UIEventSource<string>> = {}
|
/**
|
||||||
|
* A 'cache' of all the preference stores
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private readonly preferences: Record<string, UIEventSource<string>> = {}
|
||||||
|
|
||||||
private localStorageInited: Set<string> = new Set()
|
private localStorageInited: Set<string> = new Set()
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +19,10 @@ export class OsmPreferences {
|
||||||
*/
|
*/
|
||||||
private seenKeys: string[] = []
|
private seenKeys: string[] = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a dictionary which has all preferences
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
private readonly _allPreferences: UIEventSource<Record<string, string>> = new UIEventSource({})
|
private readonly _allPreferences: UIEventSource<Record<string, string>> = new UIEventSource({})
|
||||||
public readonly allPreferences: Store<Readonly<Record<string, string>>> = this._allPreferences
|
public readonly allPreferences: Store<Readonly<Record<string, string>>> = this._allPreferences
|
||||||
private readonly _fakeUser: boolean
|
private readonly _fakeUser: boolean
|
||||||
|
@ -51,6 +59,7 @@ export class OsmPreferences {
|
||||||
this.setPreferencesAll(key, value)
|
this.setPreferencesAll(key, value)
|
||||||
}
|
}
|
||||||
pref.addCallback(v => {
|
pref.addCallback(v => {
|
||||||
|
console.log("Got an update:", key, "--->", v)
|
||||||
this.uploadKvSplit(key, v)
|
this.uploadKvSplit(key, v)
|
||||||
this.setPreferencesAll(key, v)
|
this.setPreferencesAll(key, v)
|
||||||
})
|
})
|
||||||
|
@ -101,11 +110,11 @@ export class OsmPreferences {
|
||||||
key = key.replace(/[:/"' {}.%\\]/g, "")
|
key = key.replace(/[:/"' {}.%\\]/g, "")
|
||||||
|
|
||||||
|
|
||||||
const localStorage = LocalStorageSource.Get(key)
|
const localStorage = LocalStorageSource.get(key) // cached
|
||||||
if (localStorage.data === "null" || localStorage.data === "undefined") {
|
if (localStorage.data === "null" || localStorage.data === "undefined") {
|
||||||
localStorage.set(undefined)
|
localStorage.set(undefined)
|
||||||
}
|
}
|
||||||
const pref: UIEventSource<string> = this.initPreference(key, localStorage.data ?? defaultValue)
|
const pref: UIEventSource<string> = this.initPreference(key, localStorage.data ?? defaultValue) // cached
|
||||||
if (this.localStorageInited.has(key)) {
|
if (this.localStorageInited.has(key)) {
|
||||||
return pref
|
return pref
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class GeoLocationState {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private readonly _previousLocationGrant: UIEventSource<boolean> =
|
private readonly _previousLocationGrant: UIEventSource<boolean> =
|
||||||
LocalStorageSource.GetParsed<boolean>("geolocation-permissions", false)
|
LocalStorageSource.getParsed<boolean>("geolocation-permissions", false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to detect a permission retraction
|
* Used to detect a permission retraction
|
||||||
|
|
|
@ -43,8 +43,8 @@ export class OptionallySyncedHistory<T> {
|
||||||
"sync",
|
"sync",
|
||||||
)
|
)
|
||||||
const synced = this.synced = UIEventSource.asObject<T[]>(osmconnection.getPreference(key + "-history"), [])
|
const synced = this.synced = UIEventSource.asObject<T[]>(osmconnection.getPreference(key + "-history"), [])
|
||||||
const local = this.local = LocalStorageSource.GetParsed<T[]>(key + "-history", [])
|
const local = this.local = LocalStorageSource.getParsed<T[]>(key + "-history", [])
|
||||||
const thisSession = this.thisSession = new UIEventSource<T[]>([], "optionally-synced:"+key+"(session only)")
|
const thisSession = this.thisSession = new UIEventSource<T[]>([], "optionally-synced:" + key + "(session only)")
|
||||||
this.syncPreference.addCallback(syncmode => {
|
this.syncPreference.addCallback(syncmode => {
|
||||||
if (syncmode === "sync") {
|
if (syncmode === "sync") {
|
||||||
let list = [...thisSession.data, ...synced.data].slice(0, maxHistory)
|
let list = [...thisSession.data, ...synced.data].slice(0, maxHistory)
|
||||||
|
@ -164,7 +164,7 @@ export default class UserRelatedState {
|
||||||
"button" | "button_click_right" | "button_click" | "click" | "click_right"
|
"button" | "button_click_right" | "button_click" | "click" | "click_right"
|
||||||
>("button_click_right")
|
>("button_click_right")
|
||||||
|
|
||||||
public readonly showScale : UIEventSource<boolean>
|
public readonly showScale: UIEventSource<boolean>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preferences as tags exposes many preferences and state properties as record.
|
* Preferences as tags exposes many preferences and state properties as record.
|
||||||
|
@ -202,8 +202,8 @@ export default class UserRelatedState {
|
||||||
this.a11y = this.osmConnection.getPreference("a11y")
|
this.a11y = this.osmConnection.getPreference("a11y")
|
||||||
|
|
||||||
this.mangroveIdentity = new MangroveIdentity(
|
this.mangroveIdentity = new MangroveIdentity(
|
||||||
this.osmConnection.getPreference("identity", undefined,"mangrove"),
|
this.osmConnection.getPreference("identity", undefined, "mangrove"),
|
||||||
this.osmConnection.getPreference("identity-creation-date", undefined,"mangrove"),
|
this.osmConnection.getPreference("identity-creation-date", undefined, "mangrove"),
|
||||||
)
|
)
|
||||||
this.preferredBackgroundLayer = this.osmConnection.getPreference("preferred-background-layer")
|
this.preferredBackgroundLayer = this.osmConnection.getPreference("preferred-background-layer")
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ export default class UserRelatedState {
|
||||||
"preferences-add-new-mode",
|
"preferences-add-new-mode",
|
||||||
"button_click_right",
|
"button_click_right",
|
||||||
)
|
)
|
||||||
this.showScale = UIEventSource.asBoolean(this.osmConnection.GetPreference("preference-show-scale","false"))
|
this.showScale = UIEventSource.asBoolean(this.osmConnection.GetPreference("preference-show-scale", "false"))
|
||||||
|
|
||||||
this.imageLicense = this.osmConnection.getPreference("pictures-license", "CC0")
|
this.imageLicense = this.osmConnection.getPreference("pictures-license", "CC0")
|
||||||
this.installedUserThemes = UserRelatedState.initInstalledUserThemes(osmConnection)
|
this.installedUserThemes = UserRelatedState.initInstalledUserThemes(osmConnection)
|
||||||
|
@ -272,7 +272,19 @@ export default class UserRelatedState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUnofficialTheme(id: string): (MinimalLayoutInformation & { definition }) | undefined {
|
/**
|
||||||
|
* Adds a newly visited unofficial theme (or update the info).
|
||||||
|
*
|
||||||
|
* @param themeInfo note that themeInfo.id should be the URL where it was found
|
||||||
|
*/
|
||||||
|
public addUnofficialTheme(themeInfo: MinimalLayoutInformation) {
|
||||||
|
const pref = this.osmConnection.getPreference("unofficial-theme-" + themeInfo.id)
|
||||||
|
this.osmConnection.isLoggedIn.when(
|
||||||
|
() => pref.set(JSON.stringify(themeInfo))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUnofficialTheme(id: string): MinimalLayoutInformation | undefined {
|
||||||
const pref = this.osmConnection.getPreference("unofficial-theme-" + id)
|
const pref = this.osmConnection.getPreference("unofficial-theme-" + id)
|
||||||
const str = pref.data
|
const str = pref.data
|
||||||
|
|
||||||
|
@ -282,7 +294,7 @@ export default class UserRelatedState {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return <MinimalLayoutInformation & { definition: string }>JSON.parse(str)
|
return JSON.parse(str)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"Removing theme " +
|
"Removing theme " +
|
||||||
|
@ -516,10 +528,10 @@ export default class UserRelatedState {
|
||||||
// Language is managed separately
|
// Language is managed separately
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if(tags[key] === null){
|
if (tags[key] === null) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let pref = this.osmConnection.GetPreference(key, undefined, {prefix: ""})
|
let pref = this.osmConnection.GetPreference(key, undefined, { prefix: "" })
|
||||||
|
|
||||||
pref.set(tags[key])
|
pref.set(tags[key])
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ export class Stores {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FromPromiseWithErr<T>(
|
public static FromPromiseWithErr<T>(
|
||||||
promise: Promise<T>
|
promise: Promise<T>,
|
||||||
): Store<{ success: T } | { error: any }> {
|
): Store<{ success: T } | { error: any }> {
|
||||||
return UIEventSource.FromPromiseWithErr(promise)
|
return UIEventSource.FromPromiseWithErr(promise)
|
||||||
}
|
}
|
||||||
|
@ -133,13 +133,13 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
abstract map<J>(
|
abstract map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraStoresToWatch: Store<any>[],
|
extraStoresToWatch: Store<any>[],
|
||||||
callbackDestroyFunction: (f: () => void) => void
|
callbackDestroyFunction: (f: () => void) => void,
|
||||||
): Store<J>
|
): Store<J>
|
||||||
|
|
||||||
public mapD<J>(
|
public mapD<J>(
|
||||||
f: (t: Exclude<T, undefined | null>) => J,
|
f: (t: Exclude<T, undefined | null>) => J,
|
||||||
extraStoresToWatch?: Store<any>[],
|
extraStoresToWatch?: Store<any>[],
|
||||||
callbackDestroyFunction?: (f: () => void) => void
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
): Store<J> {
|
): Store<J> {
|
||||||
return this.map((t) => {
|
return this.map((t) => {
|
||||||
if (t === undefined) {
|
if (t === undefined) {
|
||||||
|
@ -176,7 +176,7 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
abstract addCallbackAndRun(callback: (data: T) => void): () => void
|
abstract addCallbackAndRun(callback: (data: T) => void): () => void
|
||||||
|
|
||||||
public withEqualityStabilized(
|
public withEqualityStabilized(
|
||||||
comparator: (t: T | undefined, t1: T | undefined) => boolean
|
comparator: (t: T | undefined, t1: T | undefined) => boolean,
|
||||||
): Store<T> {
|
): Store<T> {
|
||||||
let oldValue = undefined
|
let oldValue = undefined
|
||||||
return this.map((v) => {
|
return this.map((v) => {
|
||||||
|
@ -342,6 +342,16 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract destroy()
|
public abstract destroy()
|
||||||
|
|
||||||
|
when(callback: () => void, condition?: (v:T) => boolean) {
|
||||||
|
condition ??= v => v === true
|
||||||
|
this.addCallbackAndRunD(v => {
|
||||||
|
if ( condition(v)) {
|
||||||
|
callback()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImmutableStore<T> extends Store<T> {
|
export class ImmutableStore<T> extends Store<T> {
|
||||||
|
@ -384,7 +394,7 @@ export class ImmutableStore<T> extends Store<T> {
|
||||||
map<J>(
|
map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraStores: Store<any>[] = undefined,
|
extraStores: Store<any>[] = undefined,
|
||||||
ondestroyCallback?: (f: () => void) => void
|
ondestroyCallback?: (f: () => void) => void,
|
||||||
): ImmutableStore<J> {
|
): ImmutableStore<J> {
|
||||||
if (extraStores?.length > 0) {
|
if (extraStores?.length > 0) {
|
||||||
return new MappedStore(this, f, extraStores, undefined, f(this.data), ondestroyCallback)
|
return new MappedStore(this, f, extraStores, undefined, f(this.data), ondestroyCallback)
|
||||||
|
@ -454,7 +464,7 @@ class ListenerTracker<T> {
|
||||||
let endTime = new Date().getTime() / 1000
|
let endTime = new Date().getTime() / 1000
|
||||||
if (endTime - startTime > 500) {
|
if (endTime - startTime > 500) {
|
||||||
console.trace(
|
console.trace(
|
||||||
"Warning: a ping took more then 500ms; this is probably a performance issue"
|
"Warning: a ping took more then 500ms; this is probably a performance issue",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (toDelete !== undefined) {
|
if (toDelete !== undefined) {
|
||||||
|
@ -496,7 +506,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
extraStores: Store<any>[],
|
extraStores: Store<any>[],
|
||||||
upstreamListenerHandler: ListenerTracker<TIn> | undefined,
|
upstreamListenerHandler: ListenerTracker<TIn> | undefined,
|
||||||
initialState: T,
|
initialState: T,
|
||||||
onDestroy?: (f: () => void) => void
|
onDestroy?: (f: () => void) => void,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this._upstream = upstream
|
this._upstream = upstream
|
||||||
|
@ -536,7 +546,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
map<J>(
|
map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraStores: Store<any>[] = undefined,
|
extraStores: Store<any>[] = undefined,
|
||||||
ondestroyCallback?: (f: () => void) => void
|
ondestroyCallback?: (f: () => void) => void,
|
||||||
): Store<J> {
|
): Store<J> {
|
||||||
let stores: Store<any>[] = undefined
|
let stores: Store<any>[] = undefined
|
||||||
if (extraStores?.length > 0 || this._extraStores?.length > 0) {
|
if (extraStores?.length > 0 || this._extraStores?.length > 0) {
|
||||||
|
@ -558,7 +568,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
stores,
|
stores,
|
||||||
this._callbacks,
|
this._callbacks,
|
||||||
f(this.data),
|
f(this.data),
|
||||||
ondestroyCallback
|
ondestroyCallback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -614,7 +624,7 @@ class MappedStore<TIn, T> extends Store<T> {
|
||||||
|
|
||||||
this._unregisterFromUpstream = this._upstream.addCallback((_) => self.update())
|
this._unregisterFromUpstream = this._upstream.addCallback((_) => self.update())
|
||||||
this._unregisterFromExtraStores = this._extraStores?.map((store) =>
|
this._unregisterFromExtraStores = this._extraStores?.map((store) =>
|
||||||
store?.addCallback((_) => self.update())
|
store?.addCallback((_) => self.update()),
|
||||||
)
|
)
|
||||||
this._callbacksAreRegistered = true
|
this._callbacksAreRegistered = true
|
||||||
}
|
}
|
||||||
|
@ -651,7 +661,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
|
|
||||||
public static flatten<X>(
|
public static flatten<X>(
|
||||||
source: Store<Store<X>>,
|
source: Store<Store<X>>,
|
||||||
possibleSources?: Store<object>[]
|
possibleSources?: Store<object>[],
|
||||||
): UIEventSource<X> {
|
): UIEventSource<X> {
|
||||||
const sink = new UIEventSource<X>(source.data?.data)
|
const sink = new UIEventSource<X>(source.data?.data)
|
||||||
|
|
||||||
|
@ -680,7 +690,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
*/
|
*/
|
||||||
public static FromPromise<T>(
|
public static FromPromise<T>(
|
||||||
promise: Promise<T>,
|
promise: Promise<T>,
|
||||||
onError: (e) => void = undefined
|
onError: (e) => void = undefined,
|
||||||
): UIEventSource<T> {
|
): UIEventSource<T> {
|
||||||
const src = new UIEventSource<T>(undefined)
|
const src = new UIEventSource<T>(undefined)
|
||||||
promise?.then((d) => src.setData(d))
|
promise?.then((d) => src.setData(d))
|
||||||
|
@ -701,7 +711,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public static FromPromiseWithErr<T>(
|
public static FromPromiseWithErr<T>(
|
||||||
promise: Promise<T>
|
promise: Promise<T>,
|
||||||
): UIEventSource<{ success: T } | { error: any } | undefined> {
|
): UIEventSource<{ success: T } | { error: any } | undefined> {
|
||||||
const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
|
const src = new UIEventSource<{ success: T } | { error: any }>(undefined)
|
||||||
promise
|
promise
|
||||||
|
@ -733,7 +743,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return "" + fl
|
return "" + fl
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,7 +774,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return "" + fl
|
return "" + fl
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,7 +782,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
return stringUIEventSource.sync(
|
return stringUIEventSource.sync(
|
||||||
(str) => str === "true",
|
(str) => str === "true",
|
||||||
[],
|
[],
|
||||||
(b) => "" + b
|
(b) => "" + b,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -790,7 +800,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
(b) => JSON.stringify(b) ?? ""
|
(b) => JSON.stringify(b) ?? "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -880,7 +890,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
public map<J>(
|
public map<J>(
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraSources: Store<any>[] = [],
|
extraSources: Store<any>[] = [],
|
||||||
onDestroy?: (f: () => void) => void
|
onDestroy?: (f: () => void) => void,
|
||||||
): Store<J> {
|
): Store<J> {
|
||||||
return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy)
|
return new MappedStore(this, f, extraSources, this._callbacks, f(this.data), onDestroy)
|
||||||
}
|
}
|
||||||
|
@ -892,7 +902,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
public mapD<J>(
|
public mapD<J>(
|
||||||
f: (t: Exclude<T, undefined | null>) => J,
|
f: (t: Exclude<T, undefined | null>) => J,
|
||||||
extraSources: Store<any>[] = [],
|
extraSources: Store<any>[] = [],
|
||||||
callbackDestroyFunction?: (f: () => void) => void
|
callbackDestroyFunction?: (f: () => void) => void,
|
||||||
): Store<J | undefined> {
|
): Store<J | undefined> {
|
||||||
return new MappedStore(
|
return new MappedStore(
|
||||||
this,
|
this,
|
||||||
|
@ -910,7 +920,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
this.data === undefined || this.data === null
|
this.data === undefined || this.data === null
|
||||||
? <undefined | null>this.data
|
? <undefined | null>this.data
|
||||||
: f(<any>this.data),
|
: f(<any>this.data),
|
||||||
callbackDestroyFunction
|
callbackDestroyFunction,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -930,7 +940,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
f: (t: T) => J,
|
f: (t: T) => J,
|
||||||
extraSources: Store<any>[],
|
extraSources: Store<any>[],
|
||||||
g: (j: J, t: T) => T,
|
g: (j: J, t: T) => T,
|
||||||
allowUnregister = false
|
allowUnregister = false,
|
||||||
): UIEventSource<J> {
|
): UIEventSource<J> {
|
||||||
const self = this
|
const self = this
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,11 @@ import { UIEventSource } from "../UIEventSource"
|
||||||
* UIEventsource-wrapper around localStorage
|
* UIEventsource-wrapper around localStorage
|
||||||
*/
|
*/
|
||||||
export class LocalStorageSource {
|
export class LocalStorageSource {
|
||||||
static GetParsed<T>(key: string, defaultValue: T): UIEventSource<T> {
|
|
||||||
return LocalStorageSource.Get(key).sync(
|
private static readonly _cache: Record<string, UIEventSource<string>> = {}
|
||||||
|
|
||||||
|
static getParsed<T>(key: string, defaultValue: T): UIEventSource<T> {
|
||||||
|
return LocalStorageSource.get(key).sync(
|
||||||
(str) => {
|
(str) => {
|
||||||
if (str === undefined) {
|
if (str === undefined) {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
|
@ -17,34 +20,40 @@ export class LocalStorageSource {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
(value) => JSON.stringify(value)
|
(value) => JSON.stringify(value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
static Get(key: string, defaultValue: string = undefined): UIEventSource<string> {
|
static get(key: string, defaultValue: string = undefined): UIEventSource<string> {
|
||||||
|
const cached = LocalStorageSource._cache[key]
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
let saved = defaultValue
|
||||||
try {
|
try {
|
||||||
let saved = localStorage.getItem(key)
|
saved = localStorage.getItem(key)
|
||||||
if (saved === "undefined") {
|
if (saved === "undefined") {
|
||||||
saved = undefined
|
saved = undefined
|
||||||
}
|
}
|
||||||
const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:" + key)
|
|
||||||
|
|
||||||
source.addCallback((data) => {
|
|
||||||
if(data === undefined || data === "" || data === null){
|
|
||||||
localStorage.removeItem(key)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
localStorage.setItem(key, data)
|
|
||||||
} catch (e) {
|
|
||||||
// Probably exceeded the quota with this item!
|
|
||||||
// Lets nuke everything
|
|
||||||
localStorage.clear()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return source
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return new UIEventSource<string>(defaultValue)
|
console.error("Could not get value", key, "from local storage")
|
||||||
}
|
}
|
||||||
|
const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:" + key)
|
||||||
|
|
||||||
|
source.addCallback((data) => {
|
||||||
|
if (data === undefined || data === "" || data === null) {
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key, data)
|
||||||
|
} catch (e) {
|
||||||
|
// Probably exceeded the quota with this item!
|
||||||
|
// Let's nuke everything
|
||||||
|
localStorage.clear()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
LocalStorageSource._cache[key] = source
|
||||||
|
return source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ export default class FilteredLayer {
|
||||||
) {
|
) {
|
||||||
let isDisplayed: UIEventSource<boolean>
|
let isDisplayed: UIEventSource<boolean>
|
||||||
if (layer.syncSelection === "local") {
|
if (layer.syncSelection === "local") {
|
||||||
isDisplayed = LocalStorageSource.GetParsed(
|
isDisplayed = LocalStorageSource.getParsed(
|
||||||
context + "-layer-" + layer.id + "-enabled",
|
context + "-layer-" + layer.id + "-enabled",
|
||||||
layer.shownByDefault,
|
layer.shownByDefault,
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class MenuState {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const visitedBefore = LocalStorageSource.GetParsed<boolean>(
|
const visitedBefore = LocalStorageSource.getParsed<boolean>(
|
||||||
themeid + "thememenuisopened",
|
themeid + "thememenuisopened",
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
|
@ -370,7 +370,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
this.changes,
|
this.changes,
|
||||||
this.geolocation.geolocationState.currentGPSLocation,
|
this.geolocation.geolocationState.currentGPSLocation,
|
||||||
this.indexedFeatures,
|
this.indexedFeatures,
|
||||||
this.reportError
|
this.reportError,
|
||||||
)
|
)
|
||||||
this.favourites = new FavouritesFeatureSource(this)
|
this.favourites = new FavouritesFeatureSource(this)
|
||||||
const longAgo = new Date()
|
const longAgo = new Date()
|
||||||
|
@ -532,7 +532,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
* Selects the feature that is 'i' closest to the map center
|
* Selects the feature that is 'i' closest to the map center
|
||||||
*/
|
*/
|
||||||
private selectClosestAtCenter(i: number = 0) {
|
private selectClosestAtCenter(i: number = 0) {
|
||||||
console.log("Selecting closest",i)
|
console.log("Selecting closest", i)
|
||||||
if (this.userRelatedState.a11y.data !== "never") {
|
if (this.userRelatedState.a11y.data !== "never") {
|
||||||
this.visualFeedback.setData(true)
|
this.visualFeedback.setData(true)
|
||||||
}
|
}
|
||||||
|
@ -908,6 +908,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
* Setup various services for which no reference are needed
|
* Setup various services for which no reference are needed
|
||||||
*/
|
*/
|
||||||
private initActors() {
|
private initActors() {
|
||||||
|
|
||||||
|
if (!this.layout.official) {
|
||||||
|
// Add custom themes to the "visited custom themes"
|
||||||
|
const th = this.layout
|
||||||
|
this.userRelatedState.addUnofficialTheme({
|
||||||
|
id: th.id,
|
||||||
|
icon: th.icon,
|
||||||
|
title: th.title.translations,
|
||||||
|
shortDescription: th.shortDescription.translations ,
|
||||||
|
layers: th.layers.filter(l => l.isNormal()).map(l => l.id)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.selectedElement.addCallback((selected) => {
|
this.selectedElement.addCallback((selected) => {
|
||||||
if (selected === undefined) {
|
if (selected === undefined) {
|
||||||
this.focusOnMap()
|
this.focusOnMap()
|
||||||
|
|
|
@ -55,7 +55,6 @@
|
||||||
|
|
||||||
const customThemes: Store<MinimalLayoutInformation[]> = Stores.ListStabilized<string>(state.installedUserThemes)
|
const customThemes: Store<MinimalLayoutInformation[]> = Stores.ListStabilized<string>(state.installedUserThemes)
|
||||||
.mapD(stableIds => Utils.NoNullInplace(stableIds.map(id => state.getUnofficialTheme(id))))
|
.mapD(stableIds => Utils.NoNullInplace(stableIds.map(id => state.getUnofficialTheme(id))))
|
||||||
|
|
||||||
function filtered(themes: Store<MinimalLayoutInformation[]>): Store<MinimalLayoutInformation[]> {
|
function filtered(themes: Store<MinimalLayoutInformation[]>): Store<MinimalLayoutInformation[]> {
|
||||||
return searchStable.map(search => {
|
return searchStable.map(search => {
|
||||||
if (!search) {
|
if (!search) {
|
||||||
|
|
|
@ -42,8 +42,8 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let customWidth = LocalStorageSource.Get("custom-png-width", "20")
|
let customWidth = LocalStorageSource.get("custom-png-width", "20")
|
||||||
let customHeight = LocalStorageSource.Get("custom-png-height", "20")
|
let customHeight = LocalStorageSource.get("custom-png-height", "20")
|
||||||
|
|
||||||
async function offerCustomPng(): Promise<Blob> {
|
async function offerCustomPng(): Promise<Blob> {
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
export let coordinate: UIEventSource<{ lon: number; lat: number }>
|
export let coordinate: UIEventSource<{ lon: number; lat: number }>
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
|
|
||||||
let comment: UIEventSource<string> = LocalStorageSource.Get("note-text")
|
let comment: UIEventSource<string> = LocalStorageSource.get("note-text")
|
||||||
let created = false
|
let created = false
|
||||||
|
|
||||||
let notelayer: FilteredLayer = state.layerState.filteredLayers.get("note")
|
let notelayer: FilteredLayer = state.layerState.filteredLayers.get("note")
|
||||||
|
|
|
@ -37,7 +37,7 @@ export abstract class EditJsonState<T> {
|
||||||
public readonly osmConnection: OsmConnection
|
public readonly osmConnection: OsmConnection
|
||||||
|
|
||||||
public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>(
|
public readonly showIntro: UIEventSource<"no" | "intro" | "tagrenderings"> = <any>(
|
||||||
LocalStorageSource.Get("studio-show-intro", "intro")
|
LocalStorageSource.get("studio-show-intro", "intro")
|
||||||
)
|
)
|
||||||
|
|
||||||
public readonly expertMode: UIEventSource<boolean>
|
public readonly expertMode: UIEventSource<boolean>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
const store = state.getStoreFor(path)
|
const store = state.getStoreFor(path)
|
||||||
let value = store.data
|
let value = store.data
|
||||||
let hasSeenIntro = UIEventSource.asBoolean(
|
let hasSeenIntro = UIEventSource.asBoolean(
|
||||||
LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false")
|
LocalStorageSource.get("studio-seen-tagrendering-tutorial", "false")
|
||||||
)
|
)
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!hasSeenIntro.data) {
|
if (!hasSeenIntro.data) {
|
||||||
|
|
|
@ -74,7 +74,7 @@ export default class Locale {
|
||||||
if (typeof navigator !== "undefined") {
|
if (typeof navigator !== "undefined") {
|
||||||
browserLanguage = Locale.getBestSupportedLanguage()
|
browserLanguage = Locale.getBestSupportedLanguage()
|
||||||
}
|
}
|
||||||
source = LocalStorageSource.Get("language", browserLanguage)
|
source = LocalStorageSource.get("language", browserLanguage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Utils.runningFromConsole && typeof document !== undefined) {
|
if (!Utils.runningFromConsole && typeof document !== undefined) {
|
||||||
|
|
Loading…
Reference in a new issue