Refactoring: introduction of global state to simplify getting common objects

This commit is contained in:
Pieter Vander Vennet 2020-07-31 01:45:54 +02:00
parent afaaaaadb1
commit 004eead4ee
34 changed files with 532 additions and 506 deletions

View file

@ -57,9 +57,9 @@ export class Artwork extends LayerDefinition {
const artistQuestion = new TagRenderingOptions({
question: t.artist.question,
freeform: {
key: "artist",
key: "artist_name",
template: "$$$",
renderTemplate: "{artist}"
renderTemplate: "{artist_name}"
}
});

View file

@ -171,7 +171,7 @@ export class Widths extends LayerDefinition {
return {
icon: null,
color: c,
weight: 10,
weight: 9,
dashArray: dashArray
}
}
@ -259,15 +259,14 @@ export class Widths extends LayerDefinition {
tags.targetWidth = r(props.targetWidth);
tags.short = "";
if (props.width < props.targetWidth) {
tags.short = "Er is dus <b class='alert'>" + r(props.targetWidth - props.width) + "m</b> te weinig"
tags.short = r(props.targetWidth - props.width)
}
},
freeform: {
key: "width:carriageway",
renderTemplate: "De totale nodige ruimte voor vlot en veilig verkeer is dus <b>{targetWidth}m</b><br>" +
"{short}",
template: "$$$",
}
mappings:[
{k: new Tag("short","*"), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus <b>{targetWidth}m</b><br>" +
"Er is dus <span class='alert'>{short}m</span> te weinig", substitute: true},
{k: new Tag("short",""), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus <span class='thanks'>{targetWidth}m</span>"}
]
}
).OnlyShowIf(this._notCarFree),

View file

@ -1,12 +1,6 @@
import {LayerDefinition} from "./LayerDefinition";
import {UIElement} from "../UI/UIElement";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import Translation from "../UI/i18n/Translation";
import Translations from "../UI/i18n/Translations";
import Locale from "../UI/i18n/Locale";
import {VariableUiElement} from "../UI/Base/VariableUIElement";
import {UIEventSource} from "../UI/UIEventSource";
import {OsmConnection, UserDetails} from "../Logic/Osm/OsmConnection";
/**
* A layout is a collection of settings of the global view (thus: welcome text, title, selection of layers).
@ -83,59 +77,5 @@ export class Layout {
this.welcomeBackMessage = Translations.W(welcomeBackMessage);
this.welcomeTail = Translations.W(welcomeTail);
}
}
export class WelcomeMessage extends UIElement {
private readonly layout: Layout;
private readonly userDetails: UIEventSource<UserDetails>;
private languagePicker: UIElement;
private osmConnection: OsmConnection;
private readonly description: UIElement;
private readonly plzLogIn: UIElement;
private readonly welcomeBack: UIElement;
private readonly tail: UIElement;
constructor(layout: Layout,
languagePicker: UIElement,
osmConnection: OsmConnection) {
super(osmConnection?.userDetails);
this.languagePicker = languagePicker;
this.ListenTo(Locale.language);
this.osmConnection = osmConnection;
this.layout = layout;
this.userDetails = osmConnection?.userDetails;
this.description = layout.welcomeMessage;
this.plzLogIn = layout.gettingStartedPlzLogin;
this.welcomeBack = layout.welcomeBackMessage;
this.tail = layout.welcomeTail;
}
InnerRender(): string {
let loginStatus = "";
if (this.userDetails !== undefined) {
loginStatus = (this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render();
loginStatus = loginStatus + "<br/>"
}
return "<span>" +
this.description.Render() +
"<br/>" +
loginStatus +
this.tail.Render() +
"<br/>" +
this.languagePicker.Render() +
"</span>";
}
protected InnerUpdate(htmlElement: HTMLElement) {
this.osmConnection?.registerActivateOsmAUthenticationClass()
}
}

View file

@ -11,6 +11,7 @@ export class SmoothnessLayer extends LayerDefinition {
this.name = "smoothness";
this.minzoom = 17;
this.overpassFilter = new Or([
new Tag("highway","unclassified"),
new Tag("highway", "residential"),
new Tag("highway", "cycleway"),
new Tag("highway", "footway"),

View file

@ -5,7 +5,7 @@ import {FixedUiElement} from "../UI/Base/FixedUiElement";
import {SaveButton} from "../UI/SaveButton";
import {Changes} from "../Logic/Osm/Changes";
import {VariableUiElement} from "../UI/Base/VariableUIElement";
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
import {Dependencies, TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
import {OnlyShowIfConstructor} from "./OnlyShowIf";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {TextField} from "../UI/Input/TextField";
@ -17,6 +17,7 @@ import Translations from "../UI/i18n/Translations";
import Locale from "../UI/i18n/Locale";
import * as EmailValidator from 'email-validator';
import {parsePhoneNumberFromString} from 'libphonenumber-js'
import {State} from "../State";
export class TagRenderingOptions implements TagDependantUIElementConstructor {
@ -144,8 +145,8 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
}
construct(dependencies: { tags: UIEventSource<any>, changes: Changes }): TagDependantUIElement {
return new TagRendering(dependencies.tags, dependencies.changes, this.options);
construct(dependencies: Dependencies): TagDependantUIElement {
return new TagRendering(dependencies.tags, this.options);
}
IsKnown(properties: any): boolean {
@ -161,7 +162,6 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
class TagRendering extends UIElement implements TagDependantUIElement {
private _userDetails: UIEventSource<UserDetails>;
private _priority: number;
@ -189,7 +189,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
constructor(tags: UIEventSource<any>, changes: Changes, options: {
constructor(tags: UIEventSource<any>, options: {
priority?: number
question?: string | UIElement,
@ -206,13 +206,12 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}) {
super(tags);
this.ListenTo(Locale.language);
const self = this;
this.ListenTo(this._questionSkipped);
this.ListenTo(this._editMode);
this.ListenTo(State.state.osmConnection.userDetails);
this._userDetails = changes.login.userDetails;
this.ListenTo(this._userDetails);
const self = this;
this._priority = options.priority ?? 0;
this._tagsPreprocessor = function (properties) {
@ -265,7 +264,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
const save = () => {
const selection = self._questionElement.GetValue().data;
if (selection) {
changes.addTag(tags.data.id, selection);
State.state.changes.addTag(tags.data.id, selection);
}
self._editMode.setData(false);
}
@ -521,7 +520,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
}
const html = answer.Render();
let editButton = "";
if (this._userDetails.data.loggedIn && this._question !== undefined) {
if (State.state.osmConnection.userDetails.data.loggedIn && this._question !== undefined) {
editButton = this._editButton.Render();
}

View file

@ -3,9 +3,13 @@ import {Changes} from "../Logic/Osm/Changes";
import {UIElement} from "../UI/UIElement";
export interface Dependencies {
tags: UIEventSource<any>
}
export interface TagDependantUIElementConstructor {
construct(dependencies: {tags: UIEventSource<any>, changes: Changes}): TagDependantUIElement;
construct(dependencies: Dependencies): TagDependantUIElement;
IsKnown(properties: any): boolean;
IsQuestioning(properties: any): boolean;
Priority(): number;

View file

@ -1,5 +1,6 @@
import {UIEventSource} from "./UI/UIEventSource";
import {Changes} from "./Logic/Osm/Changes";
import {State} from "./State";
export class Helpers {
@ -13,9 +14,11 @@ export class Helpers {
}
static SetupAutoSave(changes: Changes, millisTillChangesAreSaved: UIEventSource<number>, saveAfterXMillis: number) {
static SetupAutoSave() {
const changes = State.state.changes;
const millisTillChangesAreSaved = State.state.secondsTillChangesAreSaved;
const saveAfterXMillis = State.state.secondsTillChangesAreSaved.data * 1000;
changes.pendingChangesES.addCallback(function () {
var c = changes.pendingChangesES.data;
@ -53,7 +56,8 @@ export class Helpers {
* -> Asks the user not to close. The 'not to close' dialog should profide enough time to upload
* -> WHen uploading is done, the window is closed anyway
*/
static LastEffortSave(changes: Changes) {
static LastEffortSave() {
const changes = State.state.changes;
window.addEventListener("beforeunload", function (e) {
// Quickly save everyting!
if (changes.pendingChangesES.data == 0) {

View file

@ -1,4 +1,4 @@
import {Layout, WelcomeMessage} from "./Customizations/Layout";
import {Layout} from "./Customizations/Layout";
import Locale from "./UI/i18n/Locale";
import Translations from "./UI/i18n/Translations";
import {TabbedComponent} from "./UI/Base/TabbedComponent";
@ -17,6 +17,8 @@ import {Preset} from "./UI/SimpleAddUI";
import {Changes} from "./Logic/Osm/Changes";
import {OsmConnection} from "./Logic/Osm/OsmConnection";
import {Basemap} from "./Logic/Leaflet/Basemap";
import {State} from "./State";
import {WelcomeMessage} from "./UI/WelcomeMessage";
export class InitUiElements {
@ -36,17 +38,16 @@ export class InitUiElements {
}
private static CreateWelcomePane(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap) {
private static CreateWelcomePane() {
const welcome = new WelcomeMessage(layoutToUse,
Locale.CreateLanguagePicker(layoutToUse, Translations.t.general.pickLanguage),
osmConnection)
const welcome = new WelcomeMessage()
const layoutToUse = State.state.layoutToUse.data;
const fullOptions = new TabbedComponent([
{header: `<img src='${layoutToUse.icon}'>`, content: welcome},
{header: `<img src='${'./assets/osm-logo.svg'}'>`, content: Translations.t.general.openStreetMapIntro},
{header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen(layoutToUse, bm.Location)},
{header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen(layoutToUse.name, bm.Location)}
{header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen()},
{header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen()}
])
return fullOptions;
@ -54,10 +55,9 @@ export class InitUiElements {
}
static InitWelcomeMessage(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap,
fullScreenMessage: UIEventSource<UIElement>) {
static InitWelcomeMessage() {
const fullOptions = this.CreateWelcomePane(layoutToUse, osmConnection, bm);
const fullOptions = this.CreateWelcomePane();
const help = new FixedUiElement(`<div class='collapse-button-img'><img src='assets/help.svg' alt='help'></div>`);
const close = new FixedUiElement(`<div class='collapse-button-img'><img src='assets/close.svg' alt='close'></div>`);
@ -70,43 +70,39 @@ export class InitUiElements {
, true
).AttachTo("messagesbox");
let dontCloseYet = true;
bm.Location.addCallback(() => {
if(dontCloseYet){
dontCloseYet = false;
const openedTime = new Date().getTime();
State.state.locationControl.addCallback(() => {
if (new Date().getTime() - openedTime < 15 * 1000) {
// Don't autoclose the first 15 secs
return;
}
checkbox.isEnabled.setData(false);
})
const fullOptions2 = this.CreateWelcomePane(layoutToUse, osmConnection, bm);
fullScreenMessage.setData(fullOptions2)
const fullOptions2 = this.CreateWelcomePane();
State.state.fullScreenMessage.setData(fullOptions2)
new FixedUiElement(`<div class='collapse-button-img' class="shadow"><img src='assets/help.svg' alt='help'></div>`).onClick(() => {
fullScreenMessage.setData(fullOptions2)
State.state.fullScreenMessage.setData(fullOptions2)
}).AttachTo("help-button-mobile");
}
static InitLayers(layoutToUse: Layout, osmConnection: OsmConnection,
changes: Changes,
allElements: ElementStorage,
bm: Basemap,
fullScreenMessage: UIEventSource<UIElement>,
selectedElement: UIEventSource<any>): {
static InitLayers(): {
minZoom: number
flayers: FilteredLayer[],
presets: Preset[]
} {
const addButtons:Preset[]
const addButtons: Preset[]
= [];
const flayers: FilteredLayer[] = []
let minZoom = 0;
for (const layer of layoutToUse.layers) {
const state = State.state;
for (const layer of state.layoutToUse.data.layers) {
const generateInfo = (tagsES, feature) => {
@ -115,14 +111,12 @@ export class InitUiElements {
tagsES,
layer.title,
layer.elementsToShow,
changes,
osmConnection.userDetails
)
};
minZoom = Math.max(minZoom, layer.minzoom);
const flayer = FilteredLayer.fromDefinition(layer, bm, allElements, changes, osmConnection.userDetails, selectedElement, generateInfo);
const flayer = FilteredLayer.fromDefinition(layer, generateInfo);
for (const preset of layer.presets ?? []) {

View file

@ -9,6 +9,7 @@ import codegrid from "codegrid-js";
import {Changes} from "./Osm/Changes";
import {UserDetails} from "./Osm/OsmConnection";
import {Basemap} from "./Leaflet/Basemap";
import {State} from "../State";
/***
* A filtered layer is a layer which offers a 'set-data' function
@ -25,12 +26,10 @@ export class FilteredLayer {
public readonly filters: TagsFilter;
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
public readonly layerDef: LayerDefinition;
private readonly _map: Basemap;
private readonly _maxAllowedOverlap: number;
private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize? : number[], popupAnchor?: number[], iconAnchor?:number[] } };
private readonly _storage: ElementStorage;
/** The featurecollection from overpass
*/
@ -43,22 +42,17 @@ export class FilteredLayer {
* The leaflet layer object which should be removed on rerendering
*/
private _geolayer;
private _selectedElement: UIEventSource<{ feature: any }>;
private _showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement;
private static readonly grid = codegrid.CodeGrid();
constructor(
layerDef: LayerDefinition,
map: Basemap, storage: ElementStorage,
changes: Changes,
selectedElement: UIEventSource<any>,
showOnPopup: ((tags: UIEventSource<any>, feature: any) => UIElement)
) {
this.layerDef = layerDef;
this._wayHandling = layerDef.wayHandling;
this._selectedElement = selectedElement;
this._showOnPopup = showOnPopup;
this._style = layerDef.style;
if (this._style === undefined) {
@ -67,33 +61,27 @@ export class FilteredLayer {
}
}
this.name = name;
this._map = map;
this.filters = layerDef.overpassFilter;
this._storage = storage;
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
const self = this;
this.isDisplayed.addCallback(function (isDisplayed) {
const map = State.state.bm.map;
if (self._geolayer !== undefined && self._geolayer !== null) {
if (isDisplayed) {
self._geolayer.addTo(self._map.map);
self._geolayer.addTo(map);
} else {
self._map.map.removeLayer(self._geolayer);
map.removeLayer(self._geolayer);
}
}
})
}
static fromDefinition(
definition,
basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>,
selectedElement: UIEventSource<{feature: any}>,
definition,
showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement):
FilteredLayer {
return new FilteredLayer(
definition,
basemap, allElements, changes,
selectedElement,
showOnPopup);
definition, showOnPopup);
}
@ -170,7 +158,7 @@ export class FilteredLayer {
let self = this;
if (this._geolayer !== undefined && this._geolayer !== null) {
this._map.map.removeLayer(this._geolayer);
State.state.bm.map.removeLayer(this._geolayer);
}
this._dataFromOverpass = data;
const fusedFeatures = [];
@ -227,7 +215,7 @@ export class FilteredLayer {
icon: new L.icon(style.icon),
});
}
let eventSource = self._storage.addOrGetElement(feature);
let eventSource = State.state.allElements.addOrGetElement(feature);
const uiElement = self._showOnPopup(eventSource, feature);
const popup = L.popup({}, marker).setContent(uiElement.Render());
marker.bindPopup(popup)
@ -246,7 +234,7 @@ export class FilteredLayer {
} else {
self._geolayer.setStyle(function (feature) {
const style = self._style(feature.properties);
if (self._selectedElement.data?.feature === feature) {
if (State.state.selectedElement.data?.feature === feature) {
if (style.weight !== undefined) {
style.weight = style.weight * 2;
}else{
@ -258,14 +246,14 @@ export class FilteredLayer {
}
}
let eventSource = self._storage.addOrGetElement(feature);
let eventSource = State.state.allElements.addOrGetElement(feature);
eventSource.addCallback(feature.updateStyle);
layer.on("click", function (e) {
const previousFeature = self._selectedElement.data?.feature;
self._selectedElement.setData({feature: feature});
const previousFeature =State.state.selectedElement.data?.feature;
State.state.selectedElement.setData({feature: feature});
feature.updateStyle();
previousFeature?.updateStyle();
@ -281,7 +269,7 @@ export class FilteredLayer {
})
.setContent(uiElement.Render())
.setLatLng(e.latlng)
.openOn(self._map.map);
.openOn(State.state.bm.map);
uiElement.Update();
uiElement.Activate();
L.DomEvent.stop(e); // Marks the event as consumed
@ -290,7 +278,7 @@ export class FilteredLayer {
});
if (this.isDisplayed.data) {
this._geolayer.addTo(this._map.map);
this._geolayer.addTo(State.state.bm.map);
}
}

View file

@ -5,6 +5,7 @@ import {SimpleImageElement} from "../UI/Image/SimpleImageElement";
import {UIElement} from "../UI/UIElement";
import {Changes} from "./Osm/Changes";
import {ImgurImage} from "../UI/Image/ImgurImage";
import {State} from "../State";
/**
* There are multiple way to fetch images for an object
@ -27,16 +28,13 @@ export class ImageSearcher extends UIEventSource<string[]> {
private readonly _wdItem = new UIEventSource<string>("");
private readonly _commons = new UIEventSource<string>("");
private _activated: boolean = false;
private _changes: Changes;
public _deletedImages = new UIEventSource<string[]>([]);
constructor(tags: UIEventSource<any>,
changes: Changes) {
constructor(tags: UIEventSource<any>) {
super([]);
this._tags = tags;
this._changes = changes;
const self = this;
this._wdItem.addCallback(() => {
@ -119,7 +117,7 @@ export class ImageSearcher extends UIEventSource<string[]> {
return;
}
console.log("Deleting image...", key, " --> ", url);
this._changes.addChange(this._tags.data.id, key, "");
State.state.changes.addChange(this._tags.data.id, key, "");
this._deletedImages.data.push(url);
this._deletedImages.ping();
}

View file

@ -4,9 +4,9 @@ import {FilteredLayer} from "./FilteredLayer";
import {Bounds} from "./Bounds";
import {Overpass} from "./Osm/Overpass";
import {Basemap} from "./Leaflet/Basemap";
import {State} from "../State";
export class LayerUpdater {
private _map: Basemap;
private _layers: FilteredLayer[];
private widenFactor: number;
@ -26,12 +26,10 @@ export class LayerUpdater {
* @param minzoom
* @param layers
*/
constructor(map: Basemap,
minzoom: number,
constructor(minzoom: number,
widenFactor: number,
layers: FilteredLayer[]) {
this.widenFactor = widenFactor;
this._map = map;
this._layers = layers;
this._minzoom = minzoom;
var filters: TagsFilter[] = [];
@ -41,7 +39,7 @@ export class LayerUpdater {
this._overpass = new Overpass(new Or(filters));
const self = this;
map.Location.addCallback(function () {
State.state.locationControl.addCallback(function () {
self.update();
});
self.update();
@ -67,9 +65,7 @@ export class LayerUpdater {
renderLayers(rest);
}, 50)
}
renderLayers(this._layers);
}
private handleFail(reason: any) {
@ -89,8 +85,8 @@ export class LayerUpdater {
if (this.IsInBounds()) {
return;
}
console.log("Zoom level: ",this._map.map.getZoom(), "Least needed zoom:", this._minzoom)
if (this._map.map.getZoom() < this._minzoom || this._map.Location.data.zoom < this._minzoom) {
console.log("Zoom level: ",State.state.bm.map.getZoom(), "Least needed zoom:", this._minzoom)
if (State.state.bm.map.getZoom() < this._minzoom || State.state.bm.Location.data.zoom < this._minzoom) {
return;
}
@ -98,7 +94,7 @@ export class LayerUpdater {
console.log("Still running a query, skip");
}
const bounds = this._map.map.getBounds();
const bounds = State.state.bm.map.getBounds();
const diff = this.widenFactor;
@ -131,7 +127,7 @@ export class LayerUpdater {
}
const b = this._map.map.getBounds();
const b = State.state.bm.map.getBounds();
if (b.getSouth() < this.previousBounds.south) {
return false;
}

View file

@ -1,25 +1,20 @@
import {Basemap} from "./Basemap";
import L from "leaflet";
import {UIEventSource} from "../../UI/UIEventSource";
import {UIElement} from "../../UI/UIElement";
import {Helpers} from "../../Helpers";
import {State} from "../../State";
export class GeoLocationHandler extends UIElement {
currentLocation: UIEventSource<{
latlng: number,
accuracy: number
}> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined);
private _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private _permission: UIEventSource<string> = new UIEventSource<string>("");
private _map: Basemap;
private _marker: any;
private _hasLocation: UIEventSource<boolean>;
constructor(map: Basemap) {
constructor() {
super(undefined);
this._map = map;
this.ListenTo(this.currentLocation);
this._hasLocation = State.state.currentGPSLocation.map((location) => location !== undefined);
this.ListenTo(this._hasLocation);
this.ListenTo(this._isActive);
this.ListenTo(this._permission);
@ -29,23 +24,24 @@ export class GeoLocationHandler extends UIElement {
function onAccuratePositionProgress(e) {
console.log(e.accuracy);
console.log(e.latlng);
self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
}
function onAccuratePositionFound(e) {
console.log(e.accuracy);
console.log(e.latlng);
self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
}
function onAccuratePositionError(e) {
console.log("onerror", e.message);
}
map.map.on('accuratepositionprogress', onAccuratePositionProgress);
map.map.on('accuratepositionfound', onAccuratePositionFound);
map.map.on('accuratepositionerror', onAccuratePositionError);
const map = State.state.bm.map;
map.on('accuratepositionprogress', onAccuratePositionProgress);
map.on('accuratepositionfound', onAccuratePositionFound);
map.on('accuratepositionerror', onAccuratePositionError);
const icon = L.icon(
@ -55,12 +51,12 @@ export class GeoLocationHandler extends UIElement {
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
})
this.currentLocation.addCallback((location) => {
State.state.currentGPSLocation.addCallback((location) => {
const newMarker = L.marker(location.latlng, {icon: icon});
newMarker.addTo(map.map);
if (self._marker !== undefined) {
map.map.removeLayer(self._marker);
map.removeLayer(self._marker);
}
self._marker = newMarker;
});
@ -81,7 +77,7 @@ export class GeoLocationHandler extends UIElement {
}
InnerRender(): string {
if (this.currentLocation.data) {
if (this._hasLocation.data) {
return "<img src='assets/crosshair-blue.png' alt='locate me'>";
}
if (this._isActive.data) {
@ -94,17 +90,17 @@ export class GeoLocationHandler extends UIElement {
private StartGeolocating() {
const self = this;
const map = State.state.bm.map;
if (self._permission.data === "denied") {
return "";
}
if (self.currentLocation.data !== undefined) {
self._map.map.flyTo(self.currentLocation.data.latlng, 18);
if (State.state.currentGPSLocation.data !== undefined) {
map.flyTo(State.state.currentGPSLocation.data.latlng, 18);
}
console.log("Searching location using GPS")
self._map.map.findAccuratePosition({
map.findAccuratePosition({
maxWait: 10000, // defaults to 10000
desiredAccuracy: 50 // defaults to 20
});
@ -113,13 +109,13 @@ export class GeoLocationHandler extends UIElement {
if (!self._isActive.data) {
self._isActive.setData(true);
Helpers.DoEvery(60000, () => {
if(document.visibilityState !== "visible"){
if (document.visibilityState !== "visible") {
console.log("Not starting gps: document not visible")
return;
}
self._map.map.findAccuratePosition({
map.findAccuratePosition({
maxWait: 10000, // defaults to 10000
desiredAccuracy: 50 // defaults to 20
});

View file

@ -2,29 +2,23 @@ import {Basemap} from "./Basemap";
import L from "leaflet";
import {UIEventSource} from "../../UI/UIEventSource";
import {UIElement} from "../../UI/UIElement";
import {State} from "../../State";
/**
* The stray-click-hanlders adds a marker to the map if no feature was clicked.
* Shows the given uiToShow-element in the messagebox
*/
export class StrayClickHandler {
private _basemap: Basemap;
private _lastMarker;
private _fullScreenMessage: UIEventSource<UIElement>;
private _uiToShow: (() => UIElement);
constructor(
basemap: Basemap,
selectElement: UIEventSource<{ feature: any }>,
fullScreenMessage: UIEventSource<UIElement>,
uiToShow: (() => UIElement)) {
this._basemap = basemap;
this._fullScreenMessage = fullScreenMessage;
this._uiToShow = uiToShow;
const self = this;
const map = basemap.map;
basemap.LastClickLocation.addCallback(function (lastClick) {
selectElement.setData(undefined);
const map = State.state.bm.map;
State.state.bm.LastClickLocation.addCallback(function (lastClick) {
State.state.selectedElement.setData(undefined);
if (self._lastMarker !== undefined) {
map.removeLayer(self._lastMarker);
@ -32,9 +26,9 @@ export class StrayClickHandler {
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
icon: L.icon({
iconUrl: "./assets/add.svg",
iconSize: [50,50],
iconAnchor: [25,50],
popupAnchor: [0,-45]
iconSize: [50, 50],
iconAnchor: [25, 50],
popupAnchor: [0, -45]
})
});
const uiElement = uiToShow();
@ -45,13 +39,13 @@ export class StrayClickHandler {
self._lastMarker.bindPopup(popup).openPopup();
self._lastMarker.on("click", () => {
fullScreenMessage.setData(self._uiToShow());
State.state.fullScreenMessage.setData(self._uiToShow());
});
uiElement.Update();
uiElement.Activate();
});
selectElement.addCallback(() => {
State.state.selectedElement.addCallback(() => {
if (self._lastMarker !== undefined) {
map.removeLayer(self._lastMarker);
this._lastMarker = undefined;

View file

@ -7,14 +7,12 @@ import {OsmConnection} from "./OsmConnection";
import {OsmNode, OsmObject} from "./OsmObject";
import {And, Tag, TagsFilter} from "../TagsFilter";
import {ElementStorage} from "../ElementStorage";
import {State} from "../../State";
export class Changes {
private static _nextId = -1; // New assined ID's are negative
public readonly login: OsmConnection;
public readonly _allElements: ElementStorage;
private _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll
private newElements: OsmObject[] = []; // Gets reset on uploadAll
@ -27,8 +25,6 @@ export class Changes {
login: OsmConnection,
allElements: ElementStorage) {
this._changesetComment = changesetComment;
this.login = login;
this._allElements = allElements;
}
addTag(elementId: string, tagsFilter : TagsFilter){
@ -66,7 +62,7 @@ console.log("Received change",key, value)
return;
}
const eventSource = this._allElements.getElement(elementId);
const eventSource = State.state.allElements.getElement(elementId);
eventSource.data[key] = value;
eventSource.ping();
@ -104,7 +100,7 @@ console.log("Received change",key, value)
]
}
}
this._allElements.addOrGetElement(geojson);
State.state.allElements.addOrGetElement(geojson);
// The basictags are COPIED, the id is included in the properties
// The tags are not yet written into the OsmObject, but this is applied onto a
@ -208,16 +204,16 @@ console.log("Received change",key, value)
for (const oldId in idMapping) {
const newId = idMapping[oldId];
const element = self._allElements.getElement(oldId);
const element = State.state.allElements.getElement(oldId);
element.data.id = newId;
self._allElements.addElementById(newId, element);
State.state.allElements.addElementById(newId, element);
element.ping();
}
}
console.log("Beginning upload...");
// At last, we build the changeset and upload
self.login.UploadChangeset(self._changesetComment,
State.state.osmConnection.UploadChangeset(self._changesetComment,
function (csId) {
let modifications = "";

View file

@ -1,14 +1,14 @@
import {Basemap} from "../Leaflet/Basemap";
import $ from "jquery"
import {State} from "../../State";
export class Geocoding {
private static readonly host = "https://nominatim.openstreetmap.org/search?";
static Search(query: string,
basemap: Basemap,
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void),
onFail: (() => void)) {
const b = basemap.map.getBounds();
const b = State.state.bm.map.getBounds();
console.log(b);
$.getJSON(
Geocoding.host + "format=json&limit=1&viewbox=" +

View file

@ -10,7 +10,6 @@ export class UserDetails {
public img: string;
public unreadMessages = 0;
public totalMessages = 0;
public osmConnection: OsmConnection;
public dryRun: boolean;
home: { lon: number; lat: number };
}
@ -23,24 +22,43 @@ export class OsmConnection {
constructor(dryRun: boolean, oauth_token: UIEventSource<string>) {
this.auth = new osmAuth({
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
singlepage: true,
landing: window.location.href,
auto: true // show a login form if the user is not authenticated and
// you try to do a call
let pwaStandAloneMode = false;
try {
});
if (window.matchMedia('(display-mode: standalone)').matches || window.matchMedia('(display-mode: fullscreen)').matches) {
pwaStandAloneMode = true;
}
} catch (e) {
console.warn("Detecting standalone mode failed", e, ". Assuming in browser and not worrying furhter")
}
if (pwaStandAloneMode) {
// In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway...
this.auth = new osmAuth({
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
singlepage: false,
auto: true
});
} else {
this.auth = new osmAuth({
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
singlepage: true,
landing: window.location.href,
auto: true
});
}
this.userDetails = new UIEventSource<UserDetails>(new UserDetails());
this.userDetails.data.osmConnection = this;
this.userDetails.data.dryRun = dryRun;
this._dryRun = dryRun;
if(oauth_token.data !== undefined){
if (oauth_token.data !== undefined) {
console.log(oauth_token.data)
const self = this;
this.auth.bootstrapToken(oauth_token.data,

View file

@ -6,27 +6,19 @@ import {UIEventSource} from "../../UI/UIEventSource";
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
import {UserDetails} from "./OsmConnection";
import {SlideShow} from "../../UI/SlideShow";
import {State} from "../../State";
export class OsmImageUploadHandler {
private _tags: UIEventSource<any>;
private _changeHandler: Changes;
private _userdetails: UIEventSource<UserDetails>;
private _slideShow: SlideShow;
private _preferedLicense: UIEventSource<string>;
constructor(tags: UIEventSource<any>,
userdetails: UIEventSource<UserDetails>,
preferedLicense: UIEventSource<string>,
changeHandler: Changes,
slideShow : SlideShow
) {
this._slideShow = slideShow; // To move the slideshow (if any) to the last, just added element
if (tags === undefined || userdetails === undefined || changeHandler === undefined) {
throw "Something is undefined"
}
this._tags = tags;
this._changeHandler = changeHandler;
this._userdetails = userdetails;
this._preferedLicense = preferedLicense;
}
@ -36,14 +28,14 @@ export class OsmImageUploadHandler {
const title = tags.name ?? "Unknown area";
const description = [
"author:" + this._userdetails.data.name,
"author:" + State.state.osmConnection.userDetails.data.name,
"license:" + license,
"wikidata:" + tags.wikidata,
"osmid:" + tags.id,
"name:" + tags.name
].join("\n");
const changes = this._changeHandler;
const changes = State.state.changes;
return {
title: title,
description: description,
@ -73,7 +65,6 @@ export class OsmImageUploadHandler {
getUI(): ImageUploadFlow {
const self = this;
return new ImageUploadFlow(
this._userdetails,
this._preferedLicense,
function (license) {
return self.generateOptions(license)

View file

@ -51,7 +51,10 @@ export class QueryParameters {
}
public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> {
QueryParameters.defaults[key] = deflt;
if (deflt !== undefined) {
console.log(key, "-->", deflt)
QueryParameters.defaults[key] = deflt;
}
if (QueryParameters.knownSources[key] !== undefined) {
return QueryParameters.knownSources[key];
}

134
State.ts Normal file
View file

@ -0,0 +1,134 @@
import {UIEventSource} from "./UI/UIEventSource";
import {UIElement} from "./UI/UIElement";
import {QueryParameters} from "./Logic/QueryParameters";
import {LocalStorageSource} from "./Logic/LocalStorageSource";
import {Layout} from "./Customizations/Layout";
import {Utils} from "./Utils";
import {LayerDefinition} from "./Customizations/LayerDefinition";
import {ElementStorage} from "./Logic/ElementStorage";
import {Changes} from "./Logic/Osm/Changes";
import {Basemap} from "./Logic/Leaflet/Basemap";
import {OsmConnection} from "./Logic/Osm/OsmConnection";
/**
* Contains the global state: a bunch of UI-event sources
*/
export class State {
// The singleton of the global state
public static state: State;
/**
THe layout to use
*/
public readonly layoutToUse = new UIEventSource<Layout>(undefined);
/**
The mapping from id -> UIEventSource<properties>
*/
public allElements: ElementStorage;
/**
THe change handler
*/
public changes: Changes;
/**
THe basemap with leaflet instance
*/
public bm: Basemap;
/**
The user crednetials
*/
public osmConnection: OsmConnection;
/**
* The message that should be shown at the center of the screen
*/
public readonly centerMessage = new UIEventSource<string>("");
/**
* The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
*/
public readonly secondsTillChangesAreSaved = new UIEventSource<number>(0);
/**
This message is shown full screen on mobile devices
*/
public readonly fullScreenMessage = new UIEventSource<UIElement>(undefined);
/**
The latest element that was selected - used to generate the right UI at the right place
*/
public readonly selectedElement = new UIEventSource<{ feature: any }>(undefined);
public readonly zoom = QueryParameters.GetQueryParameter("z", undefined)
.syncWith(LocalStorageSource.Get("zoom"));
public readonly lat = QueryParameters.GetQueryParameter("lat", undefined)
.syncWith(LocalStorageSource.Get("lat"));
public readonly lon = QueryParameters.GetQueryParameter("lon", undefined)
.syncWith(LocalStorageSource.Get("lon"));
public readonly featureSwitchUserbadge: UIEventSource<boolean>;
public readonly featureSwitchSearch: UIEventSource<boolean>;
public readonly featureSwitchLayers: UIEventSource<boolean>;
public readonly featureSwitchAddNew: UIEventSource<boolean>;
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>;
public readonly featureSwitchIframe: UIEventSource<boolean>;
/**
* The map location: currently centered lat, lon and zoom
*/
public readonly locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>(undefined);
/**
* The location as delivered by the GPS
*/
public currentGPSLocation: UIEventSource<{
latlng: number,
accuracy: number
}> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined);
// After this many milliseconds without changes, saves are sent of to OSM
public readonly saveTimeout = new UIEventSource<number>(30 * 1000);
constructor(layoutToUse: Layout) {
this.layoutToUse = new UIEventSource<Layout>(layoutToUse);
this.locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
zoom: Utils.asFloat(this.zoom.data) ?? layoutToUse.startzoom,
lat: Utils.asFloat(this.lat.data) ?? layoutToUse.startLat,
lon: Utils.asFloat(this.lon.data) ?? layoutToUse.startLon
}).addCallback((latlonz) => {
this.zoom.setData(latlonz.zoom.toString());
this.lat.setData(latlonz.lat.toString().substr(0, 6));
this.lon.setData(latlonz.lon.toString().substr(0, 6));
})
const self = this;
function featSw(key: string, deflt: (layout: Layout) => boolean): UIEventSource<boolean> {
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined);
// I'm so sorry about someone trying to decipher this
// It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened
return UIEventSource.flatten(
self.layoutToUse.map((layout) =>
QueryParameters.GetQueryParameter(key, "" + deflt(layout)).map((str) => str === undefined ? deflt(layout) : str !== "false")), [queryParameterSource]);
}
this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge);
this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch);
this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers);
this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAdd);
this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true);
this.featureSwitchIframe = featSw("fs-iframe", () => false);
}
}

View file

@ -2,75 +2,60 @@ import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {OsmConnection} from "../Logic/Osm/OsmConnection";
import Translations from "./i18n/Translations";
import {State} from "../State";
export class CenterMessageBox extends UIElement {
private readonly _location: UIEventSource<{ zoom: number }>;
private readonly _zoomInMore = new UIEventSource<boolean>(true);
private readonly _centermessage: UIEventSource<string>;
private readonly _osmConnection: OsmConnection;
private readonly _queryRunning: UIEventSource<boolean>;
private startZoom: number;
constructor(
startZoom: number,
centermessage: UIEventSource<string>,
osmConnection: OsmConnection,
location: UIEventSource<{ zoom: number }>,
queryRunning: UIEventSource<boolean>
) {
super(centermessage);
super(State.state.centerMessage);
this.startZoom = startZoom;
this._centermessage = centermessage;
this._location = location;
this._osmConnection = osmConnection;
this._queryRunning = queryRunning;
this.ListenTo(State.state.locationControl);
this.ListenTo(queryRunning);
this._queryRunning = queryRunning;
const self = this;
location.addCallback(function () {
self._zoomInMore.setData(location.data.zoom < startZoom);
});
this.ListenTo(this._zoomInMore);
}
private prep(): { innerHtml: string, done: boolean } {
if (State.state.centerMessage.data != "") {
return {innerHtml: State.state.centerMessage.data, done: false};
}
if (this._queryRunning.data) {
return {innerHtml: Translations.t.centerMessage.loadingData.Render(), done: false};
} else if (State.state.locationControl.data.zoom < this.startZoom) {
return {innerHtml: Translations.t.centerMessage.zoomIn.Render(), done: false};
} else {
return {innerHtml: Translations.t.centerMessage.ready.Render(), done: true};
}
}
InnerRender(): string {
if (this._centermessage.data != "") {
return this._centermessage.data;
}
if (this._queryRunning.data) {
return Translations.t.centerMessage.loadingData.Render();
} else if (this._zoomInMore.data) {
return Translations.t.centerMessage.zoomIn.Render();
}
return Translations.t.centerMessage.ready.Render();
return this.prep().innerHtml;
}
private ShouldShowSomething() : boolean{
if (this._queryRunning.data) {
return true;
}
return this._zoomInMore.data;
}
InnerUpdate(htmlElement: HTMLElement) {
const pstyle = htmlElement.parentElement.style;
if (this._centermessage.data != "") {
if (State.state.centerMessage.data != "") {
pstyle.opacity = "1";
pstyle.pointerEvents = "all";
this._osmConnection.registerActivateOsmAUthenticationClass();
State.state.osmConnection.registerActivateOsmAUthenticationClass();
return;
}
pstyle.pointerEvents = "none";
if (this.ShouldShowSomething()) {
pstyle.opacity = "0.5";
} else {
if (this.prep().done) {
pstyle.opacity = "0";
} else {
pstyle.opacity = "0.5";
}
}

View file

@ -11,6 +11,7 @@ import Translations from "./i18n/Translations";
import {Changes} from "../Logic/Osm/Changes";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {FixedUiElement} from "./Base/FixedUiElement";
import {State} from "../State";
export class FeatureInfoBox extends UIElement {
@ -23,14 +24,11 @@ export class FeatureInfoBox extends UIElement {
*/
private _tagsES: UIEventSource<any>;
private _changes: Changes;
private _userDetails: UIEventSource<UserDetails>;
private _title: UIElement;
private _osmLink: UIElement;
private _wikipedialink: UIElement;
private _infoboxes: TagDependantUIElement[];
private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
@ -41,15 +39,11 @@ export class FeatureInfoBox extends UIElement {
tagsES: UIEventSource<any>,
title: TagRenderingOptions | UIElement | string,
elementsToShow: TagDependantUIElementConstructor[],
changes: Changes,
userDetails: UIEventSource<UserDetails>
) {
super(tagsES);
this._feature = feature;
this._tagsES = tagsES;
this._changes = changes;
this._userDetails = userDetails;
this.ListenTo(userDetails);
this.ListenTo(State.state.osmConnection.userDetails);
const deps = {tags: this._tagsES, changes: this._changes}
@ -112,7 +106,7 @@ export class FeatureInfoBox extends UIElement {
let questionsHtml = "";
if (this._userDetails.data.loggedIn && questions.length > 0) {
if (State.state.osmConnection.userDetails.data.loggedIn && questions.length > 0) {
// We select the most important question and render that one
let mostImportantQuestion;
let score = -1000;

View file

@ -2,25 +2,29 @@ import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
import {State} from "../State";
/**
* Handles the full screen popup on mobile
*/
export class FullScreenMessageBoxHandler {
private _uielement: UIEventSource<UIElement>;
constructor(uielement: UIEventSource<UIElement>,
onClear: (() => void)) {
this._uielement = uielement;
this.listenTo(uielement);
constructor(onClear: (() => void)) {
this._uielement = State.state.fullScreenMessage;
const self = this;
this._uielement.addCallback(function () {
self.update();
});
this.update();
if (window !== undefined) {
window.onhashchange = function () {
if (location.hash === "") {
// No more element: back to the map!
uielement.setData(undefined);
self._uielement.setData(undefined);
onClear();
}
}
@ -28,7 +32,7 @@ export class FullScreenMessageBoxHandler {
Translations.t.general.returnToTheMap
.onClick(() => {
uielement.setData(undefined);
self._uielement.setData(undefined);
onClear();
})
.AttachTo("to-the-map");
@ -36,13 +40,6 @@ export class FullScreenMessageBoxHandler {
}
listenTo(uiEventSource: UIEventSource<any>) {
const self = this;
uiEventSource.addCallback(function () {
self.update();
})
}
update() {
const wrapper = document.getElementById("messagesboxmobilewrapper");

View file

@ -3,12 +3,15 @@ import {ImageSearcher} from "../../Logic/ImageSearcher";
import {UIEventSource} from "../UIEventSource";
import {SlideShow} from "../SlideShow";
import {FixedUiElement} from "../Base/FixedUiElement";
import {VerticalCombine} from "../Base/VerticalCombine";
import {VariableUiElement} from "../Base/VariableUIElement";
import {ConfirmDialog} from "../ConfirmDialog";
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
import {
Dependencies,
TagDependantUIElement,
TagDependantUIElementConstructor
} from "../../Customizations/UIElementConstructor";
import {Changes} from "../../Logic/Osm/Changes";
import {UserDetails} from "../../Logic/Osm/OsmConnection";
import {State} from "../../State";
export class ImageCarouselConstructor implements TagDependantUIElementConstructor{
IsKnown(properties: any): boolean {
@ -23,8 +26,8 @@ export class ImageCarouselConstructor implements TagDependantUIElementConstructo
return 0;
}
construct(dependencies: { tags: UIEventSource<any>, changes: Changes }): TagDependantUIElement {
return new ImageCarousel(dependencies.tags, dependencies.changes);
construct(dependencies: Dependencies): TagDependantUIElement {
return new ImageCarousel(dependencies.tags);
}
}
@ -41,14 +44,11 @@ export class ImageCarousel extends TagDependantUIElement {
private readonly _deleteButton: UIElement;
private readonly _isDeleted: UIElement;
private readonly _userDetails : UIEventSource<UserDetails>;
constructor(tags: UIEventSource<any>, changes: Changes) {
constructor(tags: UIEventSource<any>) {
super(tags);
this._userDetails = changes.login.userDetails;
const self = this;
this.searcher = new ImageSearcher(tags, changes);
this.searcher = new ImageSearcher(tags);
this._uiElements = this.searcher.map((imageURLS: string[]) => {
const uiElements: UIElement[] = [];
@ -65,11 +65,11 @@ export class ImageCarousel extends TagDependantUIElement {
const showDeleteButton = this.slideshow._currentSlide.map((i) => {
if(!self._userDetails.data.loggedIn){
if(!State.state.osmConnection.userDetails.data.loggedIn){
return false;
}
return self.searcher.IsDeletable(self.searcher.data[i]);
}, [this.searcher, this._userDetails]);
}, [this.searcher, State.state.osmConnection.userDetails]);
this.slideshow._currentSlide.addCallback(() => {
showDeleteButton.ping(); // This pings the showDeleteButton, which indicates that it has to hide it's subbuttons
})

View file

@ -1,9 +1,12 @@
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
import {
Dependencies,
TagDependantUIElement,
TagDependantUIElementConstructor
} from "../../Customizations/UIElementConstructor";
import {ImageCarousel} from "./ImageCarousel";
import {UIEventSource} from "../UIEventSource";
import {ImageUploadFlow} from "../ImageUploadFlow";
import {Changes} from "../../Logic/Osm/Changes";
import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler";
import {State} from "../../State";
export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
IsKnown(properties: any): boolean {
@ -27,16 +30,13 @@ class ImageCarouselWithUpload extends TagDependantUIElement {
private _imageElement: ImageCarousel;
private _pictureUploader: ImageUploadFlow;
constructor(dependencies: {tags: UIEventSource<any>, changes: Changes}) {
constructor(dependencies: Dependencies) {
super(dependencies.tags);
const tags = dependencies.tags;
const changes = dependencies.changes;
this._imageElement = new ImageCarousel(tags, changes);
const userDetails = changes.login.userDetails;
const license = changes.login.GetPreference( "pictures-license");
this._pictureUploader = new OsmImageUploadHandler(tags,
userDetails, license,
changes, this._imageElement.slideshow).getUI();
this._imageElement = new ImageCarousel(tags);
const userDetails = State.state.osmConnection.userDetails;
const license = State.state.osmConnection.GetPreference( "pictures-license");
this._pictureUploader = new OsmImageUploadHandler(tags, license, this._imageElement.slideshow).getUI();
}

View file

@ -9,6 +9,7 @@ import Translations from "./i18n/Translations";
import {fail} from "assert";
import Combine from "./Base/Combine";
import {VerticalCombine} from "./Base/VerticalCombine";
import {State} from "../State";
export class ImageUploadFlow extends UIElement {
private _licensePicker: UIElement;
@ -17,10 +18,8 @@ export class ImageUploadFlow extends UIElement {
private _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) };
private _userdetails: UIEventSource<UserDetails>;
constructor(
userInfo: UIEventSource<UserDetails>,
preferedLicense: UIEventSource<string>,
uploadOptions: ((license: string) =>
{
@ -30,9 +29,7 @@ export class ImageUploadFlow extends UIElement {
allDone: (() => void)
})
) {
super(undefined);
this._userdetails = userInfo;
this.ListenTo(userInfo);
super(State.state.osmConnection.userDetails);
this._uploadOptions = uploadOptions;
this.ListenTo(this._isUploading);
this.ListenTo(this._didFail);
@ -56,11 +53,11 @@ export class ImageUploadFlow extends UIElement {
InnerRender(): string {
const t = Translations.t.image;
if (this._userdetails === undefined) {
if (State.state.osmConnection.userDetails === undefined) {
return ""; // No user details -> logging in is probably disabled or smthing
}
if (!this._userdetails.data.loggedIn) {
if (!State.state.osmConnection.userDetails.data.loggedIn) {
return `<div class='activate-osm-authentication'>${t.pleaseLogin.Render()}</div>`;
}
@ -79,6 +76,16 @@ export class ImageUploadFlow extends UIElement {
currentState.push(t.uploadDone)
}
let currentStateHtml = "";
if (currentState.length > 0) {
currentStateHtml = new VerticalCombine(currentState).Render();
if (!this._allDone.data) {
currentStateHtml = "<span class='alert'>" +
currentStateHtml +
"</span>";
}
}
return "" +
"<div class='imageflow'>" +
@ -89,9 +96,9 @@ export class ImageUploadFlow extends UIElement {
`<span class='imageflow-add-picture'>${Translations.t.image.addPicture.R()}</span>` +
"<div class='break'></div>" +
"</div>" +
currentStateHtml +
Translations.t.image.respectPrivacy.Render() + "<br/>" +
this._licensePicker.Render() + "<br/>" +
new VerticalCombine(currentState).Render() +
"</label>" +
"<form id='fileselector-form-" + this.id + "'>" +
"<input id='fileselector-" + this.id + "' " +
@ -106,11 +113,11 @@ export class ImageUploadFlow extends UIElement {
InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
const user = this._userdetails.data;
const user = State.state.osmConnection.userDetails.data;
htmlElement.onclick = function () {
if (!user.loggedIn) {
user.osmConnection.AttemptLogin();
State.state.osmConnection.AttemptLogin();
}
}

View file

@ -9,16 +9,13 @@ import {UIEventSource} from "./UIEventSource";
import {VariableUiElement} from "./Base/VariableUIElement";
import Combine from "./Base/Combine";
import {SubtleButton} from "./Base/SubtleButton";
import {State} from "../State";
export class MoreScreen extends UIElement {
private currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>;
private currentLayout: string;
constructor(currentLayout: string, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) {
super(currentLocation);
this.currentLayout = currentLayout;
this.currentLocation = currentLocation;
constructor() {
super(State.state.locationControl);
}
InnerRender(): string {
@ -30,12 +27,13 @@ export class MoreScreen extends UIElement {
if (layout.hideFromOverview) {
continue
}
if (layout.name === this.currentLayout) {
if (layout.name === State.state.layoutToUse.data.name) {
continue;
}
const currentLocation = State.state.locationControl.data;
const linkText =
`https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${this.currentLocation.data.zoom}&lat=${this.currentLocation.data.lat}&lon=${this.currentLocation.data.lon}`
`https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
const link =
new SubtleButton(layout.icon,
new Combine([

View file

@ -1,23 +1,21 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {Changes} from "../Logic/Osm/Changes";
import {State} from "../State";
export class PendingChanges extends UIElement {
private _pendingChangesCount: UIEventSource<number>;
private _countdown: UIEventSource<number>;
private _isSaving: UIEventSource<boolean>;
constructor(changes: Changes,
countdown: UIEventSource<number>) {
super(changes.pendingChangesES);
this.ListenTo(changes.isSaving);
this.ListenTo(countdown);
this._pendingChangesCount = changes.pendingChangesES;
this._countdown = countdown;
this._isSaving = changes.isSaving;
constructor() {
super(State.state.changes.pendingChangesES);
this.ListenTo(State.state.changes.isSaving);
this.ListenTo(State.state.secondsTillChangesAreSaved);
this._pendingChangesCount = State.state.changes.pendingChangesES;
this._isSaving = State.state.changes.isSaving;
this.onClick(() => {
changes.uploadAll();
State.state.changes.uploadAll();
})
}
@ -29,7 +27,7 @@ export class PendingChanges extends UIElement {
return "";
}
var restingSeconds = this._countdown.data / 1000;
var restingSeconds =State.state.secondsTillChangesAreSaved.data / 1000;
var dots = "";
while (restingSeconds > 0) {
dots += ".";

View file

@ -8,6 +8,7 @@ import {TextField} from "./Input/TextField";
import {Geocoding} from "../Logic/Osm/Geocoding";
import Translations from "./i18n/Translations";
import {Basemap} from "../Logic/Leaflet/Basemap";
import {State} from "../State";
export class SearchAndGo extends UIElement {
@ -23,12 +24,10 @@ export class SearchAndGo extends UIElement {
);
private _foundEntries = new UIEventSource([]);
private _map: Basemap;
private _goButton = new FixedUiElement("<img class='search-go' src='./assets/search.svg' alt='GO'>");
constructor(map: Basemap) {
constructor() {
super(undefined);
this._map = map;
this.ListenTo(this._foundEntries);
const self = this;
@ -48,7 +47,7 @@ export class SearchAndGo extends UIElement {
this._searchField.Clear();
this._placeholder.setData(Translations.t.general.search.searching);
const self = this;
Geocoding.Search(searchString, this._map, (result) => {
Geocoding.Search(searchString, (result) => {
if (result.length == 0) {
this._placeholder.setData(Translations.t.general.search.nothing);
@ -60,7 +59,7 @@ export class SearchAndGo extends UIElement {
[bb[0], bb[2]],
[bb[1], bb[3]]
]
self._map.map.fitBounds(bounds);
State.state.bm.map.fitBounds(bounds);
this._placeholder.setData(Translations.t.general.search.search);
},
() => {

View file

@ -9,6 +9,7 @@ import {CheckBox} from "./Input/CheckBox";
import {VerticalCombine} from "./Base/VerticalCombine";
import {QueryParameters} from "../Logic/QueryParameters";
import {Img} from "./Img";
import {State} from "../State";
export class ShareScreen extends UIElement {
@ -19,7 +20,7 @@ export class ShareScreen extends UIElement {
private _link: UIElement;
private _linkStatus: UIElement;
constructor(layout: Layout, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) {
constructor() {
super(undefined)
const tr = Translations.t.general.sharescreen;
@ -32,6 +33,10 @@ export class ShareScreen extends UIElement {
true
)
optionCheckboxes.push(includeLocation);
const currentLocation = State.state.locationControl;
const layout = State.state.layoutToUse.data;
optionParts.push(includeLocation.isEnabled.map((includeL) => {
if (includeL) {
return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}`

View file

@ -11,6 +11,7 @@ import {VerticalCombine} from "./Base/VerticalCombine";
import Locale from "./i18n/Locale";
import {Changes} from "../Logic/Osm/Changes";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {State} from "../State";
export interface Preset {
description: string | UIElement,
@ -24,13 +25,9 @@ export interface Preset {
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/
export class SimpleAddUI extends UIElement {
private _zoomlevel: UIEventSource<{ zoom: number }>;
private _addButtons: UIElement[];
private _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
private _changes: Changes;
private _selectedElement: UIEventSource<{ feature: any }>;
private _dataIsLoading: UIEventSource<boolean>;
private _userDetails: UIEventSource<UserDetails>;
private _confirmPreset: UIEventSource<Preset>
= new UIEventSource<Preset>(undefined);
@ -39,24 +36,17 @@ export class SimpleAddUI extends UIElement {
private goToInboxButton: UIElement = new SubtleButton("./assets/envelope.svg",
Translations.t.general.goToInbox, {url:"https://www.openstreetmap.org/messages/inbox", newTab: false});
constructor(zoomlevel: UIEventSource<{ zoom: number }>,
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
changes: Changes,
selectedElement: UIEventSource<{ feature: any }>,
constructor(
dataIsLoading: UIEventSource<boolean>,
userDetails: UIEventSource<UserDetails>,
addButtons: { description: string | UIElement, name: string | UIElement; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[],
) {
super(zoomlevel);
super(State.state.locationControl);
this.ListenTo(Locale.language);
this._zoomlevel = zoomlevel;
this._lastClickLocation = lastClickLocation;
this._changes = changes;
this._selectedElement = selectedElement;
this.ListenTo(State.state.osmConnection.userDetails);
this._dataIsLoading = dataIsLoading;
this._userDetails = userDetails;
this.ListenTo(userDetails);
this.ListenTo(dataIsLoading);
this._addButtons = [];
this.ListenTo(this._confirmPreset);
this.clss = "add-ui"
@ -102,26 +92,27 @@ export class SimpleAddUI extends UIElement {
const self = this;
return () => {
const loc = self._lastClickLocation.data;
let feature = self._changes.createElement(option.tags, loc.lat, loc.lon);
const loc = State.state.bm.lastClickLocation.data;
let feature = State.state.changes.createElement(option.tags, loc.lat, loc.lon);
option.layerToAddTo.AddNewElement(feature);
self._selectedElement.setData({feature: feature});
State.state.selectedElement.setData({feature: feature});
}
}
InnerRender(): string {
const userDetails = State.state.osmConnection.userDetails;
if (this._confirmPreset.data !== undefined) {
if(this._userDetails.data.dryRun){
if(userDetails.data.dryRun){
this.CreatePoint(this._confirmPreset.data)();
return;
}
return new Combine([
Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}),
this._userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"",
userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"",
this.confirmButton,
this.cancelButton
@ -134,15 +125,15 @@ export class SimpleAddUI extends UIElement {
let header: UIElement = Translations.t.general.add.header;
if(this._userDetails === undefined){
if(userDetails === undefined){
return header.Render();
}
if (!this._userDetails.data.loggedIn) {
if (!userDetails.data.loggedIn) {
return new Combine([header, Translations.t.general.add.pleaseLogin]).Render()
}
if (this._userDetails.data.unreadMessages > 0) {
if (userDetails.data.unreadMessages > 0) {
return new Combine([header, "<span class='alert'>",
Translations.t.general.readYourMessages,
"</span>",
@ -150,7 +141,7 @@ export class SimpleAddUI extends UIElement {
]).Render();
}
if (this._userDetails.data.dryRun) {
if (userDetails.data.dryRun) {
header = new Combine([header,
"<span class='alert'>",
"Test mode - changes won't be saved",
@ -158,13 +149,13 @@ export class SimpleAddUI extends UIElement {
]);
}
if (this._userDetails.data.csCount < 5) {
if (userDetails.data.csCount < 5) {
return new Combine([header, "<span class='alert'>",
Translations.t.general.fewChangesBefore,
"</span>"]).Render();
}
if (this._zoomlevel.data.zoom < 19) {
if (State.state.locationControl.data.zoom < 19) {
return new Combine([header, Translations.t.general.add.zoomInFurther]).Render()
}
@ -183,7 +174,7 @@ export class SimpleAddUI extends UIElement {
}
InnerUpdate(htmlElement: HTMLElement) {
this._userDetails.data.osmConnection.registerActivateOsmAUthenticationClass();
State.state.osmConnection.registerActivateOsmAUthenticationClass();
}
}

View file

@ -6,6 +6,9 @@ import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {Basemap} from "../Logic/Leaflet/Basemap";
import {State} from "../State";
import {PendingChanges} from "./PendingChanges";
import Locale from "./i18n/Locale";
/**
* Handles and updates the user badge
@ -14,27 +17,22 @@ export class UserBadge extends UIElement {
private _userDetails: UIEventSource<UserDetails>;
private _pendingChanges: UIElement;
private _logout: UIElement;
private _basemap: Basemap;
private _homeButton: UIElement;
private _languagePicker: UIElement;
constructor(userDetails: UIEventSource<UserDetails>,
pendingChanges: UIElement,
languagePicker: UIElement,
basemap: Basemap) {
super(userDetails);
this._userDetails = userDetails;
this._pendingChanges = pendingChanges;
this._basemap = basemap;
this._languagePicker = languagePicker;
constructor() {
super(State.state.osmConnection.userDetails);
this._userDetails = State.state.osmConnection.userDetails;
this._pendingChanges = new PendingChanges();
this._languagePicker = Locale.CreateLanguagePicker();
this._logout = new FixedUiElement("<img src='assets/logout.svg' class='small-userbadge-icon' alt='logout'>")
.onClick(() => {
userDetails.data.osmConnection.LogOut();
State.state.osmConnection.LogOut();
});
userDetails.addCallback(function () {
this._userDetails.addCallback(function () {
const profilePic = document.getElementById("profile-pic");
if (profilePic) {
@ -45,18 +43,18 @@ export class UserBadge extends UIElement {
});
this._homeButton = new VariableUiElement(
userDetails.map((userinfo) => {
this._userDetails.map((userinfo) => {
if (userinfo.home) {
return "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'> ";
}
return "";
})
).onClick(() => {
const home = userDetails.data?.home;
const home = State.state.osmConnection.userDetails.data?.home;
if (home === undefined) {
return;
}
basemap.map.flyTo([home.lat, home.lon], 18);
State.state.bm.map.flyTo([home.lat, home.lon], 18);
});
}
@ -91,7 +89,7 @@ export class UserBadge extends UIElement {
iconSize: [20, 20],
iconAnchor: [10, 10]
});
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(this._basemap.map);
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(State.state.bm.map);
}
const settings =

60
UI/WelcomeMessage.ts Normal file
View file

@ -0,0 +1,60 @@
import {UIElement} from "../UI/UIElement";
import {UIEventSource} from "../UI/UIEventSource";
import {OsmConnection, UserDetails} from "../Logic/Osm/OsmConnection";
import Locale from "../UI/i18n/Locale";
import {State} from "../State";
import {Layout} from "../Customizations/Layout";
import Translations from "./i18n/Translations";
import {VariableUiElement} from "./Base/VariableUIElement";
export class WelcomeMessage extends UIElement {
private readonly layout: Layout;
private languagePicker: UIElement;
private osmConnection: OsmConnection;
private readonly description: UIElement;
private readonly plzLogIn: UIElement;
private readonly welcomeBack: UIElement;
private readonly tail: UIElement;
constructor() {
super(State.state.osmConnection.userDetails);
this.languagePicker = Locale.CreateLanguagePicker(Translations.t.general.pickLanguage);
this.ListenTo(Locale.language);
function fromLayout(f: (layout: Layout) => (string | UIElement)): UIElement {
return new VariableUiElement(
State.state.layoutToUse.map((layout) => Translations.W(f(layout)).Render())
)
}
this.description = fromLayout((layout) => layout.welcomeMessage);
this.plzLogIn = fromLayout((layout) => layout.gettingStartedPlzLogin);
this.welcomeBack = fromLayout((layout) => layout.welcomeBackMessage);
this.tail = fromLayout((layout) => layout.welcomeTail);
}
InnerRender(): string {
let loginStatus = "";
if (State.state.featureSwitchUserbadge.data) {
loginStatus = (State.state.osmConnection.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render();
loginStatus = loginStatus + "<br/>"
}
return "<span>" +
this.description.Render() +
"<br/>" +
loginStatus +
this.tail.Render() +
"<br/>" +
this.languagePicker.Render() +
"</span>";
}
protected InnerUpdate(htmlElement: HTMLElement) {
this.osmConnection?.registerActivateOsmAUthenticationClass()
}
}

View file

@ -3,14 +3,15 @@ import {LocalStorageSource} from "../../Logic/LocalStorageSource";
import {DropDown} from "../Input/DropDown";
import {Layout} from "../../Customizations/Layout";
import {UIElement} from "../UIElement";
import {State} from "../../State";
export default class Locale {
public static language: UIEventSource<string> = LocalStorageSource.Get('language', "en");
public static CreateLanguagePicker(layoutToUse: Layout, label: string | UIElement = "") {
public static CreateLanguagePicker(label: string | UIElement = "") {
return new DropDown(label, layoutToUse.supportedLanguages.map(lang => {
return new DropDown(label, State.state.layoutToUse.data.supportedLanguages.map(lang => {
return {value: lang, shown: lang}
}
), Locale.language);

150
index.ts
View file

@ -31,6 +31,7 @@ import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
import {OsmConnection} from "./Logic/Osm/OsmConnection";
import {Changes} from "./Logic/Osm/Changes";
import {State} from "./State";
// --------------------- Special actions based on the parameters -----------------
@ -79,70 +80,23 @@ defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data;
const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"];
console.log("Using layout: ", layoutToUse.name);
if(layoutToUse === undefined){
if (layoutToUse === undefined) {
console.log("Incorrect layout")
}
// ----------------- Setup a few event sources -------------
// The message that should be shown at the center of the screen
const centerMessage = new UIEventSource<string>("");
// The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
const secondsTillChangesAreSaved = new UIEventSource<number>(0);
// const leftMessage = new UIEventSource<() => UIElement>(undefined);
// This message is shown full screen on mobile devices
const fullScreenMessage = new UIEventSource<UIElement>(undefined);
// The latest element that was selected - used to generate the right UI at the right place
const selectedElement = new UIEventSource<{ feature: any }>(undefined);
const zoom = QueryParameters.GetQueryParameter("z", undefined)
.syncWith(LocalStorageSource.Get("zoom"));
const lat = QueryParameters.GetQueryParameter("lat", undefined)
.syncWith(LocalStorageSource.Get("lat"));
const lon = QueryParameters.GetQueryParameter("lon", undefined)
.syncWith(LocalStorageSource.Get("lon"));
function featSw(key: string, deflt: boolean): UIEventSource<boolean> {
return QueryParameters.GetQueryParameter(key, "" + deflt).map((str) => {
return str !== "false";
});
}
const featureSwitchUserbadge = featSw("fs-userbadge", layoutToUse.enableUserBadge);
const featureSwitchSearch = featSw("fs-search", layoutToUse.enableSearch);
const featureSwitchLayers = featSw("fs-layers", layoutToUse.enableLayers);
const featureSwitchAddNew = featSw("fs-add-new", layoutToUse.enableAdd);
const featureSwitchWelcomeMessage = featSw("fs-welcome-message", true);
const featureSwitchIframe = featSw("fs-iframe", false);
const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
zoom: Utils.asFloat(zoom.data) ?? layoutToUse.startzoom,
lat: Utils.asFloat(lat.data) ?? layoutToUse.startLat,
lon: Utils.asFloat(lon.data) ?? layoutToUse.startLon
});
locationControl.addCallback((latlonz) => {
zoom.setData(latlonz.zoom.toString());
lat.setData(latlonz.lat.toString().substr(0, 6));
lon.setData(latlonz.lon.toString().substr(0, 6));
})
// Setup the global state
State.state = new State(layoutToUse);
const state = State.state;
// ----------------- Prepare the important objects -----------------
const osmConnection: OsmConnection = new OsmConnection(
state.osmConnection = new OsmConnection(
QueryParameters.GetQueryParameter("test", "false").data === "true",
QueryParameters.GetQueryParameter("oauth_token", undefined)
);
Locale.language.syncWith(osmConnection.GetPreference("language"));
Locale.language.syncWith(state.osmConnection.GetPreference("language"));
// @ts-ignore
window.setLanguage = function (language: string) {
@ -158,13 +112,12 @@ Locale.language.addCallback((currentLanguage) => {
}).ping()
const saveTimeout = 30000; // After this many milliseconds without changes, saves are sent of to OSM
const allElements = new ElementStorage();
const changes = new Changes(
"Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name,
osmConnection, allElements);
const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
locationControl.map((location) => {
state.allElements = new ElementStorage();
state.changes = new Changes(
"Beantwoorden van vragen met #MapComplete voor vragenset #" + state.layoutToUse.data.name,
state.osmConnection, state.allElements);
state.bm = new Basemap("leafletDiv", state.locationControl, new VariableUiElement(
state.locationControl.map((location) => {
const mapComplete = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " +
" " +
"<a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'><img src='./assets/bug.svg' alt='Report bug' class='small-userbadge-icon'></a>";
@ -184,9 +137,9 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
// ------------- Setup the layers -------------------------------
const layerSetup = InitUiElements.InitLayers(layoutToUse, osmConnection, changes, allElements, bm, fullScreenMessage, selectedElement);
const layerSetup = InitUiElements.InitLayers();
const layerUpdater = new LayerUpdater(bm, layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers);
const layerUpdater = new LayerUpdater(layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers);
// --------------- Setting up layer selection ui --------
@ -196,22 +149,24 @@ const closedFilterButton = `<button id="filter__button" class="filter__button sh
const openFilterButton = `
<button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {return {value: layer, shown: layer.name}});
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]);
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {
return {value: layer, shown: layer.name}
});
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, State.state.bm.CurrentLayer), openFilterButton]);
const layerSelection = new Combine([`<p class="filter__label">Maplayers</p>`, new LayerSelection(layerSetup.flayers)]);
let layerControl = backgroundMapPicker;
if (layerSetup.flayers.length > 1) {
layerControl = new Combine([layerSelection, backgroundMapPicker]);
}
InitUiElements.OnlyIf(featureSwitchLayers, () => {
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
const checkbox = new CheckBox(layerControl, closedFilterButton);
checkbox.AttachTo("filter__selection");
bm.Location.addCallback(() => {
State.state.bm.Location.addCallback(() => {
checkbox.isEnabled.setData(false);
});
});
@ -224,14 +179,10 @@ Locale.language.addCallback(e => {
})
InitUiElements.OnlyIf(featureSwitchAddNew, () => {
new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => {
return new SimpleAddUI(bm.Location,
bm.LastClickLocation,
changes,
selectedElement,
InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
new StrayClickHandler(() => {
return new SimpleAddUI(
layerUpdater.runningQuery,
osmConnection.userDetails,
layerSetup.presets);
}
);
@ -242,7 +193,7 @@ InitUiElements.OnlyIf(featureSwitchAddNew, () => {
* Show the questions and information for the selected element
* This is given to the div which renders fullscreen on mobile devices
*/
selectedElement.addCallback((feature) => {
State.state.selectedElement.addCallback((feature) => {
if (feature?.feature?.properties === undefined) {
return;
}
@ -256,67 +207,54 @@ selectedElement.addCallback((feature) => {
const featureBox = new FeatureInfoBox(
feature.feature,
allElements.getElement(data.id),
State.state.allElements.getElement(data.id),
layer.title,
layer.elementsToShow,
changes,
osmConnection.userDetails
);
fullScreenMessage.setData(featureBox);
State.state.fullScreenMessage.setData(featureBox);
break;
}
}
}
);
const pendingChanges = new PendingChanges(changes, secondsTillChangesAreSaved,);
InitUiElements.OnlyIf(featureSwitchUserbadge, () => {
new UserBadge(osmConnection.userDetails,
pendingChanges,
Locale.CreateLanguagePicker(layoutToUse),
bm)
.AttachTo('userbadge');
console.log("Enable new:",State.state.featureSwitchAddNew.data,"deafult", layoutToUse.enableAdd)
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
new UserBadge().AttachTo('userbadge');
});
InitUiElements.OnlyIf((featureSwitchSearch), () => {
new SearchAndGo(bm).AttachTo("searchbox");
InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
new SearchAndGo().AttachTo("searchbox");
});
new FullScreenMessageBoxHandler(fullScreenMessage, () => {
selectedElement.setData(undefined)
new FullScreenMessageBoxHandler(() => {
State.state.selectedElement.setData(undefined)
}).update();
InitUiElements.OnlyIf(featureSwitchWelcomeMessage, () => {
InitUiElements.InitWelcomeMessage(layoutToUse,
featureSwitchUserbadge.data ? osmConnection : undefined, bm, fullScreenMessage)
InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
InitUiElements.InitWelcomeMessage()
});
if ((window != window.top && !featureSwitchWelcomeMessage.data) || featureSwitchIframe.data) {
if ((window != window.top && !State.state.featureSwitchWelcomeMessage) || State.state.featureSwitchIframe.data) {
new FixedUiElement(`<a href='${window.location}' target='_blank'><span class='iframe-escape'><img src='assets/pop-out.svg'></span></a>`).AttachTo("top-right")
}
new CenterMessageBox(
layerSetup.minZoom,
centerMessage,
osmConnection,
locationControl,
layerUpdater.runningQuery)
.AttachTo("centermessage");
Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout);
Helpers.LastEffortSave(changes);
osmConnection.registerActivateOsmAUthenticationClass();
Helpers.SetupAutoSave();
Helpers.LastEffortSave();
new GeoLocationHandler(bm).AttachTo("geolocate-button");
new GeoLocationHandler().AttachTo("geolocate-button");
locationControl.ping()
State.state.osmConnection.registerActivateOsmAUthenticationClass();
State.state.locationControl.ping()