diff --git a/Customizations/JSON/CustomLayoutFromJSON.ts b/Customizations/JSON/CustomLayoutFromJSON.ts index 122bda95f..584ecd5b5 100644 --- a/Customizations/JSON/CustomLayoutFromJSON.ts +++ b/Customizations/JSON/CustomLayoutFromJSON.ts @@ -36,8 +36,7 @@ export interface TagRenderingConfigJson { } export interface LayerConfigJson { - - id: string; + name: string; title: string | any | TagRenderingConfigJson; description: string | any; minzoom: number | string, @@ -238,16 +237,15 @@ export class CustomLayoutFromJSON { const tr = CustomLayoutFromJSON.TagRenderingFromJson; const tags = CustomLayoutFromJSON.TagsFromJson(json.overpassTags); // We run the icon rendering with the bare minimum of tags (the overpass tags) to get the actual icon - const icon = CustomLayoutFromJSON.TagRenderingFromJson(json.icon).construct({ - tags: new UIEventSource({}) - }).InnerRender(); - + const icon = CustomLayoutFromJSON.TagRenderingFromJson(json.icon).GetContent({id:"node/-1"}); + // @ts-ignore + const id = json.name?.replace(/[^a-zA-Z0-9_-]/g,'') ?? json.id; return new LayerDefinition( - json.id, + id, { description: t(json.description), - name: Translations.WT(t(json.title.render)).txt.replace(/[^a-zA-Z0-9-_]/g, ''), + name: Translations.WT(t(json.name)), icon: icon, minzoom: parseInt(""+json.minzoom), title: tr(json.title), diff --git a/Logic/LayerUpdater.ts b/Logic/LayerUpdater.ts index ee2171290..6472e51ad 100644 --- a/Logic/LayerUpdater.ts +++ b/Logic/LayerUpdater.ts @@ -32,7 +32,7 @@ export class LayerUpdater { state.layoutToUse.addCallback(() => { self.update(state) }); - + self.update(state); } @@ -41,11 +41,13 @@ export class LayerUpdater { state = state ?? State.state; for (const layer of state.layoutToUse.data.layers) { if (state.locationControl.data.zoom < layer.minzoom) { - return undefined; + console.log("Not loading layer ", layer.id, " as it needs at least ",layer.minzoom, "zoom") + continue; } filters.push(layer.overpassFilter); } if (filters.length === 0) { + console.log("No layers loaded at all") return undefined; } return new Or(filters); @@ -103,7 +105,6 @@ export class LayerUpdater { this.sufficentlyZoomed.setData(filter !== undefined); if (filter === undefined) { - console.log("Zoom insufficient to run query") return; } @@ -116,10 +117,10 @@ export class LayerUpdater { const diff = state.layoutToUse.data.widenFactor; - const n = bounds.getNorth() + diff; - const e = bounds.getEast() + diff; - const s = bounds.getSouth() - diff; - const w = bounds.getWest() - diff; + const n = Math.min(90, bounds.getNorth() + diff); + const e = Math.min( 180,bounds.getEast() + diff); + const s = Math.max(-90, bounds.getSouth() - diff); + const w = Math.max(-180, bounds.getWest() - diff); this.previousBounds = {north: n, east: e, south: s, west: w}; diff --git a/Logic/Leaflet/Basemap.ts b/Logic/Leaflet/Basemap.ts index 94e70020e..89ebcd581 100644 --- a/Logic/Leaflet/Basemap.ts +++ b/Logic/Leaflet/Basemap.ts @@ -76,8 +76,8 @@ export class Basemap { location: UIEventSource<{ zoom: number, lat: number, lon: number }>, extraAttribution: UIElement) { this.map = L.map(leafletElementId, { - center: [location.data.lat, location.data.lon], - zoom: location.data.zoom, + center: [location.data.lat ?? 0, location.data.lon ?? 0], + zoom: location.data.zoom ?? 2, layers: [BaseLayers.defaultLayer.layer], }); diff --git a/Logic/Osm/OsmPreferences.ts b/Logic/Osm/OsmPreferences.ts index d2a77ba81..5825c251e 100644 --- a/Logic/Osm/OsmPreferences.ts +++ b/Logic/Osm/OsmPreferences.ts @@ -18,6 +18,8 @@ export class OsmPreferences { osmConnection.OnLoggedIn(() => self.UpdatePreferences()); } + private longPreferences = {}; + /** * OSM preferences can be at most 255 chars * @param key @@ -25,7 +27,15 @@ export class OsmPreferences { * @constructor */ public GetLongPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource { + + if (this.longPreferences[prefix + key] !== undefined) { + return this.longPreferences[prefix + key]; + } + const source = new UIEventSource(undefined); + this.longPreferences[prefix + key] = source; + + console.log("Loading long pref", prefix + key); const allStartWith = prefix + key + "-combined"; // Gives the number of combined preferences @@ -34,22 +44,23 @@ export class OsmPreferences { console.log("Getting long pref " + prefix + key); const self = this; source.addCallback(str => { - if (str === undefined) { - for (const prefKey in self.preferenceSources) { - if (prefKey.startsWith(allStartWith)) { - self.GetPreference(prefKey, "").setData(undefined); - } - } - return; + if (str === undefined || str === "") { + return } let i = 0; while (str !== "") { + if (str === undefined || str === "undefined") { + throw "Long pref became undefined?" + } + if (i > 100) { + throw "This long preference is getting very long... " + } self.GetPreference(allStartWith + "-" + i, "").setData(str.substr(0, 255)); str = str.substr(255); i++; } - length.setData("" + i); + length.setData("" + i); // We use I, the number of preference fields used }); @@ -58,11 +69,17 @@ export class OsmPreferences { source.setData(undefined); return; } - const length = Number(l); + if (l > 25) { + throw "Length to long"; + source.setData(undefined); + return; + } + const prefsCount = Number(l); let str = ""; - for (let i = 0; i < length; i++) { + for (let i = 0; i < prefsCount; i++) { str += self.GetPreference(allStartWith + "-" + i, "").data; } + source.setData(str); source.ping(); console.log("Long preference ", key, " has ", str.length, " chars"); @@ -135,10 +152,7 @@ export class OsmPreferences { } console.log("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15)); - this.preferences.data[k] = v; - this.preferences.ping(); - - if (v === "") { + if (v === undefined || v === "") { this.auth.xhr({ method: 'DELETE', path: '/api/0.6/user/preferences/' + k, diff --git a/Logic/PersonalLayersPanel.ts b/Logic/PersonalLayersPanel.ts index 98fc1e1e6..3f16b02f8 100644 --- a/Logic/PersonalLayersPanel.ts +++ b/Logic/PersonalLayersPanel.ts @@ -10,29 +10,35 @@ import {VerticalCombine} from "../UI/Base/VerticalCombine"; import {FixedUiElement} from "../UI/Base/FixedUiElement"; import {SubtleButton} from "../UI/Base/SubtleButton"; import {PersonalLayout} from "./PersonalLayout"; +import {All} from "../Customizations/Layouts/All"; +import {Layout} from "../Customizations/Layout"; +import {TagDependantUIElement} from "../Customizations/UIElementConstructor"; +import {TagRendering} from "../Customizations/TagRendering"; export class PersonalLayersPanel extends UIElement { private checkboxes: UIElement[] = []; - private updateButton: UIElement; - constructor() { super(State.state.favouriteLayers); this.ListenTo(State.state.osmConnection.userDetails); - - const t = Translations.t.favourite; - const favs = State.state.favouriteLayers.data ?? []; - - this.updateButton = new SubtleButton("./assets/reload.svg", t.reload) - .onClick(() => { - State.state.layerUpdater.ForceRefresh(); - State.state.layoutToUse.ping(); - }) + this.UpdateView([]); + const self = this; + State.state.installedThemes.addCallback(extraThemes => { + self.UpdateView(extraThemes.map(layout => layout.layout)); + self.Update(); + }) + } + + + private UpdateView(extraThemes: Layout[]) { + this.checkboxes = []; + const favs = State.state.favouriteLayers.data ?? []; const controls = new Map>(); - for (const layout of AllKnownLayouts.layoutsList) { + const allLayouts = AllKnownLayouts.layoutsList.concat(extraThemes); + for (const layout of allLayouts) { if (layout.name === PersonalLayout.NAME) { continue; @@ -41,7 +47,7 @@ export class PersonalLayersPanel extends UIElement { State.state.osmConnection.userDetails.data.name !== "Pieter Vander Vennet") { continue } - + const header = new Combine([ `
`, @@ -54,14 +60,22 @@ export class PersonalLayersPanel extends UIElement { this.checkboxes.push(header); for (const layer of layout.layers) { + + let icon = layer.icon; + if (icon !== undefined && typeof (icon) !== "string") { + icon = icon.GetContent({"id": "node/-1"}) ?? "./assets/bug.svg"; + } const image = (layer.icon ? `` : Img.checkmark); const noimage = (layer.icon ? `` : Img.no_checkmark); - let name = layer.name; - if(typeof (name) !== "string"){ + let name = layer.name ?? layer.id; + if (name === undefined) { + continue; + } + if (typeof (name) !== "string") { name = name.InnerRender(); } - + const content = new Combine([ "", "", name ?? "", " ", @@ -73,7 +87,7 @@ export class PersonalLayersPanel extends UIElement { ]), new Combine([ "", - noimage, "", + noimage, "", "", content, "" @@ -115,7 +129,6 @@ export class PersonalLayersPanel extends UIElement { return new Combine([ t.panelIntro, - this.updateButton, ...this.checkboxes ], "custom-layer-panel").Render(); } diff --git a/Logic/TagsFilter.ts b/Logic/TagsFilter.ts index b0f4d0fee..a97f0af17 100644 --- a/Logic/TagsFilter.ts +++ b/Logic/TagsFilter.ts @@ -166,7 +166,6 @@ export class Tag extends TagsFilter { `${v}` } - console.log("Humanizing", this) if (typeof (this.value) === "string") { return this.key + (this.invertValue ? "!=": "=") + v; }else{ diff --git a/State.ts b/State.ts index e896c1961..cbc20a324 100644 --- a/State.ts +++ b/State.ts @@ -13,6 +13,7 @@ import {LayerUpdater} from "./Logic/LayerUpdater"; import {UIEventSource} from "./Logic/UIEventSource"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {QueryParameters} from "./Logic/Web/QueryParameters"; +import {CustomLayoutFromJSON} from "./Customizations/JSON/CustomLayoutFromJSON"; /** * Contains the global state: a bunch of UI-event sources @@ -23,7 +24,7 @@ export class State { // The singleton of the global state public static state: State; - public static vNumber = "0.0.6d"; + public static vNumber = "0.0.6f"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { @@ -121,6 +122,7 @@ export class State { */ public readonly saveTimeout = new UIEventSource(30 * 1000); public layoutDefinition: string; + public installedThemes: UIEventSource<{ layout: Layout; definition: string }[]>; constructor(layoutToUse: Layout) { @@ -180,6 +182,34 @@ export class State { [], layers => Utils.Dedup(layers)?.join(";") ); + this.installedThemes = this.osmConnection._preferencesHandler.preferences.map<{ layout: Layout, definition: string }[]>(allPreferences => { + const installedThemes: { layout: Layout, definition: string }[] = []; + if (allPreferences === undefined) { + return installedThemes; + } + for (const allPreferencesKey in allPreferences) { + const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/); + if (themename && themename[1] !== "") { + const customLayout = State.state.osmConnection.GetLongPreference("installed-theme-" + themename[1]); + if(customLayout.data === undefined){ + console.log("No data defined for ", themename[1]); + continue; + } + try { + installedThemes.push({ + layout: CustomLayoutFromJSON.FromQueryParam(customLayout.data), + definition: customLayout.data + }); + } catch (e) { + console.warn("Could not parse custom layout from preferences: ", allPreferencesKey, e, customLayout.data); + } + } + } + + return installedThemes; + + }); + Locale.language.syncWith(this.osmConnection.GetPreference("language")); diff --git a/UI/CustomThemeGenerator/ThemeGenerator.ts b/UI/CustomThemeGenerator/ThemeGenerator.ts index dfcebe6aa..f6146c477 100644 --- a/UI/CustomThemeGenerator/ThemeGenerator.ts +++ b/UI/CustomThemeGenerator/ThemeGenerator.ts @@ -308,7 +308,7 @@ class LayerGenerator extends UIElement { new FixedUiElement("

A layer is a collection of related objects which have the same or very similar tags renderings. In general, all objects of one layer have the same icon (or at least very similar icons)

"), - createFieldUI("Name", "id", layerConfig, {description: "The name of this layer"}), + createFieldUI("Name", "name", layerConfig, {description: "The name of this layer"}), createFieldUI("A description of objects for this layer", "description", layerConfig, {description: "The description of this layer"}), createFieldUI("Minimum zoom level", "minzoom", layerConfig, { type: "nat", diff --git a/UI/MoreScreen.ts b/UI/MoreScreen.ts index cd395fe82..27a3bc016 100644 --- a/UI/MoreScreen.ts +++ b/UI/MoreScreen.ts @@ -18,7 +18,7 @@ export class MoreScreen extends UIElement { constructor() { super(State.state.locationControl); this.ListenTo(State.state.osmConnection.userDetails); - this.ListenTo(State.state.osmConnection._preferencesHandler.preferences); + this.ListenTo(State.state.installedThemes); } @@ -30,10 +30,6 @@ export class MoreScreen extends UIElement { return undefined; } - if (layout.name === PersonalLayout.NAME) { - return undefined; - } - const currentLocation = State.state.locationControl.data; let linkText = `./${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}` @@ -80,63 +76,28 @@ export class MoreScreen extends UIElement { }) )); - els.push(new VariableUiElement( - State.state.osmConnection.userDetails.map(userDetails => { - if (userDetails.csCount < State.userJourney.customLayoutUnlock) { - return ""; - } - return new SubtleButton("./assets/star.svg", - new Combine([ - "", - Translations.t.favourite.title, - "", - "
", Translations.t.favourite.description]), { - url: "https://pietervdvn.github.io/MapComplete/personal.html", - newTab: false - }).Render(); - }) - )); - for (const k in AllKnownLayouts.allSets) { + + + if (k === PersonalLayout.NAME) { + if (State.state.osmConnection.userDetails.data.csCount < State.userJourney.customLayoutUnlock) { + continue; + } + } + + els.push(this.createLinkButton(AllKnownLayouts.allSets[k])); } - const installedThemes = State.state.osmConnection._preferencesHandler.preferences.map(allPreferences => { - const installedThemes = []; - if(allPreferences === undefined){ - return installedThemes; - } - for (const allPreferencesKey in allPreferences) { - "mapcomplete-installed-theme-Superficie-combined-length" - const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/); - if(themename){ - installedThemes.push(themename[1]); - } - } - - return installedThemes; - - }) - const customThemesNames = installedThemes.data ?? []; + const customThemesNames = State.state.installedThemes.data ?? []; if (customThemesNames !== []) { els.push(Translations.t.general.customThemeIntro) } - console.log(customThemesNames); - for (const installedThemeName of customThemesNames) { - if(installedThemeName === ""){ - continue; - } - const customThemeDefinition = State.state.osmConnection.GetLongPreference("installed-theme-" + installedThemeName); - try { - const layout = CustomLayoutFromJSON.FromQueryParam(customThemeDefinition.data); - els.push(this.createLinkButton(layout, customThemeDefinition.data)); - } catch (e) { - console.log(customThemeDefinition.data); - console.warn("Could not parse custom layout from preferences: ", installedThemeName, e); - } + for (const installed of State.state.installedThemes.data) { + els.push(this.createLinkButton(installed.layout, installed.definition)); } diff --git a/UI/ShareScreen.ts b/UI/ShareScreen.ts index 2ad80ef63..ac495fbaa 100644 --- a/UI/ShareScreen.ts +++ b/UI/ShareScreen.ts @@ -135,8 +135,7 @@ export class ShareScreen extends UIElement { let literalText = "https://pietervdvn.github.io/MapComplete/" + layout.name + ".html" - const parts = - Utils.NoEmpty(Utils.NoNull(optionParts.map((eventSource) => eventSource.data))); + const parts = Utils.NoEmpty(Utils.NoNull(optionParts.map((eventSource) => eventSource.data))); let hash = ""; if (State.state.layoutDefinition !== undefined) { @@ -161,11 +160,7 @@ export class ShareScreen extends UIElement { ); - this._link = new VariableUiElement( - url.map((url) => { - return `` - }) - ); + this._editLayout = new FixedUiElement(""); if ((State.state.layoutDefinition !== undefined)) { @@ -177,7 +172,7 @@ export class ShareScreen extends UIElement { return ""; } return `

Edit this theme

` + - `Click here to edit` + `Click here to edit` } )); @@ -187,11 +182,15 @@ export class ShareScreen extends UIElement { const status = new UIEventSource(" "); this._linkStatus = new VariableUiElement(status); const self = this; - this._link.onClick(async () => { + this._link = new VariableUiElement( + url.map((url) => { + return `` + }) + ).onClick(async () => { const shareData = { - title: Translations.W(layout.name).InnerRender(), - text: Translations.W(layout.description).InnerRender(), + title: Translations.W(layout.name)?.InnerRender() ?? "", + text: Translations.W(layout.description)?.InnerRender() ?? "", url: self._link.data, } diff --git a/assets/themes/aed/aed.json b/assets/themes/aed/aed.json index a40404325..142e4844b 100644 --- a/assets/themes/aed/aed.json +++ b/assets/themes/aed/aed.json @@ -9,7 +9,7 @@ "maintainer": "Pieter Vander Vennet", "layers": [ { - "id": "Defibrillator", + "name": "Defibrillator", "title": { "key": "*", "render": { diff --git a/assets/themes/artwork/artwork.json b/assets/themes/artwork/artwork.json index ff5f2e6e8..2baacc562 100644 --- a/assets/themes/artwork/artwork.json +++ b/assets/themes/artwork/artwork.json @@ -20,7 +20,7 @@ }, "layers": [ { - "id": "Artwork", + "name": "Artwork", "title": { "key": "*", "render": { diff --git a/assets/themes/bookcases/Bookcases.json b/assets/themes/bookcases/Bookcases.json index 906f296e5..688d143e9 100644 --- a/assets/themes/bookcases/Bookcases.json +++ b/assets/themes/bookcases/Bookcases.json @@ -20,7 +20,7 @@ ], "layers": [ { - "id": "Bookcases", + "name": "Bookcases", "title": { "key": "*", "render": { diff --git a/assets/themes/toilets/toilets.json b/assets/themes/toilets/toilets.json index 9a162ef2b..3a3cd37be 100644 --- a/assets/themes/toilets/toilets.json +++ b/assets/themes/toilets/toilets.json @@ -1,7 +1,7 @@ { "layers": [ { - "id": "Toilet", + "name": "Toilet", "title": { "key": "*", "render": "Toilet" diff --git a/index.css b/index.css index 97eee4fcb..1d2ad4bce 100644 --- a/index.css +++ b/index.css @@ -1276,7 +1276,7 @@ form { .subtle-button img{ - width: 3em; + max-width: 3em; max-height: 3em; margin-right: 0.5em; padding: 0.5em; @@ -1313,7 +1313,6 @@ form { .custom-layer-panel-header-img img { max-width: 3em; - width: 100%; max-height: 3em; padding: 0.5em; } diff --git a/index.ts b/index.ts index 7fcdfec29..f6e48c0fb 100644 --- a/index.ts +++ b/index.ts @@ -138,7 +138,6 @@ function setupAllLayerElements() { } } if (presetCount == 0) { - console.log("No presets defined - not creating the StrayClickHandler"); return; } @@ -155,20 +154,35 @@ function setupAllLayerElements() { setupAllLayerElements(); -if (layoutToUse === AllKnownLayouts.allSets[PersonalLayout.NAME]) { - State.state.favouriteLayers.addCallback((favs: string[]) => { - layoutToUse.layers = []; - for (const fav of favs) { - const layer = AllKnownLayouts.allLayers[fav]; - if (!!layer) { - layoutToUse.layers.push(layer); - } - setupAllLayerElements(); - } - ; - State.state.locationControl.ping(); - }) +function updateFavs() { + const favs = State.state.favouriteLayers.data ?? []; + + layoutToUse.layers = []; + for (const fav of favs) { + const layer = AllKnownLayouts.allLayers[fav]; + if (!!layer) { + layoutToUse.layers.push(layer); + } + + for (const layouts of State.state.installedThemes.data) { + for (const layer of layouts.layout.layers) { + if (layer.id === fav) { + layoutToUse.layers.push(layer); + } + } + } + } + + setupAllLayerElements(); + State.state.layerUpdater.ForceRefresh(); + State.state.locationControl.ping(); +} + +if (layoutToUse === AllKnownLayouts.allSets[PersonalLayout.NAME]) { + + State.state.favouriteLayers.addCallback(updateFavs); + State.state.installedThemes.addCallback(updateFavs); } @@ -229,3 +243,4 @@ if ((window != window.top && !State.state.featureSwitchWelcomeMessage.data) || S new GeoLocationHandler().AttachTo("geolocate-button"); +State.state.locationControl.ping(); \ No newline at end of file diff --git a/test.ts b/test.ts index 3ade0f050..e69de29bb 100644 --- a/test.ts +++ b/test.ts @@ -1,15 +0,0 @@ -import {OsmConnection} from "./Logic/Osm/OsmConnection"; -import {UIEventSource} from "./Logic/UIEventSource"; - -const conn = new OsmConnection(true, new UIEventSource(undefined)); -conn.AttemptLogin(); - -conn.userDetails.addCallback(userDetails => { - if (!userDetails.loggedIn) { - return; - } - const str = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - console.log(str.length); - conn.GetLongPreference("test").setData(str); -// console.log(got.length) -});