diff --git a/Customizations/JSON/CustomLayoutFromJSON.ts b/Customizations/JSON/CustomLayoutFromJSON.ts index 122bda9..584ecd5 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 ee21712..6472e51 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 94e7002..89ebcd5 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 d2a77ba..5825c25 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 98fc1e1..3f16b02 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 b0f4d0f..a97f0af 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 e896c19..cbc20a3 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 dfcebe6..f6146c4 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 cd395fe..27a3bc0 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 2ad80ef..ac495fb 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 a404043..142e484 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 ff5f2e6..2baacc5 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 906f296..688d143 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 9a162ef..3a3cd37 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 97eee4f..1d2ad4b 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 7fcdfec..f6e48c0 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 3ade0f0..e69de29 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) -});