Stabilize personal theme, textfield now correctly appears if it is an option in the freeform too
This commit is contained in:
parent
79fc3f54e5
commit
416a76ae4f
22 changed files with 278 additions and 149 deletions
|
@ -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");
|
||||||
|
|
||||||
|
|
||||||
|
updateFavs();
|
||||||
|
InitUiElements.setupAllLayerElements();
|
||||||
|
|
||||||
|
if (layoutToUse.id === personal.id) {
|
||||||
|
State.state.favouriteLayers.addCallback(updateFavs);
|
||||||
|
State.state.installedThemes.addCallback(updateFavs);
|
||||||
|
}else{
|
||||||
State.state.locationControl.ping();
|
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 }[]) => {
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
@ -59,7 +59,7 @@ export default class UpdateFromOverpass implements FeatureSource{
|
||||||
layoutToUse.addCallback(() => {
|
layoutToUse.addCallback(() => {
|
||||||
self.update()
|
self.update()
|
||||||
});
|
});
|
||||||
location.addCallbackAndRun(() => {
|
location.addCallback(() => {
|
||||||
self.update()
|
self.update()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -74,13 +74,13 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
function countDown() {
|
||||||
window?.setTimeout(
|
window?.setTimeout(
|
||||||
function () {
|
function () {
|
||||||
self.update()
|
console.log("Countdown: ", self.timeout.data)
|
||||||
}, self.retries.data * 5000
|
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;
|
||||||
|
@ -173,6 +189,4 @@ export default class UpdateFromOverpass implements FeatureSource{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -6,23 +6,24 @@ 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;
|
||||||
|
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
|
||||||
const layerDict = {};
|
const layerDict = {};
|
||||||
for (const layer of layers) {
|
for (const layer of layers.data) {
|
||||||
layerDict[layer.layerDef.id] = layer;
|
layerDict[layer.layerDef.id] = layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Updating the filtering layer, input ", upstream.features.data.length, "features")
|
||||||
function update() {
|
|
||||||
console.log("Updating the filtering layer")
|
|
||||||
const features: { feature: any, freshness: Date }[] = upstream.features.data;
|
const features: { feature: any, freshness: Date }[] = upstream.features.data;
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,21 +66,34 @@ 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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@ 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
|
||||||
|
layout.addCallbackAndRun(_ => {
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fromStorage = localStorage.getItem(key);
|
const fromStorage = localStorage.getItem(key);
|
||||||
if (fromStorage == null) {
|
if (fromStorage == null) {
|
||||||
|
@ -16,11 +19,11 @@ export default class LocalStorageSource implements FeatureSource {
|
||||||
}
|
}
|
||||||
const loaded = JSON.parse(fromStorage);
|
const loaded = JSON.parse(fromStorage);
|
||||||
this.features.setData(loaded);
|
this.features.setData(loaded);
|
||||||
console.log("Loaded ",loaded.length," features from localstorage as cache")
|
console.log("Loaded ", loaded.length, " features from localstorage as cache")
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Could not load features from localStorage:", e)
|
console.log("Could not load features from localStorage:", e)
|
||||||
localStorage.removeItem(key)
|
localStorage.removeItem(key)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
11
State.ts
11
State.ts
|
@ -123,7 +123,8 @@ export default class State {
|
||||||
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);
|
||||||
|
@ -190,7 +192,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,11 +213,12 @@ export default class State {
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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")
|
||||||
|
.syncWith(this.osmConnection.GetLongPreference("favouriteLayers"))
|
||||||
|
.map(
|
||||||
str => Utils.Dedup(str?.split(";")) ?? [],
|
str => Utils.Dedup(str?.split(";")) ?? [],
|
||||||
[], layers => Utils.Dedup(layers)?.join(";")
|
[], layers => Utils.Dedup(layers)?.join(";")
|
||||||
);
|
);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -6,19 +6,36 @@ 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)
|
||||||
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
Utils.ts
2
Utils.ts
|
@ -1,6 +1,4 @@
|
||||||
import * as $ from "jquery"
|
import * as $ from "jquery"
|
||||||
import Constants from "./Models/Constants";
|
|
||||||
|
|
||||||
|
|
||||||
export class Utils {
|
export class Utils {
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
60
test/TagQuestion.spec.ts
Normal 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);
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
);
|
|
@ -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`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue