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({ const artistQuestion = new TagRenderingOptions({
question: t.artist.question, question: t.artist.question,
freeform: { freeform: {
key: "artist", key: "artist_name",
template: "$$$", template: "$$$",
renderTemplate: "{artist}" renderTemplate: "{artist_name}"
} }
}); });

View file

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

View file

@ -1,12 +1,6 @@
import {LayerDefinition} from "./LayerDefinition"; import {LayerDefinition} from "./LayerDefinition";
import {UIElement} from "../UI/UIElement"; import {UIElement} from "../UI/UIElement";
import {FixedUiElement} from "../UI/Base/FixedUiElement";
import Translation from "../UI/i18n/Translation";
import Translations from "../UI/i18n/Translations"; 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). * 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.welcomeBackMessage = Translations.W(welcomeBackMessage);
this.welcomeTail = Translations.W(welcomeTail); 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.name = "smoothness";
this.minzoom = 17; this.minzoom = 17;
this.overpassFilter = new Or([ this.overpassFilter = new Or([
new Tag("highway","unclassified"),
new Tag("highway", "residential"), new Tag("highway", "residential"),
new Tag("highway", "cycleway"), new Tag("highway", "cycleway"),
new Tag("highway", "footway"), new Tag("highway", "footway"),

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ import codegrid from "codegrid-js";
import {Changes} from "./Osm/Changes"; import {Changes} from "./Osm/Changes";
import {UserDetails} from "./Osm/OsmConnection"; import {UserDetails} from "./Osm/OsmConnection";
import {Basemap} from "./Leaflet/Basemap"; import {Basemap} from "./Leaflet/Basemap";
import {State} from "../State";
/*** /***
* A filtered layer is a layer which offers a 'set-data' function * 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 filters: TagsFilter;
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true); public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
public readonly layerDef: LayerDefinition; public readonly layerDef: LayerDefinition;
private readonly _map: Basemap;
private readonly _maxAllowedOverlap: number; private readonly _maxAllowedOverlap: number;
private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize? : number[], popupAnchor?: number[], iconAnchor?: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 /** The featurecollection from overpass
*/ */
@ -43,22 +42,17 @@ export class FilteredLayer {
* The leaflet layer object which should be removed on rerendering * The leaflet layer object which should be removed on rerendering
*/ */
private _geolayer; private _geolayer;
private _selectedElement: UIEventSource<{ feature: any }>;
private _showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement; private _showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement;
private static readonly grid = codegrid.CodeGrid(); private static readonly grid = codegrid.CodeGrid();
constructor( constructor(
layerDef: LayerDefinition, layerDef: LayerDefinition,
map: Basemap, storage: ElementStorage,
changes: Changes,
selectedElement: UIEventSource<any>,
showOnPopup: ((tags: UIEventSource<any>, feature: any) => UIElement) showOnPopup: ((tags: UIEventSource<any>, feature: any) => UIElement)
) { ) {
this.layerDef = layerDef; this.layerDef = layerDef;
this._wayHandling = layerDef.wayHandling; this._wayHandling = layerDef.wayHandling;
this._selectedElement = selectedElement;
this._showOnPopup = showOnPopup; this._showOnPopup = showOnPopup;
this._style = layerDef.style; this._style = layerDef.style;
if (this._style === undefined) { if (this._style === undefined) {
@ -67,17 +61,16 @@ export class FilteredLayer {
} }
} }
this.name = name; this.name = name;
this._map = map;
this.filters = layerDef.overpassFilter; this.filters = layerDef.overpassFilter;
this._storage = storage;
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage; this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
const self = this; const self = this;
this.isDisplayed.addCallback(function (isDisplayed) { this.isDisplayed.addCallback(function (isDisplayed) {
const map = State.state.bm.map;
if (self._geolayer !== undefined && self._geolayer !== null) { if (self._geolayer !== undefined && self._geolayer !== null) {
if (isDisplayed) { if (isDisplayed) {
self._geolayer.addTo(self._map.map); self._geolayer.addTo(map);
} else { } else {
self._map.map.removeLayer(self._geolayer); map.removeLayer(self._geolayer);
} }
} }
}) })
@ -85,15 +78,10 @@ export class FilteredLayer {
static fromDefinition( static fromDefinition(
definition, definition,
basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>,
selectedElement: UIEventSource<{feature: any}>,
showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement): showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement):
FilteredLayer { FilteredLayer {
return new FilteredLayer( return new FilteredLayer(
definition, definition, showOnPopup);
basemap, allElements, changes,
selectedElement,
showOnPopup);
} }
@ -170,7 +158,7 @@ export class FilteredLayer {
let self = this; let self = this;
if (this._geolayer !== undefined && this._geolayer !== null) { if (this._geolayer !== undefined && this._geolayer !== null) {
this._map.map.removeLayer(this._geolayer); State.state.bm.map.removeLayer(this._geolayer);
} }
this._dataFromOverpass = data; this._dataFromOverpass = data;
const fusedFeatures = []; const fusedFeatures = [];
@ -227,7 +215,7 @@ export class FilteredLayer {
icon: new L.icon(style.icon), 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 uiElement = self._showOnPopup(eventSource, feature);
const popup = L.popup({}, marker).setContent(uiElement.Render()); const popup = L.popup({}, marker).setContent(uiElement.Render());
marker.bindPopup(popup) marker.bindPopup(popup)
@ -246,7 +234,7 @@ export class FilteredLayer {
} else { } else {
self._geolayer.setStyle(function (feature) { self._geolayer.setStyle(function (feature) {
const style = self._style(feature.properties); const style = self._style(feature.properties);
if (self._selectedElement.data?.feature === feature) { if (State.state.selectedElement.data?.feature === feature) {
if (style.weight !== undefined) { if (style.weight !== undefined) {
style.weight = style.weight * 2; style.weight = style.weight * 2;
}else{ }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); eventSource.addCallback(feature.updateStyle);
layer.on("click", function (e) { layer.on("click", function (e) {
const previousFeature = self._selectedElement.data?.feature; const previousFeature =State.state.selectedElement.data?.feature;
self._selectedElement.setData({feature: feature}); State.state.selectedElement.setData({feature: feature});
feature.updateStyle(); feature.updateStyle();
previousFeature?.updateStyle(); previousFeature?.updateStyle();
@ -281,7 +269,7 @@ export class FilteredLayer {
}) })
.setContent(uiElement.Render()) .setContent(uiElement.Render())
.setLatLng(e.latlng) .setLatLng(e.latlng)
.openOn(self._map.map); .openOn(State.state.bm.map);
uiElement.Update(); uiElement.Update();
uiElement.Activate(); uiElement.Activate();
L.DomEvent.stop(e); // Marks the event as consumed L.DomEvent.stop(e); // Marks the event as consumed
@ -290,7 +278,7 @@ export class FilteredLayer {
}); });
if (this.isDisplayed.data) { 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 {UIElement} from "../UI/UIElement";
import {Changes} from "./Osm/Changes"; import {Changes} from "./Osm/Changes";
import {ImgurImage} from "../UI/Image/ImgurImage"; import {ImgurImage} from "../UI/Image/ImgurImage";
import {State} from "../State";
/** /**
* There are multiple way to fetch images for an object * 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 _wdItem = new UIEventSource<string>("");
private readonly _commons = new UIEventSource<string>(""); private readonly _commons = new UIEventSource<string>("");
private _activated: boolean = false; private _activated: boolean = false;
private _changes: Changes;
public _deletedImages = new UIEventSource<string[]>([]); public _deletedImages = new UIEventSource<string[]>([]);
constructor(tags: UIEventSource<any>, constructor(tags: UIEventSource<any>) {
changes: Changes) {
super([]); super([]);
this._tags = tags; this._tags = tags;
this._changes = changes;
const self = this; const self = this;
this._wdItem.addCallback(() => { this._wdItem.addCallback(() => {
@ -119,7 +117,7 @@ export class ImageSearcher extends UIEventSource<string[]> {
return; return;
} }
console.log("Deleting image...", key, " --> ", url); 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.data.push(url);
this._deletedImages.ping(); this._deletedImages.ping();
} }

View file

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

View file

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

View file

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

View file

@ -7,14 +7,12 @@ import {OsmConnection} from "./OsmConnection";
import {OsmNode, OsmObject} from "./OsmObject"; import {OsmNode, OsmObject} from "./OsmObject";
import {And, Tag, TagsFilter} from "../TagsFilter"; import {And, Tag, TagsFilter} from "../TagsFilter";
import {ElementStorage} from "../ElementStorage"; import {ElementStorage} from "../ElementStorage";
import {State} from "../../State";
export class Changes { export class Changes {
private static _nextId = -1; // New assined ID's are negative 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 _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll
private newElements: OsmObject[] = []; // Gets reset on uploadAll private newElements: OsmObject[] = []; // Gets reset on uploadAll
@ -27,8 +25,6 @@ export class Changes {
login: OsmConnection, login: OsmConnection,
allElements: ElementStorage) { allElements: ElementStorage) {
this._changesetComment = changesetComment; this._changesetComment = changesetComment;
this.login = login;
this._allElements = allElements;
} }
addTag(elementId: string, tagsFilter : TagsFilter){ addTag(elementId: string, tagsFilter : TagsFilter){
@ -66,7 +62,7 @@ console.log("Received change",key, value)
return; return;
} }
const eventSource = this._allElements.getElement(elementId); const eventSource = State.state.allElements.getElement(elementId);
eventSource.data[key] = value; eventSource.data[key] = value;
eventSource.ping(); 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 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 // 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) { for (const oldId in idMapping) {
const newId = idMapping[oldId]; const newId = idMapping[oldId];
const element = self._allElements.getElement(oldId); const element = State.state.allElements.getElement(oldId);
element.data.id = newId; element.data.id = newId;
self._allElements.addElementById(newId, element); State.state.allElements.addElementById(newId, element);
element.ping(); element.ping();
} }
} }
console.log("Beginning upload..."); console.log("Beginning upload...");
// At last, we build the changeset and upload // At last, we build the changeset and upload
self.login.UploadChangeset(self._changesetComment, State.state.osmConnection.UploadChangeset(self._changesetComment,
function (csId) { function (csId) {
let modifications = ""; let modifications = "";

View file

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

View file

@ -10,7 +10,6 @@ export class UserDetails {
public img: string; public img: string;
public unreadMessages = 0; public unreadMessages = 0;
public totalMessages = 0; public totalMessages = 0;
public osmConnection: OsmConnection;
public dryRun: boolean; public dryRun: boolean;
home: { lon: number; lat: number }; home: { lon: number; lat: number };
} }
@ -23,24 +22,43 @@ export class OsmConnection {
constructor(dryRun: boolean, oauth_token: UIEventSource<string>) { constructor(dryRun: boolean, oauth_token: UIEventSource<string>) {
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({ this.auth = new osmAuth({
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem', oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI', oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
singlepage: true, singlepage: true,
landing: window.location.href, landing: window.location.href,
auto: true // show a login form if the user is not authenticated and auto: true
// you try to do a call
}); });
}
this.userDetails = new UIEventSource<UserDetails>(new UserDetails()); this.userDetails = new UIEventSource<UserDetails>(new UserDetails());
this.userDetails.data.osmConnection = this;
this.userDetails.data.dryRun = dryRun; this.userDetails.data.dryRun = dryRun;
this._dryRun = dryRun; this._dryRun = dryRun;
if(oauth_token.data !== undefined){ if (oauth_token.data !== undefined) {
console.log(oauth_token.data) console.log(oauth_token.data)
const self = this; const self = this;
this.auth.bootstrapToken(oauth_token.data, this.auth.bootstrapToken(oauth_token.data,

View file

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

View file

@ -51,7 +51,10 @@ export class QueryParameters {
} }
public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> { public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> {
if (deflt !== undefined) {
console.log(key, "-->", deflt)
QueryParameters.defaults[key] = deflt; QueryParameters.defaults[key] = deflt;
}
if (QueryParameters.knownSources[key] !== undefined) { if (QueryParameters.knownSources[key] !== undefined) {
return QueryParameters.knownSources[key]; 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 {UIEventSource} from "./UIEventSource";
import {OsmConnection} from "../Logic/Osm/OsmConnection"; import {OsmConnection} from "../Logic/Osm/OsmConnection";
import Translations from "./i18n/Translations"; import Translations from "./i18n/Translations";
import {State} from "../State";
export class CenterMessageBox extends UIElement { 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 readonly _queryRunning: UIEventSource<boolean>;
private startZoom: number;
constructor( constructor(
startZoom: number, startZoom: number,
centermessage: UIEventSource<string>,
osmConnection: OsmConnection,
location: UIEventSource<{ zoom: number }>,
queryRunning: UIEventSource<boolean> queryRunning: UIEventSource<boolean>
) { ) {
super(centermessage); super(State.state.centerMessage);
this.startZoom = startZoom;
this._centermessage = centermessage; this.ListenTo(State.state.locationControl);
this._location = location;
this._osmConnection = osmConnection;
this._queryRunning = queryRunning;
this.ListenTo(queryRunning); 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 { InnerRender(): string {
return this.prep().innerHtml;
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();
} }
private ShouldShowSomething() : boolean{
if (this._queryRunning.data) {
return true;
}
return this._zoomInMore.data;
}
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
const pstyle = htmlElement.parentElement.style; const pstyle = htmlElement.parentElement.style;
if (this._centermessage.data != "") { if (State.state.centerMessage.data != "") {
pstyle.opacity = "1"; pstyle.opacity = "1";
pstyle.pointerEvents = "all"; pstyle.pointerEvents = "all";
this._osmConnection.registerActivateOsmAUthenticationClass(); State.state.osmConnection.registerActivateOsmAUthenticationClass();
return; return;
} }
pstyle.pointerEvents = "none"; pstyle.pointerEvents = "none";
if (this.prep().done) {
if (this.ShouldShowSomething()) {
pstyle.opacity = "0.5";
} else {
pstyle.opacity = "0"; 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 {Changes} from "../Logic/Osm/Changes";
import {UserDetails} from "../Logic/Osm/OsmConnection"; import {UserDetails} from "../Logic/Osm/OsmConnection";
import {FixedUiElement} from "./Base/FixedUiElement"; import {FixedUiElement} from "./Base/FixedUiElement";
import {State} from "../State";
export class FeatureInfoBox extends UIElement { export class FeatureInfoBox extends UIElement {
@ -23,14 +24,11 @@ export class FeatureInfoBox extends UIElement {
*/ */
private _tagsES: UIEventSource<any>; private _tagsES: UIEventSource<any>;
private _changes: Changes; private _changes: Changes;
private _userDetails: UIEventSource<UserDetails>;
private _title: UIElement; private _title: UIElement;
private _osmLink: UIElement; private _osmLink: UIElement;
private _wikipedialink: UIElement; private _wikipedialink: UIElement;
private _infoboxes: TagDependantUIElement[]; private _infoboxes: TagDependantUIElement[];
private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone(); private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
@ -41,15 +39,11 @@ export class FeatureInfoBox extends UIElement {
tagsES: UIEventSource<any>, tagsES: UIEventSource<any>,
title: TagRenderingOptions | UIElement | string, title: TagRenderingOptions | UIElement | string,
elementsToShow: TagDependantUIElementConstructor[], elementsToShow: TagDependantUIElementConstructor[],
changes: Changes,
userDetails: UIEventSource<UserDetails>
) { ) {
super(tagsES); super(tagsES);
this._feature = feature; this._feature = feature;
this._tagsES = tagsES; this._tagsES = tagsES;
this._changes = changes; this.ListenTo(State.state.osmConnection.userDetails);
this._userDetails = userDetails;
this.ListenTo(userDetails);
const deps = {tags: this._tagsES, changes: this._changes} const deps = {tags: this._tagsES, changes: this._changes}
@ -112,7 +106,7 @@ export class FeatureInfoBox extends UIElement {
let questionsHtml = ""; 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 // We select the most important question and render that one
let mostImportantQuestion; let mostImportantQuestion;
let score = -1000; let score = -1000;

View file

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

View file

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

View file

@ -9,6 +9,7 @@ import Translations from "./i18n/Translations";
import {fail} from "assert"; import {fail} from "assert";
import Combine from "./Base/Combine"; import Combine from "./Base/Combine";
import {VerticalCombine} from "./Base/VerticalCombine"; import {VerticalCombine} from "./Base/VerticalCombine";
import {State} from "../State";
export class ImageUploadFlow extends UIElement { export class ImageUploadFlow extends UIElement {
private _licensePicker: UIElement; private _licensePicker: UIElement;
@ -17,10 +18,8 @@ export class ImageUploadFlow extends UIElement {
private _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false); private _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private _allDone: 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 _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) };
private _userdetails: UIEventSource<UserDetails>;
constructor( constructor(
userInfo: UIEventSource<UserDetails>,
preferedLicense: UIEventSource<string>, preferedLicense: UIEventSource<string>,
uploadOptions: ((license: string) => uploadOptions: ((license: string) =>
{ {
@ -30,9 +29,7 @@ export class ImageUploadFlow extends UIElement {
allDone: (() => void) allDone: (() => void)
}) })
) { ) {
super(undefined); super(State.state.osmConnection.userDetails);
this._userdetails = userInfo;
this.ListenTo(userInfo);
this._uploadOptions = uploadOptions; this._uploadOptions = uploadOptions;
this.ListenTo(this._isUploading); this.ListenTo(this._isUploading);
this.ListenTo(this._didFail); this.ListenTo(this._didFail);
@ -56,11 +53,11 @@ export class ImageUploadFlow extends UIElement {
InnerRender(): string { InnerRender(): string {
const t = Translations.t.image; 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 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>`; return `<div class='activate-osm-authentication'>${t.pleaseLogin.Render()}</div>`;
} }
@ -79,6 +76,16 @@ export class ImageUploadFlow extends UIElement {
currentState.push(t.uploadDone) 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 "" + return "" +
"<div class='imageflow'>" + "<div class='imageflow'>" +
@ -89,9 +96,9 @@ export class ImageUploadFlow extends UIElement {
`<span class='imageflow-add-picture'>${Translations.t.image.addPicture.R()}</span>` + `<span class='imageflow-add-picture'>${Translations.t.image.addPicture.R()}</span>` +
"<div class='break'></div>" + "<div class='break'></div>" +
"</div>" + "</div>" +
currentStateHtml +
Translations.t.image.respectPrivacy.Render() + "<br/>" + Translations.t.image.respectPrivacy.Render() + "<br/>" +
this._licensePicker.Render() + "<br/>" + this._licensePicker.Render() + "<br/>" +
new VerticalCombine(currentState).Render() +
"</label>" + "</label>" +
"<form id='fileselector-form-" + this.id + "'>" + "<form id='fileselector-form-" + this.id + "'>" +
"<input id='fileselector-" + this.id + "' " + "<input id='fileselector-" + this.id + "' " +
@ -106,11 +113,11 @@ export class ImageUploadFlow extends UIElement {
InnerUpdate(htmlElement: HTMLElement) { InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement); super.InnerUpdate(htmlElement);
const user = this._userdetails.data; const user = State.state.osmConnection.userDetails.data;
htmlElement.onclick = function () { htmlElement.onclick = function () {
if (!user.loggedIn) { 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 {VariableUiElement} from "./Base/VariableUIElement";
import Combine from "./Base/Combine"; import Combine from "./Base/Combine";
import {SubtleButton} from "./Base/SubtleButton"; import {SubtleButton} from "./Base/SubtleButton";
import {State} from "../State";
export class MoreScreen extends UIElement { 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 }>) { constructor() {
super(currentLocation); super(State.state.locationControl);
this.currentLayout = currentLayout;
this.currentLocation = currentLocation;
} }
InnerRender(): string { InnerRender(): string {
@ -30,12 +27,13 @@ export class MoreScreen extends UIElement {
if (layout.hideFromOverview) { if (layout.hideFromOverview) {
continue continue
} }
if (layout.name === this.currentLayout) { if (layout.name === State.state.layoutToUse.data.name) {
continue; continue;
} }
const currentLocation = State.state.locationControl.data;
const linkText = 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 = const link =
new SubtleButton(layout.icon, new SubtleButton(layout.icon,
new Combine([ new Combine([

View file

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

View file

@ -8,6 +8,7 @@ import {TextField} from "./Input/TextField";
import {Geocoding} from "../Logic/Osm/Geocoding"; import {Geocoding} from "../Logic/Osm/Geocoding";
import Translations from "./i18n/Translations"; import Translations from "./i18n/Translations";
import {Basemap} from "../Logic/Leaflet/Basemap"; import {Basemap} from "../Logic/Leaflet/Basemap";
import {State} from "../State";
export class SearchAndGo extends UIElement { export class SearchAndGo extends UIElement {
@ -23,12 +24,10 @@ export class SearchAndGo extends UIElement {
); );
private _foundEntries = new UIEventSource([]); private _foundEntries = new UIEventSource([]);
private _map: Basemap;
private _goButton = new FixedUiElement("<img class='search-go' src='./assets/search.svg' alt='GO'>"); private _goButton = new FixedUiElement("<img class='search-go' src='./assets/search.svg' alt='GO'>");
constructor(map: Basemap) { constructor() {
super(undefined); super(undefined);
this._map = map;
this.ListenTo(this._foundEntries); this.ListenTo(this._foundEntries);
const self = this; const self = this;
@ -48,7 +47,7 @@ export class SearchAndGo extends UIElement {
this._searchField.Clear(); this._searchField.Clear();
this._placeholder.setData(Translations.t.general.search.searching); this._placeholder.setData(Translations.t.general.search.searching);
const self = this; const self = this;
Geocoding.Search(searchString, this._map, (result) => { Geocoding.Search(searchString, (result) => {
if (result.length == 0) { if (result.length == 0) {
this._placeholder.setData(Translations.t.general.search.nothing); this._placeholder.setData(Translations.t.general.search.nothing);
@ -60,7 +59,7 @@ export class SearchAndGo extends UIElement {
[bb[0], bb[2]], [bb[0], bb[2]],
[bb[1], bb[3]] [bb[1], bb[3]]
] ]
self._map.map.fitBounds(bounds); State.state.bm.map.fitBounds(bounds);
this._placeholder.setData(Translations.t.general.search.search); 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 {VerticalCombine} from "./Base/VerticalCombine";
import {QueryParameters} from "../Logic/QueryParameters"; import {QueryParameters} from "../Logic/QueryParameters";
import {Img} from "./Img"; import {Img} from "./Img";
import {State} from "../State";
export class ShareScreen extends UIElement { export class ShareScreen extends UIElement {
@ -19,7 +20,7 @@ export class ShareScreen extends UIElement {
private _link: UIElement; private _link: UIElement;
private _linkStatus: UIElement; private _linkStatus: UIElement;
constructor(layout: Layout, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) { constructor() {
super(undefined) super(undefined)
const tr = Translations.t.general.sharescreen; const tr = Translations.t.general.sharescreen;
@ -32,6 +33,10 @@ export class ShareScreen extends UIElement {
true true
) )
optionCheckboxes.push(includeLocation); optionCheckboxes.push(includeLocation);
const currentLocation = State.state.locationControl;
const layout = State.state.layoutToUse.data;
optionParts.push(includeLocation.isEnabled.map((includeL) => { optionParts.push(includeLocation.isEnabled.map((includeL) => {
if (includeL) { if (includeL) {
return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}` 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 Locale from "./i18n/Locale";
import {Changes} from "../Logic/Osm/Changes"; import {Changes} from "../Logic/Osm/Changes";
import {UserDetails} from "../Logic/Osm/OsmConnection"; import {UserDetails} from "../Logic/Osm/OsmConnection";
import {State} from "../State";
export interface Preset { export interface Preset {
description: string | UIElement, 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 * Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/ */
export class SimpleAddUI extends UIElement { export class SimpleAddUI extends UIElement {
private _zoomlevel: UIEventSource<{ zoom: number }>;
private _addButtons: UIElement[]; private _addButtons: UIElement[];
private _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
private _changes: Changes;
private _selectedElement: UIEventSource<{ feature: any }>;
private _dataIsLoading: UIEventSource<boolean>; private _dataIsLoading: UIEventSource<boolean>;
private _userDetails: UIEventSource<UserDetails>;
private _confirmPreset: UIEventSource<Preset> private _confirmPreset: UIEventSource<Preset>
= new UIEventSource<Preset>(undefined); = new UIEventSource<Preset>(undefined);
@ -39,24 +36,17 @@ export class SimpleAddUI extends UIElement {
private goToInboxButton: UIElement = new SubtleButton("./assets/envelope.svg", private goToInboxButton: UIElement = new SubtleButton("./assets/envelope.svg",
Translations.t.general.goToInbox, {url:"https://www.openstreetmap.org/messages/inbox", newTab: false}); Translations.t.general.goToInbox, {url:"https://www.openstreetmap.org/messages/inbox", newTab: false});
constructor(zoomlevel: UIEventSource<{ zoom: number }>, constructor(
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
changes: Changes,
selectedElement: UIEventSource<{ feature: any }>,
dataIsLoading: UIEventSource<boolean>, dataIsLoading: UIEventSource<boolean>,
userDetails: UIEventSource<UserDetails>,
addButtons: { description: string | UIElement, name: string | UIElement; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[], addButtons: { description: string | UIElement, name: string | UIElement; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[],
) { ) {
super(zoomlevel); super(State.state.locationControl);
this.ListenTo(Locale.language); this.ListenTo(Locale.language);
this._zoomlevel = zoomlevel; this.ListenTo(State.state.osmConnection.userDetails);
this._lastClickLocation = lastClickLocation;
this._changes = changes;
this._selectedElement = selectedElement;
this._dataIsLoading = dataIsLoading; this._dataIsLoading = dataIsLoading;
this._userDetails = userDetails;
this.ListenTo(userDetails);
this.ListenTo(dataIsLoading); this.ListenTo(dataIsLoading);
this._addButtons = []; this._addButtons = [];
this.ListenTo(this._confirmPreset); this.ListenTo(this._confirmPreset);
this.clss = "add-ui" this.clss = "add-ui"
@ -102,26 +92,27 @@ export class SimpleAddUI extends UIElement {
const self = this; const self = this;
return () => { return () => {
const loc = self._lastClickLocation.data; const loc = State.state.bm.lastClickLocation.data;
let feature = self._changes.createElement(option.tags, loc.lat, loc.lon); let feature = State.state.changes.createElement(option.tags, loc.lat, loc.lon);
option.layerToAddTo.AddNewElement(feature); option.layerToAddTo.AddNewElement(feature);
self._selectedElement.setData({feature: feature}); State.state.selectedElement.setData({feature: feature});
} }
} }
InnerRender(): string { InnerRender(): string {
const userDetails = State.state.osmConnection.userDetails;
if (this._confirmPreset.data !== undefined) { if (this._confirmPreset.data !== undefined) {
if(this._userDetails.data.dryRun){ if(userDetails.data.dryRun){
this.CreatePoint(this._confirmPreset.data)(); this.CreatePoint(this._confirmPreset.data)();
return; return;
} }
return new Combine([ return new Combine([
Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}), 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.confirmButton,
this.cancelButton this.cancelButton
@ -134,15 +125,15 @@ export class SimpleAddUI extends UIElement {
let header: UIElement = Translations.t.general.add.header; let header: UIElement = Translations.t.general.add.header;
if(this._userDetails === undefined){ if(userDetails === undefined){
return header.Render(); return header.Render();
} }
if (!this._userDetails.data.loggedIn) { if (!userDetails.data.loggedIn) {
return new Combine([header, Translations.t.general.add.pleaseLogin]).Render() 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'>", return new Combine([header, "<span class='alert'>",
Translations.t.general.readYourMessages, Translations.t.general.readYourMessages,
"</span>", "</span>",
@ -150,7 +141,7 @@ export class SimpleAddUI extends UIElement {
]).Render(); ]).Render();
} }
if (this._userDetails.data.dryRun) { if (userDetails.data.dryRun) {
header = new Combine([header, header = new Combine([header,
"<span class='alert'>", "<span class='alert'>",
"Test mode - changes won't be saved", "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'>", return new Combine([header, "<span class='alert'>",
Translations.t.general.fewChangesBefore, Translations.t.general.fewChangesBefore,
"</span>"]).Render(); "</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() return new Combine([header, Translations.t.general.add.zoomInFurther]).Render()
} }
@ -183,7 +174,7 @@ export class SimpleAddUI extends UIElement {
} }
InnerUpdate(htmlElement: HTMLElement) { 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 Translations from "./i18n/Translations";
import {UserDetails} from "../Logic/Osm/OsmConnection"; import {UserDetails} from "../Logic/Osm/OsmConnection";
import {Basemap} from "../Logic/Leaflet/Basemap"; 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 * Handles and updates the user badge
@ -14,27 +17,22 @@ export class UserBadge extends UIElement {
private _userDetails: UIEventSource<UserDetails>; private _userDetails: UIEventSource<UserDetails>;
private _pendingChanges: UIElement; private _pendingChanges: UIElement;
private _logout: UIElement; private _logout: UIElement;
private _basemap: Basemap;
private _homeButton: UIElement; private _homeButton: UIElement;
private _languagePicker: UIElement; private _languagePicker: UIElement;
constructor(userDetails: UIEventSource<UserDetails>, constructor() {
pendingChanges: UIElement, super(State.state.osmConnection.userDetails);
languagePicker: UIElement, this._userDetails = State.state.osmConnection.userDetails;
basemap: Basemap) { this._pendingChanges = new PendingChanges();
super(userDetails); this._languagePicker = Locale.CreateLanguagePicker();
this._userDetails = userDetails;
this._pendingChanges = pendingChanges;
this._basemap = basemap;
this._languagePicker = languagePicker;
this._logout = new FixedUiElement("<img src='assets/logout.svg' class='small-userbadge-icon' alt='logout'>") this._logout = new FixedUiElement("<img src='assets/logout.svg' class='small-userbadge-icon' alt='logout'>")
.onClick(() => { .onClick(() => {
userDetails.data.osmConnection.LogOut(); State.state.osmConnection.LogOut();
}); });
userDetails.addCallback(function () { this._userDetails.addCallback(function () {
const profilePic = document.getElementById("profile-pic"); const profilePic = document.getElementById("profile-pic");
if (profilePic) { if (profilePic) {
@ -45,18 +43,18 @@ export class UserBadge extends UIElement {
}); });
this._homeButton = new VariableUiElement( this._homeButton = new VariableUiElement(
userDetails.map((userinfo) => { this._userDetails.map((userinfo) => {
if (userinfo.home) { if (userinfo.home) {
return "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'> "; return "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'> ";
} }
return ""; return "";
}) })
).onClick(() => { ).onClick(() => {
const home = userDetails.data?.home; const home = State.state.osmConnection.userDetails.data?.home;
if (home === undefined) { if (home === undefined) {
return; 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], iconSize: [20, 20],
iconAnchor: [10, 10] 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 = 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 {DropDown} from "../Input/DropDown";
import {Layout} from "../../Customizations/Layout"; import {Layout} from "../../Customizations/Layout";
import {UIElement} from "../UIElement"; import {UIElement} from "../UIElement";
import {State} from "../../State";
export default class Locale { export default class Locale {
public static language: UIEventSource<string> = LocalStorageSource.Get('language', "en"); 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} return {value: lang, shown: lang}
} }
), Locale.language); ), Locale.language);

148
index.ts
View file

@ -31,6 +31,7 @@ import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler"; import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
import {OsmConnection} from "./Logic/Osm/OsmConnection"; import {OsmConnection} from "./Logic/Osm/OsmConnection";
import {Changes} from "./Logic/Osm/Changes"; import {Changes} from "./Logic/Osm/Changes";
import {State} from "./State";
// --------------------- Special actions based on the parameters ----------------- // --------------------- Special actions based on the parameters -----------------
@ -79,70 +80,23 @@ defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data;
const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"]; const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"];
console.log("Using layout: ", layoutToUse.name); console.log("Using layout: ", layoutToUse.name);
if(layoutToUse === undefined){ if (layoutToUse === undefined) {
console.log("Incorrect layout") console.log("Incorrect layout")
} }
// Setup the global state
// ----------------- Setup a few event sources ------------- State.state = new State(layoutToUse);
const state = State.state;
// 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));
})
// ----------------- Prepare the important objects ----------------- // ----------------- Prepare the important objects -----------------
const osmConnection: OsmConnection = new OsmConnection( state.osmConnection = new OsmConnection(
QueryParameters.GetQueryParameter("test", "false").data === "true", QueryParameters.GetQueryParameter("test", "false").data === "true",
QueryParameters.GetQueryParameter("oauth_token", undefined) QueryParameters.GetQueryParameter("oauth_token", undefined)
); );
Locale.language.syncWith(osmConnection.GetPreference("language")); Locale.language.syncWith(state.osmConnection.GetPreference("language"));
// @ts-ignore // @ts-ignore
window.setLanguage = function (language: string) { window.setLanguage = function (language: string) {
@ -158,13 +112,12 @@ Locale.language.addCallback((currentLanguage) => {
}).ping() }).ping()
const saveTimeout = 30000; // After this many milliseconds without changes, saves are sent of to OSM state.allElements = new ElementStorage();
const allElements = new ElementStorage(); state.changes = new Changes(
const changes = new Changes( "Beantwoorden van vragen met #MapComplete voor vragenset #" + state.layoutToUse.data.name,
"Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name, state.osmConnection, state.allElements);
osmConnection, allElements); state.bm = new Basemap("leafletDiv", state.locationControl, new VariableUiElement(
const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( state.locationControl.map((location) => {
locationControl.map((location) => {
const mapComplete = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " + 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>"; "<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 ------------------------------- // ------------- 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 -------- // --------------- Setting up layer selection ui --------
@ -196,19 +149,21 @@ const closedFilterButton = `<button id="filter__button" class="filter__button sh
const openFilterButton = ` const openFilterButton = `
<button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`; <button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {return {value: layer, shown: layer.name}}); let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]); 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)]); const layerSelection = new Combine([`<p class="filter__label">Maplayers</p>`, new LayerSelection(layerSetup.flayers)]);
let layerControl = backgroundMapPicker; let layerControl = backgroundMapPicker;
if (layerSetup.flayers.length > 1) { if (layerSetup.flayers.length > 1) {
layerControl = new Combine([layerSelection, backgroundMapPicker]); layerControl = new Combine([layerSelection, backgroundMapPicker]);
} }
InitUiElements.OnlyIf(featureSwitchLayers, () => { InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
const checkbox = new CheckBox(layerControl, closedFilterButton); const checkbox = new CheckBox(layerControl, closedFilterButton);
checkbox.AttachTo("filter__selection"); checkbox.AttachTo("filter__selection");
bm.Location.addCallback(() => { State.state.bm.Location.addCallback(() => {
checkbox.isEnabled.setData(false); checkbox.isEnabled.setData(false);
}); });
@ -224,14 +179,10 @@ Locale.language.addCallback(e => {
}) })
InitUiElements.OnlyIf(featureSwitchAddNew, () => { InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => { new StrayClickHandler(() => {
return new SimpleAddUI(bm.Location, return new SimpleAddUI(
bm.LastClickLocation,
changes,
selectedElement,
layerUpdater.runningQuery, layerUpdater.runningQuery,
osmConnection.userDetails,
layerSetup.presets); layerSetup.presets);
} }
); );
@ -242,7 +193,7 @@ InitUiElements.OnlyIf(featureSwitchAddNew, () => {
* Show the questions and information for the selected element * Show the questions and information for the selected element
* This is given to the div which renders fullscreen on mobile devices * 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) { if (feature?.feature?.properties === undefined) {
return; return;
} }
@ -256,67 +207,54 @@ selectedElement.addCallback((feature) => {
const featureBox = new FeatureInfoBox( const featureBox = new FeatureInfoBox(
feature.feature, feature.feature,
allElements.getElement(data.id), State.state.allElements.getElement(data.id),
layer.title, layer.title,
layer.elementsToShow, layer.elementsToShow,
changes,
osmConnection.userDetails
); );
fullScreenMessage.setData(featureBox); State.state.fullScreenMessage.setData(featureBox);
break; break;
} }
} }
} }
); );
console.log("Enable new:",State.state.featureSwitchAddNew.data,"deafult", layoutToUse.enableAdd)
const pendingChanges = new PendingChanges(changes, secondsTillChangesAreSaved,); InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
new UserBadge().AttachTo('userbadge');
InitUiElements.OnlyIf(featureSwitchUserbadge, () => {
new UserBadge(osmConnection.userDetails,
pendingChanges,
Locale.CreateLanguagePicker(layoutToUse),
bm)
.AttachTo('userbadge');
}); });
InitUiElements.OnlyIf((featureSwitchSearch), () => { InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
new SearchAndGo(bm).AttachTo("searchbox"); new SearchAndGo().AttachTo("searchbox");
}); });
new FullScreenMessageBoxHandler(fullScreenMessage, () => { new FullScreenMessageBoxHandler(() => {
selectedElement.setData(undefined) State.state.selectedElement.setData(undefined)
}).update(); }).update();
InitUiElements.OnlyIf(featureSwitchWelcomeMessage, () => { InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
InitUiElements.InitWelcomeMessage(layoutToUse, InitUiElements.InitWelcomeMessage()
featureSwitchUserbadge.data ? osmConnection : undefined, bm, fullScreenMessage)
}); });
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 FixedUiElement(`<a href='${window.location}' target='_blank'><span class='iframe-escape'><img src='assets/pop-out.svg'></span></a>`).AttachTo("top-right")
} }
new CenterMessageBox( new CenterMessageBox(
layerSetup.minZoom, layerSetup.minZoom,
centerMessage,
osmConnection,
locationControl,
layerUpdater.runningQuery) layerUpdater.runningQuery)
.AttachTo("centermessage"); .AttachTo("centermessage");
Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout); Helpers.SetupAutoSave();
Helpers.LastEffortSave(changes); Helpers.LastEffortSave();
osmConnection.registerActivateOsmAUthenticationClass();
new GeoLocationHandler(bm).AttachTo("geolocate-button");
new GeoLocationHandler().AttachTo("geolocate-button");
locationControl.ping() State.state.osmConnection.registerActivateOsmAUthenticationClass();
State.state.locationControl.ping()