diff --git a/InitUiElements.ts b/InitUiElements.ts index b8a96e9fc2..a134fcdf91 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -74,15 +74,8 @@ export class InitUiElements { } - InitUiElements.InitBaseMap(); - - InitUiElements.setupAllLayerElements(); - - if (layoutToUse.customCss !== undefined) { - Utils.LoadCustomCss(layoutToUse.customCss); - } - function updateFavs() { + // This is purely for the personal theme to load the layers there const favs = State.state.favouriteLayers.data ?? []; layoutToUse.layers.splice(0, layoutToUse.layers.length); @@ -103,19 +96,16 @@ export class InitUiElements { } } } - - InitUiElements.setupAllLayerElements(); - State.state.layerUpdater.ForceRefresh(); State.state.layoutToUse.ping(); - + State.state.layerUpdater?.ForceRefresh(); } - if (layoutToUse.id === personal.id) { - State.state.favouriteLayers.addCallback(updateFavs); - State.state.installedThemes.addCallback(updateFavs); + if (layoutToUse.customCss !== undefined) { + Utils.LoadCustomCss(layoutToUse.customCss); } + InitUiElements.InitBaseMap(); InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { new UserBadge().AttachTo('userbadge'); @@ -162,7 +152,17 @@ export class InitUiElements { , State.state.featureSwitchGeolocation) .AttachTo("geolocate-button"); - State.state.locationControl.ping(); + + updateFavs(); + InitUiElements.setupAllLayerElements(); + + if (layoutToUse.id === personal.id) { + State.state.favouriteLayers.addCallback(updateFavs); + State.state.installedThemes.addCallback(updateFavs); + }else{ + State.state.locationControl.ping(); + } + // Reset the loading message once things are loaded new CenterMessageBox().AttachTo("centermessage"); @@ -209,7 +209,6 @@ export class InitUiElements { const isOpened = new UIEventSource(true); const fullOptions = new FullWelcomePaneWithTabs(() => { - console.log("Closing the welcome message...") isOpened.setData(false); }); @@ -325,7 +324,7 @@ export class InitUiElements { const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap); State.state.layerUpdater = updater; - const source = new FeaturePipeline(state.filteredLayers.data, updater, state.layoutToUse, state.changes, state.locationControl); + const source = new FeaturePipeline(state.filteredLayers, updater, state.layoutToUse, state.changes, state.locationControl); source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => { diff --git a/Logic/Actors/InstalledThemes.ts b/Logic/Actors/InstalledThemes.ts index b21871d970..42dd949d64 100644 --- a/Logic/Actors/InstalledThemes.ts +++ b/Logic/Actors/InstalledThemes.ts @@ -13,7 +13,7 @@ export default class InstalledThemes { return installedThemes; } const invalidThemes = [] - for (var allPreferencesKey in allPreferences) { + for (const allPreferencesKey in allPreferences) { const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/); if (themename && themename[1] !== "") { const customLayout = osmConnection.GetLongPreference("installed-theme-" + themename[1]); @@ -37,7 +37,7 @@ export default class InstalledThemes { } InstalledThemes.DeleteInvalid(osmConnection, invalidThemes); - + return installedThemes; }); diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index 5c4a34d7fd..ce362425e5 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -44,10 +44,15 @@ export default class SelectedFeatureHandler { // Feature already selected return; } + + const hash = this._hash.data; + if(hash === undefined || hash === "" || hash === "#"){ + return; + } console.log("Selecting a feature from the hash...") for (const feature of features) { const id = feature.feature?.properties?.id; - if(id === this._hash.data){ + if(id === hash){ this._selectedFeature.setData(feature.feature); break; } diff --git a/Logic/Actors/UpdateFromOverpass.ts b/Logic/Actors/UpdateFromOverpass.ts index 1b2dc0675c..f53d33c8e8 100644 --- a/Logic/Actors/UpdateFromOverpass.ts +++ b/Logic/Actors/UpdateFromOverpass.ts @@ -7,18 +7,18 @@ import Bounds from "../../Models/Bounds"; import FeatureSource from "../FeatureSource/FeatureSource"; -export default class UpdateFromOverpass implements FeatureSource{ +export default class UpdateFromOverpass implements FeatureSource { /** * The last loaded features of the geojson */ - public readonly features: UIEventSource<{feature:any, freshness: Date}[]> = new UIEventSource(undefined); + public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource(undefined); public readonly sufficientlyZoomed: UIEventSource; public readonly runningQuery: UIEventSource = new UIEventSource(false); - public readonly retries: UIEventSource = new UIEventSource(0); - + public readonly timeout: UIEventSource = new UIEventSource(0); + private readonly retries: UIEventSource = new UIEventSource(0); /** * The previous bounds for which the query has been run at the given zoom level * @@ -44,7 +44,7 @@ export default class UpdateFromOverpass implements FeatureSource{ const self = this; this.sufficientlyZoomed = location.map(location => { - if(location?.zoom === undefined){ + if (location?.zoom === undefined) { return false; } let minzoom = Math.min(...layoutToUse.data.layers.map(layer => layer.minzoom ?? 18)); @@ -55,11 +55,11 @@ export default class UpdateFromOverpass implements FeatureSource{ // This update removes all data on all layers -> erase the map on lower levels too this._previousBounds.set(i, []); } - + layoutToUse.addCallback(() => { self.update() }); - location.addCallbackAndRun(() => { + location.addCallback(() => { self.update() }); } @@ -74,17 +74,17 @@ export default class UpdateFromOverpass implements FeatureSource{ private GetFilter() { const filters: TagsFilter[] = []; for (const layer of this._layoutToUse.data.layers) { - if(typeof(layer) === "string"){ + if (typeof (layer) === "string") { continue; } if (this._location.data.zoom < layer.minzoom) { continue; } - if(layer.doNotDownload){ + if (layer.doNotDownload) { continue; } - - + + // Check if data for this layer has already been loaded let previouslyLoaded = false; for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) { @@ -94,7 +94,7 @@ export default class UpdateFromOverpass implements FeatureSource{ } for (const previousLoadedBound of previousLoadedBounds) { previouslyLoaded = previouslyLoaded || this.IsInBounds(previousLoadedBound); - if(previouslyLoaded){ + if (previouslyLoaded) { break; } } @@ -109,6 +109,7 @@ export default class UpdateFromOverpass implements FeatureSource{ } return new Or(filters); } + private update(): void { const filter = this.GetFilter(); if (filter === undefined) { @@ -145,21 +146,36 @@ export default class UpdateFromOverpass implements FeatureSource{ function (reason) { self.retries.data++; self.ForceRefresh(); - console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec)`, undefined); + self.timeout.setData(self.retries.data * 5); + console.log(`QUERY FAILED (retrying in ${5 * self.retries.data} sec)`, reason); self.retries.ping(); - self.runningQuery.setData(false) - window?.setTimeout( - function () { - self.update() - }, self.retries.data * 5000 - ) + self.runningQuery.setData(false); + + function countDown() { + window?.setTimeout( + function () { + console.log("Countdown: ", self.timeout.data) + if (self.timeout.data > 1) { + self.timeout.setData(self.timeout.data - 1); + window.setTimeout( + countDown, + 1000 + ) + } else { + self.timeout.setData(0); + self.update() + } + }, 1000 + ) + } + countDown(); + } ); - - - + } + private IsInBounds(bounds: Bounds): boolean { if (this._previousBounds === undefined) { return false; @@ -171,8 +187,6 @@ export default class UpdateFromOverpass implements FeatureSource{ b.getEast() <= bounds.east && b.getWest() >= bounds.west; } - - - - + + } \ No newline at end of file diff --git a/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts b/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts index 1ac8be76f3..f4be692288 100644 --- a/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts +++ b/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts @@ -12,7 +12,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource { public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; - constructor(layers: { layerDef: LayerConfig }[], upstream: FeatureSource) { + constructor(layers: UIEventSource<{ layerDef: LayerConfig }[]>, upstream: FeatureSource) { this.features = upstream.features.map(features => { const newFeatures: { feature: any, freshness: Date }[] = []; if(features === undefined){ @@ -29,7 +29,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource { let foundALayer = false; - for (const layer of layers) { + for (const layer of layers.data) { if (layer.layerDef.overpassTags.matchesProperties(f.feature.properties)) { foundALayer = true; if (layer.layerDef.passAllFeatures) { diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index ef7ff6fc23..cfe8989851 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -16,7 +16,7 @@ export default class FeaturePipeline implements FeatureSource { public features: UIEventSource<{ feature: any; freshness: Date }[]>; - constructor(flayers: { isDisplayed: UIEventSource, layerDef: LayerConfig }[], + constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource, layerDef: LayerConfig }[]>, updater: FeatureSource, layout: UIEventSource, newPoints: FeatureSource, diff --git a/Logic/FeatureSource/FilteringFeatureSource.ts b/Logic/FeatureSource/FilteringFeatureSource.ts index d63d412a14..931a472a5e 100644 --- a/Logic/FeatureSource/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/FilteringFeatureSource.ts @@ -6,26 +6,27 @@ import Loc from "../../Models/Loc"; export default class FilteringFeatureSource implements FeatureSource { public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); - constructor(layers: { + constructor(layers: UIEventSource<{ isDisplayed: UIEventSource, layerDef: LayerConfig - }[], + }[]>, location: UIEventSource, upstream: FeatureSource) { const self = this; - - const layerDict = {}; - for (const layer of layers) { - layerDict[layer.layerDef.id] = layer; - } - + function update() { - console.log("Updating the filtering layer") + + const layerDict = {}; + for (const layer of layers.data) { + layerDict[layer.layerDef.id] = layer; + } + + console.log("Updating the filtering layer, input ", upstream.features.data.length, "features") const features: { feature: any, freshness: Date }[] = upstream.features.data; - - + + const newFeatures = features.filter(f => { const layerId = f.feature._matching_layer_id; if (layerId !== undefined) { @@ -42,7 +43,7 @@ export default class FilteringFeatureSource implements FeatureSource { } } // Does it match any other layer - e.g. because of a switch? - for (const toCheck of layers) { + for (const toCheck of layers.data) { if (!FilteringFeatureSource.showLayer(toCheck, location)) { continue; } @@ -53,6 +54,8 @@ export default class FilteringFeatureSource implements FeatureSource { return false; }); + console.log("Updating the filtering layer, output ", newFeatures.length, "features") + self.features.setData(newFeatures); } @@ -63,20 +66,33 @@ export default class FilteringFeatureSource implements FeatureSource { location.map(l => { // We want something that is stable for the shown layers const displayedLayerIndexes = []; - for (let i = 0; i < layers.length; i++) { - if (l.zoom < layers[i].layerDef.minzoom) { + for (let i = 0; i < layers.data.length; i++) { + const layer = layers.data[i]; + if (l.zoom < layer.layerDef.minzoom) { continue; } - if (!layers[i].isDisplayed.data) { + if (!layer.isDisplayed.data) { continue; } displayedLayerIndexes.push(i); } return displayedLayerIndexes.join(",") - }, layers.map(l => l.isDisplayed)) - .addCallback(() => { - update(); - }); + }).addCallback(() => { + update(); + }); + + layers.addCallback(update); + + const registered = new Set>(); + layers.addCallback(layers => { + for (const layer of layers) { + if(registered.has(layer.isDisplayed)){ + continue; + } + registered.add(layer.isDisplayed); + layer.isDisplayed.addCallback(update); + } + }) update(); diff --git a/Logic/FeatureSource/LocalStorageSource.ts b/Logic/FeatureSource/LocalStorageSource.ts index 41711f8ded..39d96a6e23 100644 --- a/Logic/FeatureSource/LocalStorageSource.ts +++ b/Logic/FeatureSource/LocalStorageSource.ts @@ -9,18 +9,21 @@ export default class LocalStorageSource implements FeatureSource { constructor(layout: UIEventSource) { this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) const key = LocalStorageSaver.storageKey + layout.data.id - try { - const fromStorage = localStorage.getItem(key); - if (fromStorage == null) { - return; - } - const loaded = JSON.parse(fromStorage); - this.features.setData(loaded); - console.log("Loaded ",loaded.length," features from localstorage as cache") - } catch (e) { - console.log("Could not load features from localStorage:", e) - localStorage.removeItem(key) - } + layout.addCallbackAndRun(_ => { + + try { + const fromStorage = localStorage.getItem(key); + if (fromStorage == null) { + return; + } + const loaded = JSON.parse(fromStorage); + this.features.setData(loaded); + console.log("Loaded ", loaded.length, " features from localstorage as cache") + } catch (e) { + console.log("Could not load features from localStorage:", e) + localStorage.removeItem(key) + } + }) } } \ No newline at end of file diff --git a/Logic/FeatureSource/NoOverlapSource.ts b/Logic/FeatureSource/NoOverlapSource.ts index 73d69de805..c2af365328 100644 --- a/Logic/FeatureSource/NoOverlapSource.ts +++ b/Logic/FeatureSource/NoOverlapSource.ts @@ -12,16 +12,12 @@ export default class NoOverlapSource { features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<{ feature: any, freshness: Date }[]>([]); - constructor(layers: { + constructor(layers: UIEventSource<{ layerDef: LayerConfig - }[], + }[]>, upstream: FeatureSource) { - const layerDict = {}; let noOverlapRemoval = true; - const layerIds = [] - for (const layer of layers) { - layerDict[layer.layerDef.id] = layer; - layerIds.push(layer.layerDef.id); + for (const layer of layers.data) { if ((layer.layerDef.hideUnderlayingFeaturesMinPercentage ?? 0) !== 0) { noOverlapRemoval = false; } @@ -31,13 +27,22 @@ export default class NoOverlapSource { return; } - this.features = upstream.features.map( features => { if (features === undefined) { return; } - + + const layerIds = [] + const layerDict = {}; + for (const layer of layers.data) { + layerDict[layer.layerDef.id] = layer; + layerIds.push(layer.layerDef.id); + if ((layer.layerDef.hideUnderlayingFeaturesMinPercentage ?? 0) !== 0) { + noOverlapRemoval = false; + } + } + // There is overlap removal active // We partition all the features with their respective layerIDs const partitions = {}; @@ -67,7 +72,7 @@ export default class NoOverlapSource { guardPartition.map(f => f.feature), percentage ); - if(!doesOverlap){ + if (!doesOverlap) { newPartition.push(mightBeDeleted); } } diff --git a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts index 66faccfbca..97f340de85 100644 --- a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts +++ b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts @@ -6,29 +6,26 @@ import {GeoOperations} from "../GeoOperations"; export default class WayHandlingApplyingFeatureSource implements FeatureSource { features: UIEventSource<{ feature: any; freshness: Date }[]>; - constructor(layers: { + constructor(layers: UIEventSource<{ layerDef: LayerConfig - }[], + }[]>, upstream: FeatureSource) { - const layerDict = {}; - let allDefaultWayHandling = true; - for (const layer of layers) { - layerDict[layer.layerDef.id] = layer; - if (layer.layerDef.wayHandling !== LayerConfig.WAYHANDLING_DEFAULT) { - allDefaultWayHandling = false; - } - } - if (allDefaultWayHandling) { - this.features = upstream.features; - return; - } - this.features = upstream.features.map( features => { if(features === undefined){ return; } + + const layerDict = {}; + let allDefaultWayHandling = true; + for (const layer of layers.data) { + layerDict[layer.layerDef.id] = layer; + if (layer.layerDef.wayHandling !== LayerConfig.WAYHANDLING_DEFAULT) { + allDefaultWayHandling = false; + } + } + const newFeatures: { feature: any, freshness: Date }[] = []; for (const f of features) { const feat = f.feature; diff --git a/State.ts b/State.ts index 9e990564ee..655b66722a 100644 --- a/State.ts +++ b/State.ts @@ -28,7 +28,7 @@ export default class State { // The singleton of the global state public static state: State; - + public readonly layoutToUse = new UIEventSource(undefined); /** @@ -77,7 +77,7 @@ export default class State { */ public readonly selectedElement = new UIEventSource(undefined) - + public readonly featureSwitchUserbadge: UIEventSource; public readonly featureSwitchSearch: UIEventSource; public readonly featureSwitchLayers: UIEventSource; @@ -119,11 +119,12 @@ export default class State { constructor(layoutToUse: LayoutConfig) { const self = this; - + this.layoutToUse.setData(layoutToUse); // -- Location control initialization - { const zoom = State.asFloat( + { + const zoom = State.asFloat( QueryParameters.GetQueryParameter("z", "" + (layoutToUse?.startZoom ?? 1), "The initial/current zoom level") .syncWith(LocalStorageSource.Get("zoom"))); const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude") @@ -151,6 +152,7 @@ export default class State { }); } + // Helper function to initialize feature switches function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource { const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation); @@ -166,8 +168,8 @@ export default class State { } // Feature switch initialization - not as a function as the UIEventSources are readonly - { - + { + this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true, "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode."); this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true, @@ -190,10 +192,10 @@ export default class State { this.featureSwitchIsTesting = QueryParameters.GetQueryParameter("test", "false", "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org") - .map(str => str === "true",[], b => ""+b); + .map(str => str === "true", [], b => "" + b); } - - + + this.osmConnection = new OsmConnection( this.featureSwitchIsTesting.data, QueryParameters.GetQueryParameter("oauth_token", undefined, @@ -205,20 +207,21 @@ export default class State { this.allElements = new ElementStorage(); this.changes = new Changes(); - + this.mangroveIdentity = new MangroveIdentity( this.osmConnection.GetLongPreference("identity", "mangrove") ); - this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes; // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme - this.favouriteLayers = this.osmConnection.GetLongPreference("favouriteLayers").map( - str => Utils.Dedup(str?.split(";")) ?? [], - [], layers => Utils.Dedup(layers)?.join(";") - ); + this.favouriteLayers = LocalStorageSource.Get("favouriteLayers") + .syncWith(this.osmConnection.GetLongPreference("favouriteLayers")) + .map( + str => Utils.Dedup(str?.split(";")) ?? [], + [], layers => Utils.Dedup(layers)?.join(";") + ); Locale.language.syncWith(this.osmConnection.GetPreference("language")); @@ -236,7 +239,7 @@ export default class State { }).ping() new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); - + } @@ -251,6 +254,6 @@ export default class State { return ("" + fl).substr(0, 8); }) } - + } diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index f78549cd01..86b1153b36 100644 --- a/UI/Base/ScrollableFullScreen.ts +++ b/UI/Base/ScrollableFullScreen.ts @@ -21,7 +21,6 @@ export default class ScrollableFullScreen extends UIElement { Svg.close_svg().SetClass("hidden md:block") ]) .onClick(() => { - console.log("Closing...") ScrollableFullScreen.RestoreLeaflet(); if (onClose !== undefined) { onClose(); diff --git a/UI/BigComponents/LayerControlPanel.ts b/UI/BigComponents/LayerControlPanel.ts index a55c2912e9..dc5dc0a62b 100644 --- a/UI/BigComponents/LayerControlPanel.ts +++ b/UI/BigComponents/LayerControlPanel.ts @@ -22,7 +22,7 @@ export default class LayerControlPanel extends UIElement { } if (State.state.filteredLayers.data.length > 1) { - const layerSelection = new LayerSelection(); + const layerSelection = new LayerSelection(State.state.filteredLayers); layerSelection.onClick(() => { }); layerControlPanel = new Combine([layerSelection, "
", layerControlPanel]); diff --git a/UI/BigComponents/LayerSelection.ts b/UI/BigComponents/LayerSelection.ts index d812b6810e..d56455965a 100644 --- a/UI/BigComponents/LayerSelection.ts +++ b/UI/BigComponents/LayerSelection.ts @@ -6,27 +6,44 @@ import CheckBox from "../Input/CheckBox"; import Combine from "../Base/Combine"; import {FixedUiElement} from "../Base/FixedUiElement"; import Translations from "../i18n/Translations"; +import LayerConfig from "../../Customizations/JSON/LayerConfig"; /** * Shows the panel with all layers and a toggle for each of them */ export default class LayerSelection extends UIElement { - private readonly _checkboxes: UIElement[]; + private _checkboxes: UIElement[]; + private activeLayers: UIEventSource<{ + readonly isDisplayed: UIEventSource, + readonly layerDef: LayerConfig; + }[]>; + + constructor(activeLayers: UIEventSource<{ + readonly isDisplayed: UIEventSource, + readonly layerDef: LayerConfig; + }[]>) { + super(activeLayers); + if(activeLayers === undefined){ + throw "ActiveLayers should be defined..." + } + this.activeLayers = activeLayers; + + } + + InnerRender(): string { - constructor() { - super(undefined); this._checkboxes = []; - for (const layer of State.state.filteredLayers.data) { + for (const layer of this.activeLayers.data) { const leafletStyle = layer.layerDef.GenerateLeafletStyle( - new UIEventSource({id: "node/-1"}), + new UIEventSource({id: "node/-1"}), false) const leafletHtml = leafletStyle.icon.html; const icon = new FixedUiElement(leafletHtml.Render()) .SetClass("single-layer-selection-toggle") - let iconUnselected: UIElement = new FixedUiElement(leafletHtml.Render()) + let iconUnselected: UIElement = new FixedUiElement(leafletHtml.Render()) .SetClass("single-layer-selection-toggle") .SetStyle("opacity:0.2;"); @@ -54,9 +71,8 @@ export default class LayerSelection extends UIElement { .SetStyle("margin:0.3em;") ); } - } - InnerRender(): string { + return new Combine(this._checkboxes) .SetStyle("display:flex;flex-direction:column;") .Render(); diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index 1b2c000f6a..7c3ed26ff2 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -8,7 +8,7 @@ export default class CenterMessageBox extends UIElement { super(State.state.centerMessage); this.ListenTo(State.state.locationControl); - this.ListenTo(State.state.layerUpdater.retries); + this.ListenTo(State.state.layerUpdater.timeout); this.ListenTo(State.state.layerUpdater.runningQuery); this.ListenTo(State.state.layerUpdater.sufficientlyZoomed); } @@ -18,9 +18,9 @@ export default class CenterMessageBox extends UIElement { return {innerHtml: State.state.centerMessage.data, done: false}; } const lu = State.state.layerUpdater; - if (lu.retries.data > 0) { + if (lu.timeout.data > 0) { return { - innerHtml: Translations.t.centerMessage.retrying.Subs({count: "" + lu.retries.data}).Render(), + innerHtml: Translations.t.centerMessage.retrying.Subs({count: "" + lu.timeout.data}).Render(), done: false }; } diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index b538858acd..6a516f1e0f 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -102,6 +102,10 @@ export default class TagRenderingQuestion extends UIElement { return ff; } + if(ff){ + mappings.push(ff); + } + if (this._configuration.multiAnswer) { return this.GenerateMultiAnswer(mappings, ff) } else { diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 812b05399c..481d787833 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -15,7 +15,7 @@ import {GeoOperations} from "../Logic/GeoOperations"; export default class ShowDataLayer { - private readonly _layerDict; + private _layerDict; private readonly _leafletMap: UIEventSource; constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, @@ -24,12 +24,11 @@ export default class ShowDataLayer { this._leafletMap = leafletMap; const self = this; const mp = leafletMap.data; - - this._layerDict = {}; + self._layerDict = {}; layoutToUse.addCallbackAndRun(layoutToUse => { for (const layer of layoutToUse.layers) { - this._layerDict[layer.id] = layer; + self._layerDict[layer.id] = layer; } }); @@ -81,7 +80,7 @@ export default class ShowDataLayer { const tagsSource = State.state.allElements.getEventSourceFor(feature); // Every object is tied to exactly one layer const layer = this._layerDict[feature._matching_layer_id]; - return layer.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); + return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); } private pointToLayer(feature, latLng): L.Layer { @@ -111,6 +110,10 @@ export default class ShowDataLayer { private postProcessFeature(feature, leafletLayer: L.Layer) { const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; + if(layer === undefined){ + console.warn("No layer found for object (probably a now disabled layer)", feature) + return; + } if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) { // No popup action defined -> Don't do anything return; @@ -159,10 +162,10 @@ export default class ShowDataLayer { const mp = this._leafletMap.data; if (!popup.isOpen() && mp !== undefined) { - var centerpoint = GeoOperations.centerpointCoordinates(feature); popup .setLatLng(GeoOperations.centerpointCoordinates(feature)) .openOn(mp); + uiElement.Activate(); } } } diff --git a/Utils.ts b/Utils.ts index d9d3940988..8cb53b1b07 100644 --- a/Utils.ts +++ b/Utils.ts @@ -1,6 +1,4 @@ import * as $ from "jquery" -import Constants from "./Models/Constants"; - export class Utils { diff --git a/package.json b/package.json index 440e3a8a5b..51ba5800fc 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "scripts": { "increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096", "start": "npm run increase-memory && parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", - "test": "ts-node test/*", + "test": "ts-node test/Tag.spec.ts && ts-node test/TagQuestion.spec.ts", "generate:editor-layer-index": "cd assets/ && wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json", "generate:images": "ts-node scripts/generateIncludedImages.ts", "generate:translations": "ts-node scripts/generateTranslations.ts", diff --git a/test/Tag.spec.ts b/test/Tag.spec.ts index 93ce901d48..edc2adc042 100644 --- a/test/Tag.spec.ts +++ b/test/Tag.spec.ts @@ -15,7 +15,7 @@ import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput"; import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; -new T([ +new T("Tags", [ ["Tag replacement works in translation", () => { const tr = new Translation({ "en": "Test {key} abc" diff --git a/test/TagQuestion.spec.ts b/test/TagQuestion.spec.ts new file mode 100644 index 0000000000..a8dd6d77bc --- /dev/null +++ b/test/TagQuestion.spec.ts @@ -0,0 +1,60 @@ +import T from "./TestHelper"; +import {Utils} from "../Utils"; + +Utils.runningFromConsole = true; +import TagRenderingQuestion from "../UI/Popup/TagRenderingQuestion"; +import {UIEventSource} from "../Logic/UIEventSource"; +import TagRenderingConfig from "../Customizations/JSON/TagRenderingConfig"; +import {equal} from "assert"; +import * as assert from "assert"; + + +new T("TagQuestionElement", + [ + ["Freeform has textfield", () => { + const tags = new UIEventSource({ + id: "way/123", + amenity: 'public_bookcases' + }); + const config = new TagRenderingConfig( + { + render: "The name is {name}", + question: "What is the name of this bookcase?", + freeform: { + key: "name", + type: "string" + } + }, undefined, "Testing tag" + ); + const questionElement = new TagRenderingQuestion(tags, config); + const html = questionElement.InnerRender(); + T.assertContains("What is the name of this bookcase?", html); + T.assertContains(" { + const tags = new UIEventSource({ + id: "way/123", + amenity: 'public_bookcases' + }); + const config = new TagRenderingConfig( + { + render: "The name is {name}", + question: "What is the name of this bookcase?", + freeform: { + key: "name", + type: "string" + }, + mappings: [ + {"if": "noname=yes", + "then": "This bookcase has no name"} + ] + }, undefined, "Testing tag" + ); + const questionElement = new TagRenderingQuestion(tags, config); + const html = questionElement.InnerRender(); + T.assertContains("What is the name of this bookcase?", html); + T.assertContains("This bookcase has no name", html); + T.assertContains(" void ][]) { + constructor(testsuite: string, tests: [string, () => void ][]) { let failures : string []= []; for (const [name, test] of tests) { try { @@ -11,11 +12,17 @@ export default class T { } } if (failures.length == 0) { - console.log("All tests done!") + console.log(`All tests of ${testsuite} done!`) } else { - console.warn(failures.length, "tests failed :(") + console.warn(failures.length, `tests of ${testsuite} failed :(`) console.log("Failed tests: ", failures.join(",")) } } + static assertContains(needle: string, actual: string){ + if(actual.indexOf(needle) < 0){ + throw `The substring ${needle} was not found` + } + } + }