Stabilize personal theme, textfield now correctly appears if it is an option in the freeform too

This commit is contained in:
pietervdvn 2021-02-20 01:45:51 +01:00
parent 79fc3f54e5
commit 416a76ae4f
22 changed files with 278 additions and 149 deletions

View file

@ -74,15 +74,8 @@ export class InitUiElements {
} }
InitUiElements.InitBaseMap();
InitUiElements.setupAllLayerElements();
if (layoutToUse.customCss !== undefined) {
Utils.LoadCustomCss(layoutToUse.customCss);
}
function updateFavs() { function updateFavs() {
// This is purely for the personal theme to load the layers there
const favs = State.state.favouriteLayers.data ?? []; const favs = State.state.favouriteLayers.data ?? [];
layoutToUse.layers.splice(0, layoutToUse.layers.length); 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.layoutToUse.ping();
State.state.layerUpdater?.ForceRefresh();
} }
if (layoutToUse.id === personal.id) { if (layoutToUse.customCss !== undefined) {
State.state.favouriteLayers.addCallback(updateFavs); Utils.LoadCustomCss(layoutToUse.customCss);
State.state.installedThemes.addCallback(updateFavs);
} }
InitUiElements.InitBaseMap();
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
new UserBadge().AttachTo('userbadge'); new UserBadge().AttachTo('userbadge');
@ -162,7 +152,17 @@ export class InitUiElements {
, State.state.featureSwitchGeolocation) , State.state.featureSwitchGeolocation)
.AttachTo("geolocate-button"); .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 // Reset the loading message once things are loaded
new CenterMessageBox().AttachTo("centermessage"); new CenterMessageBox().AttachTo("centermessage");
@ -209,7 +209,6 @@ export class InitUiElements {
const isOpened = new UIEventSource<boolean>(true); const isOpened = new UIEventSource<boolean>(true);
const fullOptions = new FullWelcomePaneWithTabs(() => { const fullOptions = new FullWelcomePaneWithTabs(() => {
console.log("Closing the welcome message...")
isOpened.setData(false); isOpened.setData(false);
}); });
@ -325,7 +324,7 @@ export class InitUiElements {
const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap); const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap);
State.state.layerUpdater = updater; 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 }[]) => { source.features.addCallbackAndRun((featuresFreshness: { feature: any, freshness: Date }[]) => {

View file

@ -13,7 +13,7 @@ export default class InstalledThemes {
return installedThemes; return installedThemes;
} }
const invalidThemes = [] const invalidThemes = []
for (var allPreferencesKey in allPreferences) { for (const allPreferencesKey in allPreferences) {
const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/); const themename = allPreferencesKey.match(/^mapcomplete-installed-theme-(.*)-combined-length$/);
if (themename && themename[1] !== "") { if (themename && themename[1] !== "") {
const customLayout = osmConnection.GetLongPreference("installed-theme-" + themename[1]); const customLayout = osmConnection.GetLongPreference("installed-theme-" + themename[1]);
@ -37,7 +37,7 @@ export default class InstalledThemes {
} }
InstalledThemes.DeleteInvalid(osmConnection, invalidThemes); InstalledThemes.DeleteInvalid(osmConnection, invalidThemes);
return installedThemes; return installedThemes;
}); });

View file

@ -44,10 +44,15 @@ export default class SelectedFeatureHandler {
// Feature already selected // Feature already selected
return; return;
} }
const hash = this._hash.data;
if(hash === undefined || hash === "" || hash === "#"){
return;
}
console.log("Selecting a feature from the hash...") console.log("Selecting a feature from the hash...")
for (const feature of features) { for (const feature of features) {
const id = feature.feature?.properties?.id; const id = feature.feature?.properties?.id;
if(id === this._hash.data){ if(id === hash){
this._selectedFeature.setData(feature.feature); this._selectedFeature.setData(feature.feature);
break; break;
} }

View file

@ -7,18 +7,18 @@ import Bounds from "../../Models/Bounds";
import FeatureSource from "../FeatureSource/FeatureSource"; import FeatureSource from "../FeatureSource/FeatureSource";
export default class UpdateFromOverpass implements FeatureSource{ export default class UpdateFromOverpass implements FeatureSource {
/** /**
* The last loaded features of the geojson * The last loaded features of the geojson
*/ */
public readonly features: UIEventSource<{feature:any, freshness: Date}[]> = new UIEventSource<any[]>(undefined); public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<any[]>(undefined);
public readonly sufficientlyZoomed: UIEventSource<boolean>; public readonly sufficientlyZoomed: UIEventSource<boolean>;
public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false);
public readonly retries: UIEventSource<number> = new UIEventSource<number>(0); public readonly timeout: UIEventSource<number> = new UIEventSource<number>(0);
private readonly retries: UIEventSource<number> = new UIEventSource<number>(0);
/** /**
* The previous bounds for which the query has been run at the given zoom level * 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; const self = this;
this.sufficientlyZoomed = location.map(location => { this.sufficientlyZoomed = location.map(location => {
if(location?.zoom === undefined){ if (location?.zoom === undefined) {
return false; return false;
} }
let minzoom = Math.min(...layoutToUse.data.layers.map(layer => layer.minzoom ?? 18)); 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 update removes all data on all layers -> erase the map on lower levels too
this._previousBounds.set(i, []); this._previousBounds.set(i, []);
} }
layoutToUse.addCallback(() => { layoutToUse.addCallback(() => {
self.update() self.update()
}); });
location.addCallbackAndRun(() => { location.addCallback(() => {
self.update() self.update()
}); });
} }
@ -74,17 +74,17 @@ export default class UpdateFromOverpass implements FeatureSource{
private GetFilter() { private GetFilter() {
const filters: TagsFilter[] = []; const filters: TagsFilter[] = [];
for (const layer of this._layoutToUse.data.layers) { for (const layer of this._layoutToUse.data.layers) {
if(typeof(layer) === "string"){ if (typeof (layer) === "string") {
continue; continue;
} }
if (this._location.data.zoom < layer.minzoom) { if (this._location.data.zoom < layer.minzoom) {
continue; continue;
} }
if(layer.doNotDownload){ if (layer.doNotDownload) {
continue; continue;
} }
// Check if data for this layer has already been loaded // Check if data for this layer has already been loaded
let previouslyLoaded = false; let previouslyLoaded = false;
for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) { for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) {
@ -94,7 +94,7 @@ export default class UpdateFromOverpass implements FeatureSource{
} }
for (const previousLoadedBound of previousLoadedBounds) { for (const previousLoadedBound of previousLoadedBounds) {
previouslyLoaded = previouslyLoaded || this.IsInBounds(previousLoadedBound); previouslyLoaded = previouslyLoaded || this.IsInBounds(previousLoadedBound);
if(previouslyLoaded){ if (previouslyLoaded) {
break; break;
} }
} }
@ -109,6 +109,7 @@ export default class UpdateFromOverpass implements FeatureSource{
} }
return new Or(filters); return new Or(filters);
} }
private update(): void { private update(): void {
const filter = this.GetFilter(); const filter = this.GetFilter();
if (filter === undefined) { if (filter === undefined) {
@ -145,21 +146,36 @@ export default class UpdateFromOverpass implements FeatureSource{
function (reason) { function (reason) {
self.retries.data++; self.retries.data++;
self.ForceRefresh(); 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.retries.ping();
self.runningQuery.setData(false) self.runningQuery.setData(false);
window?.setTimeout(
function () { function countDown() {
self.update() window?.setTimeout(
}, self.retries.data * 5000 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 { private IsInBounds(bounds: Bounds): boolean {
if (this._previousBounds === undefined) { if (this._previousBounds === undefined) {
return false; return false;
@ -171,8 +187,6 @@ export default class UpdateFromOverpass implements FeatureSource{
b.getEast() <= bounds.east && b.getEast() <= bounds.east &&
b.getWest() >= bounds.west; b.getWest() >= bounds.west;
} }
} }

View file

@ -12,7 +12,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; 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 => { this.features = upstream.features.map(features => {
const newFeatures: { feature: any, freshness: Date }[] = []; const newFeatures: { feature: any, freshness: Date }[] = [];
if(features === undefined){ if(features === undefined){
@ -29,7 +29,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
let foundALayer = false; let foundALayer = false;
for (const layer of layers) { for (const layer of layers.data) {
if (layer.layerDef.overpassTags.matchesProperties(f.feature.properties)) { if (layer.layerDef.overpassTags.matchesProperties(f.feature.properties)) {
foundALayer = true; foundALayer = true;
if (layer.layerDef.passAllFeatures) { if (layer.layerDef.passAllFeatures) {

View file

@ -16,7 +16,7 @@ export default class FeaturePipeline implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]>; public features: UIEventSource<{ feature: any; freshness: Date }[]>;
constructor(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>,
updater: FeatureSource, updater: FeatureSource,
layout: UIEventSource<LayoutConfig>, layout: UIEventSource<LayoutConfig>,
newPoints: FeatureSource, newPoints: FeatureSource,

View file

@ -6,26 +6,27 @@ import Loc from "../../Models/Loc";
export default class FilteringFeatureSource implements FeatureSource { export default class FilteringFeatureSource implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]); public features: UIEventSource<{ feature: any; freshness: Date }[]> = new UIEventSource<{ feature: any; freshness: Date }[]>([]);
constructor(layers: { constructor(layers: UIEventSource<{
isDisplayed: UIEventSource<boolean>, isDisplayed: UIEventSource<boolean>,
layerDef: LayerConfig layerDef: LayerConfig
}[], }[]>,
location: UIEventSource<Loc>, location: UIEventSource<Loc>,
upstream: FeatureSource) { upstream: FeatureSource) {
const self = this; const self = this;
const layerDict = {};
for (const layer of layers) {
layerDict[layer.layerDef.id] = layer;
}
function update() { 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 features: { feature: any, freshness: Date }[] = upstream.features.data;
const newFeatures = features.filter(f => { const newFeatures = features.filter(f => {
const layerId = f.feature._matching_layer_id; const layerId = f.feature._matching_layer_id;
if (layerId !== undefined) { 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? // 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)) { if (!FilteringFeatureSource.showLayer(toCheck, location)) {
continue; continue;
} }
@ -53,6 +54,8 @@ export default class FilteringFeatureSource implements FeatureSource {
return false; return false;
}); });
console.log("Updating the filtering layer, output ", newFeatures.length, "features")
self.features.setData(newFeatures); self.features.setData(newFeatures);
} }
@ -63,20 +66,33 @@ export default class FilteringFeatureSource implements FeatureSource {
location.map(l => { location.map(l => {
// We want something that is stable for the shown layers // We want something that is stable for the shown layers
const displayedLayerIndexes = []; const displayedLayerIndexes = [];
for (let i = 0; i < layers.length; i++) { for (let i = 0; i < layers.data.length; i++) {
if (l.zoom < layers[i].layerDef.minzoom) { const layer = layers.data[i];
if (l.zoom < layer.layerDef.minzoom) {
continue; continue;
} }
if (!layers[i].isDisplayed.data) { if (!layer.isDisplayed.data) {
continue; continue;
} }
displayedLayerIndexes.push(i); displayedLayerIndexes.push(i);
} }
return displayedLayerIndexes.join(",") return displayedLayerIndexes.join(",")
}, layers.map(l => l.isDisplayed)) }).addCallback(() => {
.addCallback(() => { update();
update(); });
});
layers.addCallback(update);
const registered = new Set<UIEventSource<boolean>>();
layers.addCallback(layers => {
for (const layer of layers) {
if(registered.has(layer.isDisplayed)){
continue;
}
registered.add(layer.isDisplayed);
layer.isDisplayed.addCallback(update);
}
})
update(); update();

View file

@ -9,18 +9,21 @@ export default class LocalStorageSource implements FeatureSource {
constructor(layout: UIEventSource<LayoutConfig>) { constructor(layout: UIEventSource<LayoutConfig>) {
this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([])
const key = LocalStorageSaver.storageKey + layout.data.id const key = LocalStorageSaver.storageKey + layout.data.id
try { layout.addCallbackAndRun(_ => {
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)
}
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)
}
})
} }
} }

View file

@ -12,16 +12,12 @@ export default class NoOverlapSource {
features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<{ feature: any, freshness: Date }[]>([]); features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<{ feature: any, freshness: Date }[]>([]);
constructor(layers: { constructor(layers: UIEventSource<{
layerDef: LayerConfig layerDef: LayerConfig
}[], }[]>,
upstream: FeatureSource) { upstream: FeatureSource) {
const layerDict = {};
let noOverlapRemoval = true; let noOverlapRemoval = true;
const layerIds = [] for (const layer of layers.data) {
for (const layer of layers) {
layerDict[layer.layerDef.id] = layer;
layerIds.push(layer.layerDef.id);
if ((layer.layerDef.hideUnderlayingFeaturesMinPercentage ?? 0) !== 0) { if ((layer.layerDef.hideUnderlayingFeaturesMinPercentage ?? 0) !== 0) {
noOverlapRemoval = false; noOverlapRemoval = false;
} }
@ -31,13 +27,22 @@ export default class NoOverlapSource {
return; return;
} }
this.features = upstream.features.map( this.features = upstream.features.map(
features => { features => {
if (features === undefined) { if (features === undefined) {
return; 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 // There is overlap removal active
// We partition all the features with their respective layerIDs // We partition all the features with their respective layerIDs
const partitions = {}; const partitions = {};
@ -67,7 +72,7 @@ export default class NoOverlapSource {
guardPartition.map(f => f.feature), guardPartition.map(f => f.feature),
percentage percentage
); );
if(!doesOverlap){ if (!doesOverlap) {
newPartition.push(mightBeDeleted); newPartition.push(mightBeDeleted);
} }
} }

View file

@ -6,29 +6,26 @@ import {GeoOperations} from "../GeoOperations";
export default class WayHandlingApplyingFeatureSource implements FeatureSource { export default class WayHandlingApplyingFeatureSource implements FeatureSource {
features: UIEventSource<{ feature: any; freshness: Date }[]>; features: UIEventSource<{ feature: any; freshness: Date }[]>;
constructor(layers: { constructor(layers: UIEventSource<{
layerDef: LayerConfig layerDef: LayerConfig
}[], }[]>,
upstream: FeatureSource) { 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( this.features = upstream.features.map(
features => { features => {
if(features === undefined){ if(features === undefined){
return; 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 }[] = []; const newFeatures: { feature: any, freshness: Date }[] = [];
for (const f of features) { for (const f of features) {
const feat = f.feature; const feat = f.feature;

View file

@ -28,7 +28,7 @@ export default class State {
// The singleton of the global state // The singleton of the global state
public static state: State; public static state: State;
public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined); public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined);
/** /**
@ -77,7 +77,7 @@ export default class State {
*/ */
public readonly selectedElement = new UIEventSource<any>(undefined) public readonly selectedElement = new UIEventSource<any>(undefined)
public readonly featureSwitchUserbadge: UIEventSource<boolean>; public readonly featureSwitchUserbadge: UIEventSource<boolean>;
public readonly featureSwitchSearch: UIEventSource<boolean>; public readonly featureSwitchSearch: UIEventSource<boolean>;
public readonly featureSwitchLayers: UIEventSource<boolean>; public readonly featureSwitchLayers: UIEventSource<boolean>;
@ -119,11 +119,12 @@ export default class State {
constructor(layoutToUse: LayoutConfig) { constructor(layoutToUse: LayoutConfig) {
const self = this; const self = this;
this.layoutToUse.setData(layoutToUse); this.layoutToUse.setData(layoutToUse);
// -- Location control initialization // -- Location control initialization
{ const zoom = State.asFloat( {
const zoom = State.asFloat(
QueryParameters.GetQueryParameter("z", "" + (layoutToUse?.startZoom ?? 1), "The initial/current zoom level") QueryParameters.GetQueryParameter("z", "" + (layoutToUse?.startZoom ?? 1), "The initial/current zoom level")
.syncWith(LocalStorageSource.Get("zoom"))); .syncWith(LocalStorageSource.Get("zoom")));
const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude") 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 // Helper function to initialize feature switches
function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource<boolean> { function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource<boolean> {
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation); 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 // Feature switch initialization - not as a function as the UIEventSources are readonly
{ {
this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true, 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."); "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, this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true,
@ -190,10 +192,10 @@ export default class State {
this.featureSwitchIsTesting = QueryParameters.GetQueryParameter("test", "false", 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") "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.osmConnection = new OsmConnection(
this.featureSwitchIsTesting.data, this.featureSwitchIsTesting.data,
QueryParameters.GetQueryParameter("oauth_token", undefined, QueryParameters.GetQueryParameter("oauth_token", undefined,
@ -205,20 +207,21 @@ export default class State {
this.allElements = new ElementStorage(); this.allElements = new ElementStorage();
this.changes = new Changes(); this.changes = new Changes();
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove")
); );
this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes; this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes;
// Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme // Important: the favourite layers are initialized _after_ the installed themes, as these might contain an installedTheme
this.favouriteLayers = this.osmConnection.GetLongPreference("favouriteLayers").map( this.favouriteLayers = LocalStorageSource.Get("favouriteLayers")
str => Utils.Dedup(str?.split(";")) ?? [], .syncWith(this.osmConnection.GetLongPreference("favouriteLayers"))
[], layers => Utils.Dedup(layers)?.join(";") .map(
); str => Utils.Dedup(str?.split(";")) ?? [],
[], layers => Utils.Dedup(layers)?.join(";")
);
Locale.language.syncWith(this.osmConnection.GetPreference("language")); Locale.language.syncWith(this.osmConnection.GetPreference("language"));
@ -236,7 +239,7 @@ export default class State {
}).ping() }).ping()
new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements);
} }
@ -251,6 +254,6 @@ export default class State {
return ("" + fl).substr(0, 8); return ("" + fl).substr(0, 8);
}) })
} }
} }

View file

@ -21,7 +21,6 @@ export default class ScrollableFullScreen extends UIElement {
Svg.close_svg().SetClass("hidden md:block") Svg.close_svg().SetClass("hidden md:block")
]) ])
.onClick(() => { .onClick(() => {
console.log("Closing...")
ScrollableFullScreen.RestoreLeaflet(); ScrollableFullScreen.RestoreLeaflet();
if (onClose !== undefined) { if (onClose !== undefined) {
onClose(); onClose();

View file

@ -22,7 +22,7 @@ export default class LayerControlPanel extends UIElement {
} }
if (State.state.filteredLayers.data.length > 1) { if (State.state.filteredLayers.data.length > 1) {
const layerSelection = new LayerSelection(); const layerSelection = new LayerSelection(State.state.filteredLayers);
layerSelection.onClick(() => { layerSelection.onClick(() => {
}); });
layerControlPanel = new Combine([layerSelection, "<br/>", layerControlPanel]); layerControlPanel = new Combine([layerSelection, "<br/>", layerControlPanel]);

View file

@ -6,27 +6,44 @@ import CheckBox from "../Input/CheckBox";
import Combine from "../Base/Combine"; import Combine from "../Base/Combine";
import {FixedUiElement} from "../Base/FixedUiElement"; import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations"; import Translations from "../i18n/Translations";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
/** /**
* Shows the panel with all layers and a toggle for each of them * Shows the panel with all layers and a toggle for each of them
*/ */
export default class LayerSelection extends UIElement { export default class LayerSelection extends UIElement {
private readonly _checkboxes: UIElement[]; private _checkboxes: UIElement[];
private activeLayers: UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>,
readonly layerDef: LayerConfig;
}[]>;
constructor(activeLayers: UIEventSource<{
readonly isDisplayed: UIEventSource<boolean>,
readonly layerDef: LayerConfig;
}[]>) {
super(activeLayers);
if(activeLayers === undefined){
throw "ActiveLayers should be defined..."
}
this.activeLayers = activeLayers;
}
InnerRender(): string {
constructor() {
super(undefined);
this._checkboxes = []; this._checkboxes = [];
for (const layer of State.state.filteredLayers.data) { for (const layer of this.activeLayers.data) {
const leafletStyle = layer.layerDef.GenerateLeafletStyle( const leafletStyle = layer.layerDef.GenerateLeafletStyle(
new UIEventSource<any>({id: "node/-1"}), new UIEventSource<any>({id: "node/-1"}),
false) false)
const leafletHtml = leafletStyle.icon.html; const leafletHtml = leafletStyle.icon.html;
const icon = const icon =
new FixedUiElement(leafletHtml.Render()) new FixedUiElement(leafletHtml.Render())
.SetClass("single-layer-selection-toggle") .SetClass("single-layer-selection-toggle")
let iconUnselected: UIElement = new FixedUiElement(leafletHtml.Render()) let iconUnselected: UIElement = new FixedUiElement(leafletHtml.Render())
.SetClass("single-layer-selection-toggle") .SetClass("single-layer-selection-toggle")
.SetStyle("opacity:0.2;"); .SetStyle("opacity:0.2;");
@ -54,9 +71,8 @@ export default class LayerSelection extends UIElement {
.SetStyle("margin:0.3em;") .SetStyle("margin:0.3em;")
); );
} }
}
InnerRender(): string {
return new Combine(this._checkboxes) return new Combine(this._checkboxes)
.SetStyle("display:flex;flex-direction:column;") .SetStyle("display:flex;flex-direction:column;")
.Render(); .Render();

View file

@ -8,7 +8,7 @@ export default class CenterMessageBox extends UIElement {
super(State.state.centerMessage); super(State.state.centerMessage);
this.ListenTo(State.state.locationControl); 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.runningQuery);
this.ListenTo(State.state.layerUpdater.sufficientlyZoomed); this.ListenTo(State.state.layerUpdater.sufficientlyZoomed);
} }
@ -18,9 +18,9 @@ export default class CenterMessageBox extends UIElement {
return {innerHtml: State.state.centerMessage.data, done: false}; return {innerHtml: State.state.centerMessage.data, done: false};
} }
const lu = State.state.layerUpdater; const lu = State.state.layerUpdater;
if (lu.retries.data > 0) { if (lu.timeout.data > 0) {
return { 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 done: false
}; };
} }

View file

@ -102,6 +102,10 @@ export default class TagRenderingQuestion extends UIElement {
return ff; return ff;
} }
if(ff){
mappings.push(ff);
}
if (this._configuration.multiAnswer) { if (this._configuration.multiAnswer) {
return this.GenerateMultiAnswer(mappings, ff) return this.GenerateMultiAnswer(mappings, ff)
} else { } else {

View file

@ -15,7 +15,7 @@ import {GeoOperations} from "../Logic/GeoOperations";
export default class ShowDataLayer { export default class ShowDataLayer {
private readonly _layerDict; private _layerDict;
private readonly _leafletMap: UIEventSource<L.Map>; private readonly _leafletMap: UIEventSource<L.Map>;
constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>,
@ -24,12 +24,11 @@ export default class ShowDataLayer {
this._leafletMap = leafletMap; this._leafletMap = leafletMap;
const self = this; const self = this;
const mp = leafletMap.data; const mp = leafletMap.data;
self._layerDict = {};
this._layerDict = {};
layoutToUse.addCallbackAndRun(layoutToUse => { layoutToUse.addCallbackAndRun(layoutToUse => {
for (const layer of layoutToUse.layers) { 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); const tagsSource = State.state.allElements.getEventSourceFor(feature);
// Every object is tied to exactly one layer // Every object is tied to exactly one layer
const layer = this._layerDict[feature._matching_layer_id]; 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 { private pointToLayer(feature, latLng): L.Layer {
@ -111,6 +110,10 @@ export default class ShowDataLayer {
private postProcessFeature(feature, leafletLayer: L.Layer) { private postProcessFeature(feature, leafletLayer: L.Layer) {
const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; 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) { if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) {
// No popup action defined -> Don't do anything // No popup action defined -> Don't do anything
return; return;
@ -159,10 +162,10 @@ export default class ShowDataLayer {
const mp = this._leafletMap.data; const mp = this._leafletMap.data;
if (!popup.isOpen() && mp !== undefined) { if (!popup.isOpen() && mp !== undefined) {
var centerpoint = GeoOperations.centerpointCoordinates(feature);
popup popup
.setLatLng(GeoOperations.centerpointCoordinates(feature)) .setLatLng(GeoOperations.centerpointCoordinates(feature))
.openOn(mp); .openOn(mp);
uiElement.Activate();
} }
} }
} }

View file

@ -1,6 +1,4 @@
import * as $ from "jquery" import * as $ from "jquery"
import Constants from "./Models/Constants";
export class Utils { export class Utils {

View file

@ -9,7 +9,7 @@
"scripts": { "scripts": {
"increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096", "increase-memory": "export NODE_OPTIONS=--max_old_space_size=4096",
"start": "npm run increase-memory && parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "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: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:images": "ts-node scripts/generateIncludedImages.ts",
"generate:translations": "ts-node scripts/generateTranslations.ts", "generate:translations": "ts-node scripts/generateTranslations.ts",

View file

@ -15,7 +15,7 @@ import PublicHolidayInput from "../UI/OpeningHours/PublicHolidayInput";
import {SubstitutedTranslation} from "../UI/SubstitutedTranslation"; import {SubstitutedTranslation} from "../UI/SubstitutedTranslation";
new T([ new T("Tags", [
["Tag replacement works in translation", () => { ["Tag replacement works in translation", () => {
const tr = new Translation({ const tr = new Translation({
"en": "Test {key} abc" "en": "Test {key} abc"

60
test/TagQuestion.spec.ts Normal file
View file

@ -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("<input type='text'", html);
}],
["TagsQuestion with Freeform and mappings 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"
},
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("<input type='text'", html);
}]
]
);

View file

@ -1,6 +1,7 @@
export default class T { export default class T {
constructor(tests: [string, () => void ][]) { constructor(testsuite: string, tests: [string, () => void ][]) {
let failures : string []= []; let failures : string []= [];
for (const [name, test] of tests) { for (const [name, test] of tests) {
try { try {
@ -11,11 +12,17 @@ export default class T {
} }
} }
if (failures.length == 0) { if (failures.length == 0) {
console.log("All tests done!") console.log(`All tests of ${testsuite} done!`)
} else { } else {
console.warn(failures.length, "tests failed :(") console.warn(failures.length, `tests of ${testsuite} failed :(`)
console.log("Failed tests: ", failures.join(",")) console.log("Failed tests: ", failures.join(","))
} }
} }
static assertContains(needle: string, actual: string){
if(actual.indexOf(needle) < 0){
throw `The substring ${needle} was not found`
}
}
} }