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() {
|
||||
// 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<boolean>(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 }[]) => {
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<any[]>(undefined);
|
||||
public readonly features: UIEventSource<{ feature: any, freshness: Date }[]> = new UIEventSource<any[]>(undefined);
|
||||
|
||||
|
||||
public readonly sufficientlyZoomed: UIEventSource<boolean>;
|
||||
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
|
||||
*
|
||||
|
@ -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));
|
||||
|
@ -59,7 +59,7 @@ export default class UpdateFromOverpass implements FeatureSource{
|
|||
layoutToUse.addCallback(() => {
|
||||
self.update()
|
||||
});
|
||||
location.addCallbackAndRun(() => {
|
||||
location.addCallback(() => {
|
||||
self.update()
|
||||
});
|
||||
}
|
||||
|
@ -74,13 +74,13 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 }[]>;
|
||||
|
||||
|
||||
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) {
|
||||
|
|
|
@ -16,7 +16,7 @@ export default class FeaturePipeline implements FeatureSource {
|
|||
|
||||
public features: UIEventSource<{ feature: any; freshness: Date }[]>;
|
||||
|
||||
constructor(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[],
|
||||
constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>,
|
||||
updater: FeatureSource,
|
||||
layout: UIEventSource<LayoutConfig>,
|
||||
newPoints: FeatureSource,
|
||||
|
|
|
@ -6,23 +6,24 @@ 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<boolean>,
|
||||
layerDef: LayerConfig
|
||||
}[],
|
||||
}[]>,
|
||||
location: UIEventSource<Loc>,
|
||||
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;
|
||||
|
||||
|
||||
|
@ -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<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();
|
||||
|
||||
|
|
|
@ -9,18 +9,21 @@ export default class LocalStorageSource implements FeatureSource {
|
|||
constructor(layout: UIEventSource<LayoutConfig>) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
17
State.ts
17
State.ts
|
@ -123,7 +123,8 @@ export default class State {
|
|||
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<boolean> {
|
||||
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation);
|
||||
|
@ -190,7 +192,7 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -211,14 +213,15 @@ export default class State {
|
|||
);
|
||||
|
||||
|
||||
|
||||
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"));
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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, "<br/>", layerControlPanel]);
|
||||
|
|
|
@ -6,19 +6,36 @@ 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<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 = [];
|
||||
|
||||
for (const layer of State.state.filteredLayers.data) {
|
||||
for (const layer of this.activeLayers.data) {
|
||||
const leafletStyle = layer.layerDef.GenerateLeafletStyle(
|
||||
new UIEventSource<any>({id: "node/-1"}),
|
||||
false)
|
||||
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -15,7 +15,7 @@ import {GeoOperations} from "../Logic/GeoOperations";
|
|||
|
||||
export default class ShowDataLayer {
|
||||
|
||||
private readonly _layerDict;
|
||||
private _layerDict;
|
||||
private readonly _leafletMap: UIEventSource<L.Map>;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
2
Utils.ts
2
Utils.ts
|
@ -1,6 +1,4 @@
|
|||
import * as $ from "jquery"
|
||||
import Constants from "./Models/Constants";
|
||||
|
||||
|
||||
export class Utils {
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
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 {
|
||||
|
||||
constructor(tests: [string, () => 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`
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue