diff --git a/Logic/Actors/AvailableBaseLayersImplementation.ts b/Logic/Actors/AvailableBaseLayersImplementation.ts index 80cdb9c18c..d5df62bab2 100644 --- a/Logic/Actors/AvailableBaseLayersImplementation.ts +++ b/Logic/Actors/AvailableBaseLayersImplementation.ts @@ -103,7 +103,8 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL id: id, name: name, layer: () => L.tileLayer.provider(id, { - maxNativeZoom: layer.options?.maxZoom + maxNativeZoom: layer.options?.maxZoom, + maxZoom: Math.max(layer.options?.maxZoom ?? 19, 21) }), min_zoom: 1, max_zoom: layer.options.maxZoom, diff --git a/Logic/Osm/OsmPreferences.ts b/Logic/Osm/OsmPreferences.ts index 3a212ade5f..14880f3297 100644 --- a/Logic/Osm/OsmPreferences.ts +++ b/Logic/Osm/OsmPreferences.ts @@ -5,7 +5,7 @@ import {Utils} from "../../Utils"; export class OsmPreferences { public preferences = new UIEventSource({}, "all-osm-preferences"); - public preferenceSources: any = {} + private readonly preferenceSources = new Map>() private auth: any; private userDetails: UIEventSource; private longPreferences = {}; @@ -29,6 +29,7 @@ export class OsmPreferences { return this.longPreferences[prefix + key]; } + const source = new UIEventSource(undefined, "long-osm-preference:" + prefix + key); this.longPreferences[prefix + key] = source; @@ -36,6 +37,10 @@ export class OsmPreferences { // Gives the number of combined preferences const length = this.GetPreference(allStartWith + "-length", ""); + if( (allStartWith + "-length").length > 255){ + throw "This preference key is too long, it has "+key.length+" characters, but at most "+(255 - "-length".length - "-combined".length - prefix.length)+" characters are allowed" + } + const self = this; source.addCallback(str => { if (str === undefined || str === "") { @@ -101,24 +106,21 @@ export class OsmPreferences { if (key.length >= 255) { throw "Preferences: key length to big"; } - if (this.preferenceSources[key] !== undefined) { - return this.preferenceSources[key]; + const cached = this.preferenceSources.get(key) + if (cached !== undefined) { + return cached; } if (this.userDetails.data.loggedIn && this.preferences.data[key] === undefined) { this.UpdatePreferences(); } + const pref = new UIEventSource(this.preferences.data[key], "osm-preference:" + key); pref.addCallback((v) => { - this.SetPreference(key, v); + this.UploadPreference(key, v); }); - this.preferences.addCallback((prefs) => { - if (prefs[key] !== undefined) { - pref.setData(prefs[key]); - } - }); - - this.preferenceSources[key] = pref; + + this.preferenceSources.set(key, pref) return pref; } @@ -167,11 +169,25 @@ export class OsmPreferences { const v = pref.getAttribute("v"); self.preferences.data[k] = v; } + + // We merge all the preferences: new keys are uploaded + // For differing values, the server overrides local changes + self.preferenceSources.forEach((preference, key) => { + const osmValue = self.preferences[key] + if(osmValue === undefined){ + // OSM doesn't know this value yet + self.UploadPreference(key, preference.data) + } else { + // OSM does have a value - set it + preference.setData(osmValue) + } + }) + self.preferences.ping(); }); } - private SetPreference(k: string, v: string) { + private UploadPreference(k: string, v: string) { if (!this.userDetails.data.loggedIn) { console.debug(`Not saving preference ${k}: user not logged in`); return; diff --git a/Logic/State/UserRelatedState.ts b/Logic/State/UserRelatedState.ts index 663edc0d25..f46eb90d9d 100644 --- a/Logic/State/UserRelatedState.ts +++ b/Logic/State/UserRelatedState.ts @@ -29,17 +29,6 @@ export default class UserRelatedState extends ElementsState { */ public favouriteLayers: UIEventSource; - /** - * WHich other themes the user previously visited - */ - public installedThemes: UIEventSource<{ - id: string, // The id doubles as the URL - icon: string, - title: any, - shortDescription: any - }[]>; - - constructor(layoutToUse: LayoutConfig, options?: { attemptLogin: true | boolean }) { super(layoutToUse); @@ -73,50 +62,15 @@ export default class UserRelatedState extends ElementsState { }) } - this.installedThemes = this.osmConnection.GetLongPreference("installed-themes").map( - str => { - if (str === undefined || str === "") { - return [] - } - try { - return JSON.parse(str); - } catch (e) { - console.warn("Could not parse preference with installed themes due to ", e, "\nThe offending string is", str) - return [] - } - }, [], (installed) => JSON.stringify(installed) - ) - - - const self = this; - - if (this.layoutToUse?.id?.startsWith("http")) { - this.installedThemes.addCallbackAndRun(currentThemes => { - if (currentThemes === undefined) { - // We wait till we are logged in - return - } - - if (self.osmConnection.isLoggedIn.data == false) { - return; - } - - if (currentThemes.some(installed => installed.id === this.layoutToUse.id)) { - // Already added to the 'installed theme' list - return; - } - - console.log("Current installed themes are", this.installedThemes.data) - currentThemes.push({ - id: self.layoutToUse.id, - icon: self.layoutToUse.icon, - title: self.layoutToUse.title.translations, - shortDescription: self.layoutToUse.shortDescription.translations - }) - self.installedThemes.ping() - console.log("Registered " + self.layoutToUse.id + " as installed themes") - - }) + if (this.layoutToUse !== undefined && !this.layoutToUse.official) { + console.log("Marking unofficial theme as visited") + this.osmConnection.GetLongPreference("unofficial-theme-" + this.layoutToUse.id) + .setData(JSON.stringify({ + id: this.layoutToUse.id, + icon: this.layoutToUse.icon, + title: this.layoutToUse.title.translations, + shortDescription: this.layoutToUse.shortDescription.translations + })) } diff --git a/UI/BigComponents/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts index f984bba2c5..fb2e03566c 100644 --- a/UI/BigComponents/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -120,14 +120,42 @@ export default class MoreScreen extends Combine { } private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses): BaseUIElement { - return new VariableUiElement(state.installedThemes.map(customThemes => { - if (customThemes.length <= 0) { + const prefix = "mapcomplete-unofficial-theme-"; + return new VariableUiElement(state.osmConnection.preferencesHandler.preferences.map(allPreferences => { + console.log("All preferences are ", allPreferences) + const allThemes: BaseUIElement[] = [] + for (const key in allPreferences) { + if (key.startsWith(prefix) && key.endsWith("-combined-length")) { + const id = key.substring(0, key.length - "-length".length) + const length = Number(allPreferences[key]) + + let str = ""; + for (let i = 0; i < length; i++) { + str += allPreferences[id + "-" + i] + } + console.log("Theme " + id + " has settings ", str) + try { + const value: { + id: string + icon: string, + title: any, + shortDescription: any + } = JSON.parse(str) + + const link = MoreScreen.createLinkButton(state, value, true).SetClass(buttonClass) + allThemes.push(link) + } catch (e) { + console.error("Could not parse unofficial theme information for " + id, e) + } + } + } + + if (allThemes.length <= 0) { return undefined; } - const customThemeButtons = customThemes.map(theme => MoreScreen.createLinkButton(state, theme, true)?.SetClass(buttonClass)) return new Combine([ Translations.t.general.customThemeIntro.Clone(), - new Combine(customThemeButtons).SetClass(themeListClasses) + new Combine(allThemes).SetClass(themeListClasses) ]); })); }