Refactoring: introduction of global state to simplify getting common objects
This commit is contained in:
parent
afaaaaadb1
commit
004eead4ee
34 changed files with 532 additions and 506 deletions
|
@ -57,9 +57,9 @@ export class Artwork extends LayerDefinition {
|
|||
const artistQuestion = new TagRenderingOptions({
|
||||
question: t.artist.question,
|
||||
freeform: {
|
||||
key: "artist",
|
||||
key: "artist_name",
|
||||
template: "$$$",
|
||||
renderTemplate: "{artist}"
|
||||
renderTemplate: "{artist_name}"
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ export class Widths extends LayerDefinition {
|
|||
return {
|
||||
icon: null,
|
||||
color: c,
|
||||
weight: 10,
|
||||
weight: 9,
|
||||
dashArray: dashArray
|
||||
}
|
||||
}
|
||||
|
@ -259,15 +259,14 @@ export class Widths extends LayerDefinition {
|
|||
tags.targetWidth = r(props.targetWidth);
|
||||
tags.short = "";
|
||||
if (props.width < props.targetWidth) {
|
||||
tags.short = "Er is dus <b class='alert'>" + r(props.targetWidth - props.width) + "m</b> te weinig"
|
||||
tags.short = r(props.targetWidth - props.width)
|
||||
}
|
||||
},
|
||||
freeform: {
|
||||
key: "width:carriageway",
|
||||
renderTemplate: "De totale nodige ruimte voor vlot en veilig verkeer is dus <b>{targetWidth}m</b><br>" +
|
||||
"{short}",
|
||||
template: "$$$",
|
||||
}
|
||||
mappings:[
|
||||
{k: new Tag("short","*"), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus <b>{targetWidth}m</b><br>" +
|
||||
"Er is dus <span class='alert'>{short}m</span> te weinig", substitute: true},
|
||||
{k: new Tag("short",""), txt: "De totale nodige ruimte voor vlot en veilig verkeer is dus <span class='thanks'>{targetWidth}m</span>"}
|
||||
]
|
||||
}
|
||||
).OnlyShowIf(this._notCarFree),
|
||||
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import {LayerDefinition} from "./LayerDefinition";
|
||||
import {UIElement} from "../UI/UIElement";
|
||||
import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
||||
import Translation from "../UI/i18n/Translation";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
import Locale from "../UI/i18n/Locale";
|
||||
import {VariableUiElement} from "../UI/Base/VariableUIElement";
|
||||
import {UIEventSource} from "../UI/UIEventSource";
|
||||
import {OsmConnection, UserDetails} from "../Logic/Osm/OsmConnection";
|
||||
|
||||
/**
|
||||
* A layout is a collection of settings of the global view (thus: welcome text, title, selection of layers).
|
||||
|
@ -83,59 +77,5 @@ export class Layout {
|
|||
this.welcomeBackMessage = Translations.W(welcomeBackMessage);
|
||||
this.welcomeTail = Translations.W(welcomeTail);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export class WelcomeMessage extends UIElement {
|
||||
private readonly layout: Layout;
|
||||
private readonly userDetails: UIEventSource<UserDetails>;
|
||||
private languagePicker: UIElement;
|
||||
private osmConnection: OsmConnection;
|
||||
|
||||
private readonly description: UIElement;
|
||||
private readonly plzLogIn: UIElement;
|
||||
private readonly welcomeBack: UIElement;
|
||||
private readonly tail: UIElement;
|
||||
|
||||
|
||||
constructor(layout: Layout,
|
||||
languagePicker: UIElement,
|
||||
osmConnection: OsmConnection) {
|
||||
super(osmConnection?.userDetails);
|
||||
this.languagePicker = languagePicker;
|
||||
this.ListenTo(Locale.language);
|
||||
this.osmConnection = osmConnection;
|
||||
this.layout = layout;
|
||||
this.userDetails = osmConnection?.userDetails;
|
||||
|
||||
this.description = layout.welcomeMessage;
|
||||
this.plzLogIn = layout.gettingStartedPlzLogin;
|
||||
this.welcomeBack = layout.welcomeBackMessage;
|
||||
this.tail = layout.welcomeTail;
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
||||
let loginStatus = "";
|
||||
if (this.userDetails !== undefined) {
|
||||
loginStatus = (this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render();
|
||||
loginStatus = loginStatus + "<br/>"
|
||||
}
|
||||
|
||||
return "<span>" +
|
||||
this.description.Render() +
|
||||
"<br/>" +
|
||||
loginStatus +
|
||||
this.tail.Render() +
|
||||
"<br/>" +
|
||||
this.languagePicker.Render() +
|
||||
"</span>";
|
||||
}
|
||||
|
||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
||||
this.osmConnection?.registerActivateOsmAUthenticationClass()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ export class SmoothnessLayer extends LayerDefinition {
|
|||
this.name = "smoothness";
|
||||
this.minzoom = 17;
|
||||
this.overpassFilter = new Or([
|
||||
new Tag("highway","unclassified"),
|
||||
new Tag("highway", "residential"),
|
||||
new Tag("highway", "cycleway"),
|
||||
new Tag("highway", "footway"),
|
||||
|
|
|
@ -5,7 +5,7 @@ import {FixedUiElement} from "../UI/Base/FixedUiElement";
|
|||
import {SaveButton} from "../UI/SaveButton";
|
||||
import {Changes} from "../Logic/Osm/Changes";
|
||||
import {VariableUiElement} from "../UI/Base/VariableUIElement";
|
||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||
import {Dependencies, TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||
import {OnlyShowIfConstructor} from "./OnlyShowIf";
|
||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
||||
import {TextField} from "../UI/Input/TextField";
|
||||
|
@ -17,6 +17,7 @@ import Translations from "../UI/i18n/Translations";
|
|||
import Locale from "../UI/i18n/Locale";
|
||||
import * as EmailValidator from 'email-validator';
|
||||
import {parsePhoneNumberFromString} from 'libphonenumber-js'
|
||||
import {State} from "../State";
|
||||
|
||||
|
||||
export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
||||
|
@ -144,8 +145,8 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
|||
}
|
||||
|
||||
|
||||
construct(dependencies: { tags: UIEventSource<any>, changes: Changes }): TagDependantUIElement {
|
||||
return new TagRendering(dependencies.tags, dependencies.changes, this.options);
|
||||
construct(dependencies: Dependencies): TagDependantUIElement {
|
||||
return new TagRendering(dependencies.tags, this.options);
|
||||
}
|
||||
|
||||
IsKnown(properties: any): boolean {
|
||||
|
@ -161,7 +162,6 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
|||
class TagRendering extends UIElement implements TagDependantUIElement {
|
||||
|
||||
|
||||
private _userDetails: UIEventSource<UserDetails>;
|
||||
private _priority: number;
|
||||
|
||||
|
||||
|
@ -189,7 +189,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
|
||||
constructor(tags: UIEventSource<any>, changes: Changes, options: {
|
||||
constructor(tags: UIEventSource<any>, options: {
|
||||
priority?: number
|
||||
|
||||
question?: string | UIElement,
|
||||
|
@ -206,13 +206,12 @@ class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
}) {
|
||||
super(tags);
|
||||
this.ListenTo(Locale.language);
|
||||
const self = this;
|
||||
this.ListenTo(this._questionSkipped);
|
||||
this.ListenTo(this._editMode);
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
|
||||
this._userDetails = changes.login.userDetails;
|
||||
this.ListenTo(this._userDetails);
|
||||
|
||||
const self = this;
|
||||
|
||||
this._priority = options.priority ?? 0;
|
||||
this._tagsPreprocessor = function (properties) {
|
||||
|
@ -265,7 +264,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
const save = () => {
|
||||
const selection = self._questionElement.GetValue().data;
|
||||
if (selection) {
|
||||
changes.addTag(tags.data.id, selection);
|
||||
State.state.changes.addTag(tags.data.id, selection);
|
||||
}
|
||||
self._editMode.setData(false);
|
||||
}
|
||||
|
@ -521,7 +520,7 @@ class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
}
|
||||
const html = answer.Render();
|
||||
let editButton = "";
|
||||
if (this._userDetails.data.loggedIn && this._question !== undefined) {
|
||||
if (State.state.osmConnection.userDetails.data.loggedIn && this._question !== undefined) {
|
||||
editButton = this._editButton.Render();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,13 @@ import {Changes} from "../Logic/Osm/Changes";
|
|||
import {UIElement} from "../UI/UIElement";
|
||||
|
||||
|
||||
export interface Dependencies {
|
||||
tags: UIEventSource<any>
|
||||
}
|
||||
|
||||
export interface TagDependantUIElementConstructor {
|
||||
|
||||
construct(dependencies: {tags: UIEventSource<any>, changes: Changes}): TagDependantUIElement;
|
||||
construct(dependencies: Dependencies): TagDependantUIElement;
|
||||
IsKnown(properties: any): boolean;
|
||||
IsQuestioning(properties: any): boolean;
|
||||
Priority(): number;
|
||||
|
|
10
Helpers.ts
10
Helpers.ts
|
@ -1,5 +1,6 @@
|
|||
import {UIEventSource} from "./UI/UIEventSource";
|
||||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {State} from "./State";
|
||||
|
||||
export class Helpers {
|
||||
|
||||
|
@ -13,9 +14,11 @@ export class Helpers {
|
|||
}
|
||||
|
||||
|
||||
static SetupAutoSave(changes: Changes, millisTillChangesAreSaved: UIEventSource<number>, saveAfterXMillis: number) {
|
||||
|
||||
static SetupAutoSave() {
|
||||
|
||||
const changes = State.state.changes;
|
||||
const millisTillChangesAreSaved = State.state.secondsTillChangesAreSaved;
|
||||
const saveAfterXMillis = State.state.secondsTillChangesAreSaved.data * 1000;
|
||||
changes.pendingChangesES.addCallback(function () {
|
||||
|
||||
var c = changes.pendingChangesES.data;
|
||||
|
@ -53,7 +56,8 @@ export class Helpers {
|
|||
* -> Asks the user not to close. The 'not to close' dialog should profide enough time to upload
|
||||
* -> WHen uploading is done, the window is closed anyway
|
||||
*/
|
||||
static LastEffortSave(changes: Changes) {
|
||||
static LastEffortSave() {
|
||||
const changes = State.state.changes;
|
||||
window.addEventListener("beforeunload", function (e) {
|
||||
// Quickly save everyting!
|
||||
if (changes.pendingChangesES.data == 0) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Layout, WelcomeMessage} from "./Customizations/Layout";
|
||||
import {Layout} from "./Customizations/Layout";
|
||||
import Locale from "./UI/i18n/Locale";
|
||||
import Translations from "./UI/i18n/Translations";
|
||||
import {TabbedComponent} from "./UI/Base/TabbedComponent";
|
||||
|
@ -17,6 +17,8 @@ import {Preset} from "./UI/SimpleAddUI";
|
|||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import {Basemap} from "./Logic/Leaflet/Basemap";
|
||||
import {State} from "./State";
|
||||
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
||||
|
||||
export class InitUiElements {
|
||||
|
||||
|
@ -36,17 +38,16 @@ export class InitUiElements {
|
|||
}
|
||||
|
||||
|
||||
private static CreateWelcomePane(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap) {
|
||||
private static CreateWelcomePane() {
|
||||
|
||||
const welcome = new WelcomeMessage(layoutToUse,
|
||||
Locale.CreateLanguagePicker(layoutToUse, Translations.t.general.pickLanguage),
|
||||
osmConnection)
|
||||
const welcome = new WelcomeMessage()
|
||||
|
||||
const layoutToUse = State.state.layoutToUse.data;
|
||||
const fullOptions = new TabbedComponent([
|
||||
{header: `<img src='${layoutToUse.icon}'>`, content: welcome},
|
||||
{header: `<img src='${'./assets/osm-logo.svg'}'>`, content: Translations.t.general.openStreetMapIntro},
|
||||
{header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen(layoutToUse, bm.Location)},
|
||||
{header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen(layoutToUse.name, bm.Location)}
|
||||
{header: `<img src='${'./assets/share.svg'}'>`, content: new ShareScreen()},
|
||||
{header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen()}
|
||||
])
|
||||
|
||||
return fullOptions;
|
||||
|
@ -54,10 +55,9 @@ export class InitUiElements {
|
|||
}
|
||||
|
||||
|
||||
static InitWelcomeMessage(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap,
|
||||
fullScreenMessage: UIEventSource<UIElement>) {
|
||||
static InitWelcomeMessage() {
|
||||
|
||||
const fullOptions = this.CreateWelcomePane(layoutToUse, osmConnection, bm);
|
||||
const fullOptions = this.CreateWelcomePane();
|
||||
|
||||
const help = new FixedUiElement(`<div class='collapse-button-img'><img src='assets/help.svg' alt='help'></div>`);
|
||||
const close = new FixedUiElement(`<div class='collapse-button-img'><img src='assets/close.svg' alt='close'></div>`);
|
||||
|
@ -70,43 +70,39 @@ export class InitUiElements {
|
|||
, true
|
||||
).AttachTo("messagesbox");
|
||||
let dontCloseYet = true;
|
||||
bm.Location.addCallback(() => {
|
||||
if(dontCloseYet){
|
||||
dontCloseYet = false;
|
||||
const openedTime = new Date().getTime();
|
||||
State.state.locationControl.addCallback(() => {
|
||||
if (new Date().getTime() - openedTime < 15 * 1000) {
|
||||
// Don't autoclose the first 15 secs
|
||||
return;
|
||||
}
|
||||
checkbox.isEnabled.setData(false);
|
||||
})
|
||||
|
||||
|
||||
const fullOptions2 = this.CreateWelcomePane(layoutToUse, osmConnection, bm);
|
||||
fullScreenMessage.setData(fullOptions2)
|
||||
const fullOptions2 = this.CreateWelcomePane();
|
||||
State.state.fullScreenMessage.setData(fullOptions2)
|
||||
new FixedUiElement(`<div class='collapse-button-img' class="shadow"><img src='assets/help.svg' alt='help'></div>`).onClick(() => {
|
||||
fullScreenMessage.setData(fullOptions2)
|
||||
State.state.fullScreenMessage.setData(fullOptions2)
|
||||
}).AttachTo("help-button-mobile");
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
static InitLayers(layoutToUse: Layout, osmConnection: OsmConnection,
|
||||
changes: Changes,
|
||||
allElements: ElementStorage,
|
||||
bm: Basemap,
|
||||
fullScreenMessage: UIEventSource<UIElement>,
|
||||
selectedElement: UIEventSource<any>): {
|
||||
static InitLayers(): {
|
||||
minZoom: number
|
||||
flayers: FilteredLayer[],
|
||||
presets: Preset[]
|
||||
} {
|
||||
const addButtons:Preset[]
|
||||
const addButtons: Preset[]
|
||||
= [];
|
||||
|
||||
const flayers: FilteredLayer[] = []
|
||||
|
||||
let minZoom = 0;
|
||||
|
||||
for (const layer of layoutToUse.layers) {
|
||||
const state = State.state;
|
||||
for (const layer of state.layoutToUse.data.layers) {
|
||||
|
||||
const generateInfo = (tagsES, feature) => {
|
||||
|
||||
|
@ -115,14 +111,12 @@ export class InitUiElements {
|
|||
tagsES,
|
||||
layer.title,
|
||||
layer.elementsToShow,
|
||||
changes,
|
||||
osmConnection.userDetails
|
||||
)
|
||||
};
|
||||
|
||||
minZoom = Math.max(minZoom, layer.minzoom);
|
||||
|
||||
const flayer = FilteredLayer.fromDefinition(layer, bm, allElements, changes, osmConnection.userDetails, selectedElement, generateInfo);
|
||||
const flayer = FilteredLayer.fromDefinition(layer, generateInfo);
|
||||
|
||||
for (const preset of layer.presets ?? []) {
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import codegrid from "codegrid-js";
|
|||
import {Changes} from "./Osm/Changes";
|
||||
import {UserDetails} from "./Osm/OsmConnection";
|
||||
import {Basemap} from "./Leaflet/Basemap";
|
||||
import {State} from "../State";
|
||||
|
||||
/***
|
||||
* A filtered layer is a layer which offers a 'set-data' function
|
||||
|
@ -25,12 +26,10 @@ export class FilteredLayer {
|
|||
public readonly filters: TagsFilter;
|
||||
public readonly isDisplayed: UIEventSource<boolean> = new UIEventSource(true);
|
||||
public readonly layerDef: LayerDefinition;
|
||||
private readonly _map: Basemap;
|
||||
private readonly _maxAllowedOverlap: number;
|
||||
|
||||
private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize? : number[], popupAnchor?: number[], iconAnchor?:number[] } };
|
||||
|
||||
private readonly _storage: ElementStorage;
|
||||
|
||||
/** The featurecollection from overpass
|
||||
*/
|
||||
|
@ -43,22 +42,17 @@ export class FilteredLayer {
|
|||
* The leaflet layer object which should be removed on rerendering
|
||||
*/
|
||||
private _geolayer;
|
||||
private _selectedElement: UIEventSource<{ feature: any }>;
|
||||
private _showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement;
|
||||
|
||||
private static readonly grid = codegrid.CodeGrid();
|
||||
|
||||
constructor(
|
||||
layerDef: LayerDefinition,
|
||||
map: Basemap, storage: ElementStorage,
|
||||
changes: Changes,
|
||||
selectedElement: UIEventSource<any>,
|
||||
showOnPopup: ((tags: UIEventSource<any>, feature: any) => UIElement)
|
||||
) {
|
||||
this.layerDef = layerDef;
|
||||
|
||||
this._wayHandling = layerDef.wayHandling;
|
||||
this._selectedElement = selectedElement;
|
||||
this._showOnPopup = showOnPopup;
|
||||
this._style = layerDef.style;
|
||||
if (this._style === undefined) {
|
||||
|
@ -67,17 +61,16 @@ export class FilteredLayer {
|
|||
}
|
||||
}
|
||||
this.name = name;
|
||||
this._map = map;
|
||||
this.filters = layerDef.overpassFilter;
|
||||
this._storage = storage;
|
||||
this._maxAllowedOverlap = layerDef.maxAllowedOverlapPercentage;
|
||||
const self = this;
|
||||
this.isDisplayed.addCallback(function (isDisplayed) {
|
||||
const map = State.state.bm.map;
|
||||
if (self._geolayer !== undefined && self._geolayer !== null) {
|
||||
if (isDisplayed) {
|
||||
self._geolayer.addTo(self._map.map);
|
||||
self._geolayer.addTo(map);
|
||||
} else {
|
||||
self._map.map.removeLayer(self._geolayer);
|
||||
map.removeLayer(self._geolayer);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -85,15 +78,10 @@ export class FilteredLayer {
|
|||
|
||||
static fromDefinition(
|
||||
definition,
|
||||
basemap: Basemap, allElements: ElementStorage, changes: Changes, userDetails: UIEventSource<UserDetails>,
|
||||
selectedElement: UIEventSource<{feature: any}>,
|
||||
showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement):
|
||||
FilteredLayer {
|
||||
return new FilteredLayer(
|
||||
definition,
|
||||
basemap, allElements, changes,
|
||||
selectedElement,
|
||||
showOnPopup);
|
||||
definition, showOnPopup);
|
||||
|
||||
}
|
||||
|
||||
|
@ -170,7 +158,7 @@ export class FilteredLayer {
|
|||
let self = this;
|
||||
|
||||
if (this._geolayer !== undefined && this._geolayer !== null) {
|
||||
this._map.map.removeLayer(this._geolayer);
|
||||
State.state.bm.map.removeLayer(this._geolayer);
|
||||
}
|
||||
this._dataFromOverpass = data;
|
||||
const fusedFeatures = [];
|
||||
|
@ -227,7 +215,7 @@ export class FilteredLayer {
|
|||
icon: new L.icon(style.icon),
|
||||
});
|
||||
}
|
||||
let eventSource = self._storage.addOrGetElement(feature);
|
||||
let eventSource = State.state.allElements.addOrGetElement(feature);
|
||||
const uiElement = self._showOnPopup(eventSource, feature);
|
||||
const popup = L.popup({}, marker).setContent(uiElement.Render());
|
||||
marker.bindPopup(popup)
|
||||
|
@ -246,7 +234,7 @@ export class FilteredLayer {
|
|||
} else {
|
||||
self._geolayer.setStyle(function (feature) {
|
||||
const style = self._style(feature.properties);
|
||||
if (self._selectedElement.data?.feature === feature) {
|
||||
if (State.state.selectedElement.data?.feature === feature) {
|
||||
if (style.weight !== undefined) {
|
||||
style.weight = style.weight * 2;
|
||||
}else{
|
||||
|
@ -258,14 +246,14 @@ export class FilteredLayer {
|
|||
}
|
||||
}
|
||||
|
||||
let eventSource = self._storage.addOrGetElement(feature);
|
||||
let eventSource = State.state.allElements.addOrGetElement(feature);
|
||||
|
||||
|
||||
eventSource.addCallback(feature.updateStyle);
|
||||
|
||||
layer.on("click", function (e) {
|
||||
const previousFeature = self._selectedElement.data?.feature;
|
||||
self._selectedElement.setData({feature: feature});
|
||||
const previousFeature =State.state.selectedElement.data?.feature;
|
||||
State.state.selectedElement.setData({feature: feature});
|
||||
feature.updateStyle();
|
||||
previousFeature?.updateStyle();
|
||||
|
||||
|
@ -281,7 +269,7 @@ export class FilteredLayer {
|
|||
})
|
||||
.setContent(uiElement.Render())
|
||||
.setLatLng(e.latlng)
|
||||
.openOn(self._map.map);
|
||||
.openOn(State.state.bm.map);
|
||||
uiElement.Update();
|
||||
uiElement.Activate();
|
||||
L.DomEvent.stop(e); // Marks the event as consumed
|
||||
|
@ -290,7 +278,7 @@ export class FilteredLayer {
|
|||
});
|
||||
|
||||
if (this.isDisplayed.data) {
|
||||
this._geolayer.addTo(this._map.map);
|
||||
this._geolayer.addTo(State.state.bm.map);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import {SimpleImageElement} from "../UI/Image/SimpleImageElement";
|
|||
import {UIElement} from "../UI/UIElement";
|
||||
import {Changes} from "./Osm/Changes";
|
||||
import {ImgurImage} from "../UI/Image/ImgurImage";
|
||||
import {State} from "../State";
|
||||
|
||||
/**
|
||||
* There are multiple way to fetch images for an object
|
||||
|
@ -27,16 +28,13 @@ export class ImageSearcher extends UIEventSource<string[]> {
|
|||
private readonly _wdItem = new UIEventSource<string>("");
|
||||
private readonly _commons = new UIEventSource<string>("");
|
||||
private _activated: boolean = false;
|
||||
private _changes: Changes;
|
||||
public _deletedImages = new UIEventSource<string[]>([]);
|
||||
|
||||
|
||||
constructor(tags: UIEventSource<any>,
|
||||
changes: Changes) {
|
||||
constructor(tags: UIEventSource<any>) {
|
||||
super([]);
|
||||
|
||||
this._tags = tags;
|
||||
this._changes = changes;
|
||||
|
||||
const self = this;
|
||||
this._wdItem.addCallback(() => {
|
||||
|
@ -119,7 +117,7 @@ export class ImageSearcher extends UIEventSource<string[]> {
|
|||
return;
|
||||
}
|
||||
console.log("Deleting image...", key, " --> ", url);
|
||||
this._changes.addChange(this._tags.data.id, key, "");
|
||||
State.state.changes.addChange(this._tags.data.id, key, "");
|
||||
this._deletedImages.data.push(url);
|
||||
this._deletedImages.ping();
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import {FilteredLayer} from "./FilteredLayer";
|
|||
import {Bounds} from "./Bounds";
|
||||
import {Overpass} from "./Osm/Overpass";
|
||||
import {Basemap} from "./Leaflet/Basemap";
|
||||
import {State} from "../State";
|
||||
|
||||
export class LayerUpdater {
|
||||
private _map: Basemap;
|
||||
private _layers: FilteredLayer[];
|
||||
private widenFactor: number;
|
||||
|
||||
|
@ -26,12 +26,10 @@ export class LayerUpdater {
|
|||
* @param minzoom
|
||||
* @param layers
|
||||
*/
|
||||
constructor(map: Basemap,
|
||||
minzoom: number,
|
||||
constructor(minzoom: number,
|
||||
widenFactor: number,
|
||||
layers: FilteredLayer[]) {
|
||||
this.widenFactor = widenFactor;
|
||||
this._map = map;
|
||||
this._layers = layers;
|
||||
this._minzoom = minzoom;
|
||||
var filters: TagsFilter[] = [];
|
||||
|
@ -41,7 +39,7 @@ export class LayerUpdater {
|
|||
this._overpass = new Overpass(new Or(filters));
|
||||
|
||||
const self = this;
|
||||
map.Location.addCallback(function () {
|
||||
State.state.locationControl.addCallback(function () {
|
||||
self.update();
|
||||
});
|
||||
self.update();
|
||||
|
@ -67,9 +65,7 @@ export class LayerUpdater {
|
|||
renderLayers(rest);
|
||||
}, 50)
|
||||
}
|
||||
|
||||
renderLayers(this._layers);
|
||||
|
||||
}
|
||||
|
||||
private handleFail(reason: any) {
|
||||
|
@ -89,8 +85,8 @@ export class LayerUpdater {
|
|||
if (this.IsInBounds()) {
|
||||
return;
|
||||
}
|
||||
console.log("Zoom level: ",this._map.map.getZoom(), "Least needed zoom:", this._minzoom)
|
||||
if (this._map.map.getZoom() < this._minzoom || this._map.Location.data.zoom < this._minzoom) {
|
||||
console.log("Zoom level: ",State.state.bm.map.getZoom(), "Least needed zoom:", this._minzoom)
|
||||
if (State.state.bm.map.getZoom() < this._minzoom || State.state.bm.Location.data.zoom < this._minzoom) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -98,7 +94,7 @@ export class LayerUpdater {
|
|||
console.log("Still running a query, skip");
|
||||
}
|
||||
|
||||
const bounds = this._map.map.getBounds();
|
||||
const bounds = State.state.bm.map.getBounds();
|
||||
|
||||
const diff = this.widenFactor;
|
||||
|
||||
|
@ -131,7 +127,7 @@ export class LayerUpdater {
|
|||
}
|
||||
|
||||
|
||||
const b = this._map.map.getBounds();
|
||||
const b = State.state.bm.map.getBounds();
|
||||
if (b.getSouth() < this.previousBounds.south) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,20 @@
|
|||
import {Basemap} from "./Basemap";
|
||||
import L from "leaflet";
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import {Helpers} from "../../Helpers";
|
||||
import {State} from "../../State";
|
||||
|
||||
export class GeoLocationHandler extends UIElement {
|
||||
|
||||
currentLocation: UIEventSource<{
|
||||
latlng: number,
|
||||
accuracy: number
|
||||
}> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined);
|
||||
|
||||
private _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private _permission: UIEventSource<string> = new UIEventSource<string>("");
|
||||
private _map: Basemap;
|
||||
private _marker: any;
|
||||
private _hasLocation: UIEventSource<boolean>;
|
||||
|
||||
constructor(map: Basemap) {
|
||||
constructor() {
|
||||
super(undefined);
|
||||
this._map = map;
|
||||
this.ListenTo(this.currentLocation);
|
||||
this._hasLocation = State.state.currentGPSLocation.map((location) => location !== undefined);
|
||||
this.ListenTo(this._hasLocation);
|
||||
this.ListenTo(this._isActive);
|
||||
this.ListenTo(this._permission);
|
||||
|
||||
|
@ -29,13 +24,13 @@ export class GeoLocationHandler extends UIElement {
|
|||
function onAccuratePositionProgress(e) {
|
||||
console.log(e.accuracy);
|
||||
console.log(e.latlng);
|
||||
self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
}
|
||||
|
||||
function onAccuratePositionFound(e) {
|
||||
console.log(e.accuracy);
|
||||
console.log(e.latlng);
|
||||
self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
}
|
||||
|
||||
function onAccuratePositionError(e) {
|
||||
|
@ -43,9 +38,10 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
}
|
||||
|
||||
map.map.on('accuratepositionprogress', onAccuratePositionProgress);
|
||||
map.map.on('accuratepositionfound', onAccuratePositionFound);
|
||||
map.map.on('accuratepositionerror', onAccuratePositionError);
|
||||
const map = State.state.bm.map;
|
||||
map.on('accuratepositionprogress', onAccuratePositionProgress);
|
||||
map.on('accuratepositionfound', onAccuratePositionFound);
|
||||
map.on('accuratepositionerror', onAccuratePositionError);
|
||||
|
||||
|
||||
const icon = L.icon(
|
||||
|
@ -55,12 +51,12 @@ export class GeoLocationHandler extends UIElement {
|
|||
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
|
||||
})
|
||||
|
||||
this.currentLocation.addCallback((location) => {
|
||||
State.state.currentGPSLocation.addCallback((location) => {
|
||||
const newMarker = L.marker(location.latlng, {icon: icon});
|
||||
newMarker.addTo(map.map);
|
||||
|
||||
if (self._marker !== undefined) {
|
||||
map.map.removeLayer(self._marker);
|
||||
map.removeLayer(self._marker);
|
||||
}
|
||||
self._marker = newMarker;
|
||||
});
|
||||
|
@ -81,7 +77,7 @@ export class GeoLocationHandler extends UIElement {
|
|||
}
|
||||
|
||||
InnerRender(): string {
|
||||
if (this.currentLocation.data) {
|
||||
if (this._hasLocation.data) {
|
||||
return "<img src='assets/crosshair-blue.png' alt='locate me'>";
|
||||
}
|
||||
if (this._isActive.data) {
|
||||
|
@ -94,17 +90,17 @@ export class GeoLocationHandler extends UIElement {
|
|||
|
||||
private StartGeolocating() {
|
||||
const self = this;
|
||||
|
||||
const map = State.state.bm.map;
|
||||
if (self._permission.data === "denied") {
|
||||
return "";
|
||||
}
|
||||
if (self.currentLocation.data !== undefined) {
|
||||
self._map.map.flyTo(self.currentLocation.data.latlng, 18);
|
||||
if (State.state.currentGPSLocation.data !== undefined) {
|
||||
map.flyTo(State.state.currentGPSLocation.data.latlng, 18);
|
||||
}
|
||||
|
||||
|
||||
console.log("Searching location using GPS")
|
||||
self._map.map.findAccuratePosition({
|
||||
map.findAccuratePosition({
|
||||
maxWait: 10000, // defaults to 10000
|
||||
desiredAccuracy: 50 // defaults to 20
|
||||
});
|
||||
|
@ -114,12 +110,12 @@ export class GeoLocationHandler extends UIElement {
|
|||
self._isActive.setData(true);
|
||||
Helpers.DoEvery(60000, () => {
|
||||
|
||||
if(document.visibilityState !== "visible"){
|
||||
if (document.visibilityState !== "visible") {
|
||||
console.log("Not starting gps: document not visible")
|
||||
return;
|
||||
}
|
||||
|
||||
self._map.map.findAccuratePosition({
|
||||
map.findAccuratePosition({
|
||||
maxWait: 10000, // defaults to 10000
|
||||
desiredAccuracy: 50 // defaults to 20
|
||||
});
|
||||
|
|
|
@ -2,29 +2,23 @@ import {Basemap} from "./Basemap";
|
|||
import L from "leaflet";
|
||||
import {UIEventSource} from "../../UI/UIEventSource";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
import {State} from "../../State";
|
||||
|
||||
/**
|
||||
* The stray-click-hanlders adds a marker to the map if no feature was clicked.
|
||||
* Shows the given uiToShow-element in the messagebox
|
||||
*/
|
||||
export class StrayClickHandler {
|
||||
private _basemap: Basemap;
|
||||
private _lastMarker;
|
||||
private _fullScreenMessage: UIEventSource<UIElement>;
|
||||
private _uiToShow: (() => UIElement);
|
||||
|
||||
constructor(
|
||||
basemap: Basemap,
|
||||
selectElement: UIEventSource<{ feature: any }>,
|
||||
fullScreenMessage: UIEventSource<UIElement>,
|
||||
uiToShow: (() => UIElement)) {
|
||||
this._basemap = basemap;
|
||||
this._fullScreenMessage = fullScreenMessage;
|
||||
this._uiToShow = uiToShow;
|
||||
const self = this;
|
||||
const map = basemap.map;
|
||||
basemap.LastClickLocation.addCallback(function (lastClick) {
|
||||
selectElement.setData(undefined);
|
||||
const map = State.state.bm.map;
|
||||
State.state.bm.LastClickLocation.addCallback(function (lastClick) {
|
||||
State.state.selectedElement.setData(undefined);
|
||||
|
||||
if (self._lastMarker !== undefined) {
|
||||
map.removeLayer(self._lastMarker);
|
||||
|
@ -32,9 +26,9 @@ export class StrayClickHandler {
|
|||
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
|
||||
icon: L.icon({
|
||||
iconUrl: "./assets/add.svg",
|
||||
iconSize: [50,50],
|
||||
iconAnchor: [25,50],
|
||||
popupAnchor: [0,-45]
|
||||
iconSize: [50, 50],
|
||||
iconAnchor: [25, 50],
|
||||
popupAnchor: [0, -45]
|
||||
})
|
||||
});
|
||||
const uiElement = uiToShow();
|
||||
|
@ -45,13 +39,13 @@ export class StrayClickHandler {
|
|||
self._lastMarker.bindPopup(popup).openPopup();
|
||||
|
||||
self._lastMarker.on("click", () => {
|
||||
fullScreenMessage.setData(self._uiToShow());
|
||||
State.state.fullScreenMessage.setData(self._uiToShow());
|
||||
});
|
||||
uiElement.Update();
|
||||
uiElement.Activate();
|
||||
});
|
||||
|
||||
selectElement.addCallback(() => {
|
||||
State.state.selectedElement.addCallback(() => {
|
||||
if (self._lastMarker !== undefined) {
|
||||
map.removeLayer(self._lastMarker);
|
||||
this._lastMarker = undefined;
|
||||
|
|
|
@ -7,14 +7,12 @@ import {OsmConnection} from "./OsmConnection";
|
|||
import {OsmNode, OsmObject} from "./OsmObject";
|
||||
import {And, Tag, TagsFilter} from "../TagsFilter";
|
||||
import {ElementStorage} from "../ElementStorage";
|
||||
import {State} from "../../State";
|
||||
|
||||
export class Changes {
|
||||
|
||||
private static _nextId = -1; // New assined ID's are negative
|
||||
|
||||
public readonly login: OsmConnection;
|
||||
public readonly _allElements: ElementStorage;
|
||||
|
||||
private _pendingChanges: { elementId: string, key: string, value: string }[] = []; // Gets reset on uploadAll
|
||||
private newElements: OsmObject[] = []; // Gets reset on uploadAll
|
||||
|
||||
|
@ -27,8 +25,6 @@ export class Changes {
|
|||
login: OsmConnection,
|
||||
allElements: ElementStorage) {
|
||||
this._changesetComment = changesetComment;
|
||||
this.login = login;
|
||||
this._allElements = allElements;
|
||||
}
|
||||
|
||||
addTag(elementId: string, tagsFilter : TagsFilter){
|
||||
|
@ -66,7 +62,7 @@ console.log("Received change",key, value)
|
|||
return;
|
||||
}
|
||||
|
||||
const eventSource = this._allElements.getElement(elementId);
|
||||
const eventSource = State.state.allElements.getElement(elementId);
|
||||
|
||||
eventSource.data[key] = value;
|
||||
eventSource.ping();
|
||||
|
@ -104,7 +100,7 @@ console.log("Received change",key, value)
|
|||
]
|
||||
}
|
||||
}
|
||||
this._allElements.addOrGetElement(geojson);
|
||||
State.state.allElements.addOrGetElement(geojson);
|
||||
|
||||
// The basictags are COPIED, the id is included in the properties
|
||||
// The tags are not yet written into the OsmObject, but this is applied onto a
|
||||
|
@ -208,16 +204,16 @@ console.log("Received change",key, value)
|
|||
for (const oldId in idMapping) {
|
||||
const newId = idMapping[oldId];
|
||||
|
||||
const element = self._allElements.getElement(oldId);
|
||||
const element = State.state.allElements.getElement(oldId);
|
||||
element.data.id = newId;
|
||||
self._allElements.addElementById(newId, element);
|
||||
State.state.allElements.addElementById(newId, element);
|
||||
element.ping();
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Beginning upload...");
|
||||
// At last, we build the changeset and upload
|
||||
self.login.UploadChangeset(self._changesetComment,
|
||||
State.state.osmConnection.UploadChangeset(self._changesetComment,
|
||||
function (csId) {
|
||||
|
||||
let modifications = "";
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import {Basemap} from "../Leaflet/Basemap";
|
||||
import $ from "jquery"
|
||||
import {State} from "../../State";
|
||||
export class Geocoding {
|
||||
|
||||
private static readonly host = "https://nominatim.openstreetmap.org/search?";
|
||||
|
||||
static Search(query: string,
|
||||
basemap: Basemap,
|
||||
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void),
|
||||
onFail: (() => void)) {
|
||||
const b = basemap.map.getBounds();
|
||||
const b = State.state.bm.map.getBounds();
|
||||
console.log(b);
|
||||
$.getJSON(
|
||||
Geocoding.host + "format=json&limit=1&viewbox=" +
|
||||
|
|
|
@ -10,7 +10,6 @@ export class UserDetails {
|
|||
public img: string;
|
||||
public unreadMessages = 0;
|
||||
public totalMessages = 0;
|
||||
public osmConnection: OsmConnection;
|
||||
public dryRun: boolean;
|
||||
home: { lon: number; lat: number };
|
||||
}
|
||||
|
@ -23,24 +22,43 @@ export class OsmConnection {
|
|||
|
||||
constructor(dryRun: boolean, oauth_token: UIEventSource<string>) {
|
||||
|
||||
let pwaStandAloneMode = false;
|
||||
try {
|
||||
|
||||
if (window.matchMedia('(display-mode: standalone)').matches || window.matchMedia('(display-mode: fullscreen)').matches) {
|
||||
pwaStandAloneMode = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Detecting standalone mode failed", e, ". Assuming in browser and not worrying furhter")
|
||||
}
|
||||
|
||||
|
||||
if (pwaStandAloneMode) {
|
||||
// In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway...
|
||||
this.auth = new osmAuth({
|
||||
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
|
||||
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
|
||||
singlepage: false,
|
||||
auto: true
|
||||
});
|
||||
} else {
|
||||
|
||||
this.auth = new osmAuth({
|
||||
oauth_consumer_key: 'hivV7ec2o49Two8g9h8Is1VIiVOgxQ1iYexCbvem',
|
||||
oauth_secret: 'wDBRTCem0vxD7txrg1y6p5r8nvmz8tAhET7zDASI',
|
||||
singlepage: true,
|
||||
landing: window.location.href,
|
||||
auto: true // show a login form if the user is not authenticated and
|
||||
// you try to do a call
|
||||
|
||||
auto: true
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.userDetails = new UIEventSource<UserDetails>(new UserDetails());
|
||||
this.userDetails.data.osmConnection = this;
|
||||
this.userDetails.data.dryRun = dryRun;
|
||||
this._dryRun = dryRun;
|
||||
|
||||
|
||||
if(oauth_token.data !== undefined){
|
||||
if (oauth_token.data !== undefined) {
|
||||
console.log(oauth_token.data)
|
||||
const self = this;
|
||||
this.auth.bootstrapToken(oauth_token.data,
|
||||
|
|
|
@ -6,27 +6,19 @@ import {UIEventSource} from "../../UI/UIEventSource";
|
|||
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
|
||||
import {UserDetails} from "./OsmConnection";
|
||||
import {SlideShow} from "../../UI/SlideShow";
|
||||
import {State} from "../../State";
|
||||
|
||||
export class OsmImageUploadHandler {
|
||||
private _tags: UIEventSource<any>;
|
||||
private _changeHandler: Changes;
|
||||
private _userdetails: UIEventSource<UserDetails>;
|
||||
private _slideShow: SlideShow;
|
||||
private _preferedLicense: UIEventSource<string>;
|
||||
|
||||
constructor(tags: UIEventSource<any>,
|
||||
userdetails: UIEventSource<UserDetails>,
|
||||
preferedLicense: UIEventSource<string>,
|
||||
changeHandler: Changes,
|
||||
slideShow : SlideShow
|
||||
) {
|
||||
this._slideShow = slideShow; // To move the slideshow (if any) to the last, just added element
|
||||
if (tags === undefined || userdetails === undefined || changeHandler === undefined) {
|
||||
throw "Something is undefined"
|
||||
}
|
||||
this._tags = tags;
|
||||
this._changeHandler = changeHandler;
|
||||
this._userdetails = userdetails;
|
||||
this._preferedLicense = preferedLicense;
|
||||
}
|
||||
|
||||
|
@ -36,14 +28,14 @@ export class OsmImageUploadHandler {
|
|||
|
||||
const title = tags.name ?? "Unknown area";
|
||||
const description = [
|
||||
"author:" + this._userdetails.data.name,
|
||||
"author:" + State.state.osmConnection.userDetails.data.name,
|
||||
"license:" + license,
|
||||
"wikidata:" + tags.wikidata,
|
||||
"osmid:" + tags.id,
|
||||
"name:" + tags.name
|
||||
].join("\n");
|
||||
|
||||
const changes = this._changeHandler;
|
||||
const changes = State.state.changes;
|
||||
return {
|
||||
title: title,
|
||||
description: description,
|
||||
|
@ -73,7 +65,6 @@ export class OsmImageUploadHandler {
|
|||
getUI(): ImageUploadFlow {
|
||||
const self = this;
|
||||
return new ImageUploadFlow(
|
||||
this._userdetails,
|
||||
this._preferedLicense,
|
||||
function (license) {
|
||||
return self.generateOptions(license)
|
||||
|
|
|
@ -51,7 +51,10 @@ export class QueryParameters {
|
|||
}
|
||||
|
||||
public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> {
|
||||
if (deflt !== undefined) {
|
||||
console.log(key, "-->", deflt)
|
||||
QueryParameters.defaults[key] = deflt;
|
||||
}
|
||||
if (QueryParameters.knownSources[key] !== undefined) {
|
||||
return QueryParameters.knownSources[key];
|
||||
}
|
||||
|
|
134
State.ts
Normal file
134
State.ts
Normal 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);
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -2,75 +2,60 @@ import {UIElement} from "./UIElement";
|
|||
import {UIEventSource} from "./UIEventSource";
|
||||
import {OsmConnection} from "../Logic/Osm/OsmConnection";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {State} from "../State";
|
||||
|
||||
export class CenterMessageBox extends UIElement {
|
||||
|
||||
private readonly _location: UIEventSource<{ zoom: number }>;
|
||||
private readonly _zoomInMore = new UIEventSource<boolean>(true);
|
||||
private readonly _centermessage: UIEventSource<string>;
|
||||
private readonly _osmConnection: OsmConnection;
|
||||
private readonly _queryRunning: UIEventSource<boolean>;
|
||||
private startZoom: number;
|
||||
|
||||
constructor(
|
||||
startZoom: number,
|
||||
centermessage: UIEventSource<string>,
|
||||
osmConnection: OsmConnection,
|
||||
location: UIEventSource<{ zoom: number }>,
|
||||
queryRunning: UIEventSource<boolean>
|
||||
) {
|
||||
super(centermessage);
|
||||
super(State.state.centerMessage);
|
||||
this.startZoom = startZoom;
|
||||
|
||||
this._centermessage = centermessage;
|
||||
this._location = location;
|
||||
this._osmConnection = osmConnection;
|
||||
this._queryRunning = queryRunning;
|
||||
this.ListenTo(State.state.locationControl);
|
||||
this.ListenTo(queryRunning);
|
||||
|
||||
this._queryRunning = queryRunning;
|
||||
|
||||
const self = this;
|
||||
location.addCallback(function () {
|
||||
self._zoomInMore.setData(location.data.zoom < startZoom);
|
||||
});
|
||||
this.ListenTo(this._zoomInMore);
|
||||
|
||||
}
|
||||
|
||||
private prep(): { innerHtml: string, done: boolean } {
|
||||
if (State.state.centerMessage.data != "") {
|
||||
return {innerHtml: State.state.centerMessage.data, done: false};
|
||||
}
|
||||
if (this._queryRunning.data) {
|
||||
return {innerHtml: Translations.t.centerMessage.loadingData.Render(), done: false};
|
||||
} else if (State.state.locationControl.data.zoom < this.startZoom) {
|
||||
return {innerHtml: Translations.t.centerMessage.zoomIn.Render(), done: false};
|
||||
} else {
|
||||
return {innerHtml: Translations.t.centerMessage.ready.Render(), done: true};
|
||||
}
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
||||
if (this._centermessage.data != "") {
|
||||
return this._centermessage.data;
|
||||
}
|
||||
if (this._queryRunning.data) {
|
||||
return Translations.t.centerMessage.loadingData.Render();
|
||||
} else if (this._zoomInMore.data) {
|
||||
return Translations.t.centerMessage.zoomIn.Render();
|
||||
}
|
||||
return Translations.t.centerMessage.ready.Render();
|
||||
return this.prep().innerHtml;
|
||||
}
|
||||
|
||||
|
||||
private ShouldShowSomething() : boolean{
|
||||
if (this._queryRunning.data) {
|
||||
return true;
|
||||
}
|
||||
return this._zoomInMore.data;
|
||||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
const pstyle = htmlElement.parentElement.style;
|
||||
if (this._centermessage.data != "") {
|
||||
if (State.state.centerMessage.data != "") {
|
||||
pstyle.opacity = "1";
|
||||
pstyle.pointerEvents = "all";
|
||||
this._osmConnection.registerActivateOsmAUthenticationClass();
|
||||
State.state.osmConnection.registerActivateOsmAUthenticationClass();
|
||||
return;
|
||||
}
|
||||
pstyle.pointerEvents = "none";
|
||||
|
||||
|
||||
if (this.ShouldShowSomething()) {
|
||||
pstyle.opacity = "0.5";
|
||||
} else {
|
||||
if (this.prep().done) {
|
||||
pstyle.opacity = "0";
|
||||
} else {
|
||||
pstyle.opacity = "0.5";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import Translations from "./i18n/Translations";
|
|||
import {Changes} from "../Logic/Osm/Changes";
|
||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import {State} from "../State";
|
||||
|
||||
export class FeatureInfoBox extends UIElement {
|
||||
|
||||
|
@ -23,14 +24,11 @@ export class FeatureInfoBox extends UIElement {
|
|||
*/
|
||||
private _tagsES: UIEventSource<any>;
|
||||
private _changes: Changes;
|
||||
private _userDetails: UIEventSource<UserDetails>;
|
||||
|
||||
|
||||
private _title: UIElement;
|
||||
private _osmLink: UIElement;
|
||||
private _wikipedialink: UIElement;
|
||||
|
||||
|
||||
private _infoboxes: TagDependantUIElement[];
|
||||
|
||||
private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone();
|
||||
|
@ -41,15 +39,11 @@ export class FeatureInfoBox extends UIElement {
|
|||
tagsES: UIEventSource<any>,
|
||||
title: TagRenderingOptions | UIElement | string,
|
||||
elementsToShow: TagDependantUIElementConstructor[],
|
||||
changes: Changes,
|
||||
userDetails: UIEventSource<UserDetails>
|
||||
) {
|
||||
super(tagsES);
|
||||
this._feature = feature;
|
||||
this._tagsES = tagsES;
|
||||
this._changes = changes;
|
||||
this._userDetails = userDetails;
|
||||
this.ListenTo(userDetails);
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
|
||||
const deps = {tags: this._tagsES, changes: this._changes}
|
||||
|
||||
|
@ -112,7 +106,7 @@ export class FeatureInfoBox extends UIElement {
|
|||
|
||||
let questionsHtml = "";
|
||||
|
||||
if (this._userDetails.data.loggedIn && questions.length > 0) {
|
||||
if (State.state.osmConnection.userDetails.data.loggedIn && questions.length > 0) {
|
||||
// We select the most important question and render that one
|
||||
let mostImportantQuestion;
|
||||
let score = -1000;
|
||||
|
|
|
@ -2,6 +2,7 @@ import {UIEventSource} from "./UIEventSource";
|
|||
import {UIElement} from "./UIElement";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {State} from "../State";
|
||||
|
||||
/**
|
||||
* Handles the full screen popup on mobile
|
||||
|
@ -10,17 +11,20 @@ export class FullScreenMessageBoxHandler {
|
|||
|
||||
private _uielement: UIEventSource<UIElement>;
|
||||
|
||||
constructor(uielement: UIEventSource<UIElement>,
|
||||
onClear: (() => void)) {
|
||||
this._uielement = uielement;
|
||||
this.listenTo(uielement);
|
||||
constructor(onClear: (() => void)) {
|
||||
this._uielement = State.state.fullScreenMessage;
|
||||
const self = this;
|
||||
this._uielement.addCallback(function () {
|
||||
self.update();
|
||||
});
|
||||
|
||||
this.update();
|
||||
|
||||
if (window !== undefined) {
|
||||
window.onhashchange = function () {
|
||||
if (location.hash === "") {
|
||||
// No more element: back to the map!
|
||||
uielement.setData(undefined);
|
||||
self._uielement.setData(undefined);
|
||||
onClear();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +32,7 @@ export class FullScreenMessageBoxHandler {
|
|||
|
||||
Translations.t.general.returnToTheMap
|
||||
.onClick(() => {
|
||||
uielement.setData(undefined);
|
||||
self._uielement.setData(undefined);
|
||||
onClear();
|
||||
})
|
||||
.AttachTo("to-the-map");
|
||||
|
@ -36,13 +40,6 @@ export class FullScreenMessageBoxHandler {
|
|||
|
||||
}
|
||||
|
||||
listenTo(uiEventSource: UIEventSource<any>) {
|
||||
const self = this;
|
||||
uiEventSource.addCallback(function () {
|
||||
self.update();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
update() {
|
||||
const wrapper = document.getElementById("messagesboxmobilewrapper");
|
||||
|
|
|
@ -3,12 +3,15 @@ import {ImageSearcher} from "../../Logic/ImageSearcher";
|
|||
import {UIEventSource} from "../UIEventSource";
|
||||
import {SlideShow} from "../SlideShow";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {VerticalCombine} from "../Base/VerticalCombine";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {ConfirmDialog} from "../ConfirmDialog";
|
||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
|
||||
import {
|
||||
Dependencies,
|
||||
TagDependantUIElement,
|
||||
TagDependantUIElementConstructor
|
||||
} from "../../Customizations/UIElementConstructor";
|
||||
import {Changes} from "../../Logic/Osm/Changes";
|
||||
import {UserDetails} from "../../Logic/Osm/OsmConnection";
|
||||
import {State} from "../../State";
|
||||
|
||||
export class ImageCarouselConstructor implements TagDependantUIElementConstructor{
|
||||
IsKnown(properties: any): boolean {
|
||||
|
@ -23,8 +26,8 @@ export class ImageCarouselConstructor implements TagDependantUIElementConstructo
|
|||
return 0;
|
||||
}
|
||||
|
||||
construct(dependencies: { tags: UIEventSource<any>, changes: Changes }): TagDependantUIElement {
|
||||
return new ImageCarousel(dependencies.tags, dependencies.changes);
|
||||
construct(dependencies: Dependencies): TagDependantUIElement {
|
||||
return new ImageCarousel(dependencies.tags);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,14 +44,11 @@ export class ImageCarousel extends TagDependantUIElement {
|
|||
private readonly _deleteButton: UIElement;
|
||||
private readonly _isDeleted: UIElement;
|
||||
|
||||
private readonly _userDetails : UIEventSource<UserDetails>;
|
||||
|
||||
constructor(tags: UIEventSource<any>, changes: Changes) {
|
||||
constructor(tags: UIEventSource<any>) {
|
||||
super(tags);
|
||||
this._userDetails = changes.login.userDetails;
|
||||
|
||||
const self = this;
|
||||
this.searcher = new ImageSearcher(tags, changes);
|
||||
this.searcher = new ImageSearcher(tags);
|
||||
|
||||
this._uiElements = this.searcher.map((imageURLS: string[]) => {
|
||||
const uiElements: UIElement[] = [];
|
||||
|
@ -65,11 +65,11 @@ export class ImageCarousel extends TagDependantUIElement {
|
|||
|
||||
|
||||
const showDeleteButton = this.slideshow._currentSlide.map((i) => {
|
||||
if(!self._userDetails.data.loggedIn){
|
||||
if(!State.state.osmConnection.userDetails.data.loggedIn){
|
||||
return false;
|
||||
}
|
||||
return self.searcher.IsDeletable(self.searcher.data[i]);
|
||||
}, [this.searcher, this._userDetails]);
|
||||
}, [this.searcher, State.state.osmConnection.userDetails]);
|
||||
this.slideshow._currentSlide.addCallback(() => {
|
||||
showDeleteButton.ping(); // This pings the showDeleteButton, which indicates that it has to hide it's subbuttons
|
||||
})
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
|
||||
import {
|
||||
Dependencies,
|
||||
TagDependantUIElement,
|
||||
TagDependantUIElementConstructor
|
||||
} from "../../Customizations/UIElementConstructor";
|
||||
import {ImageCarousel} from "./ImageCarousel";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {ImageUploadFlow} from "../ImageUploadFlow";
|
||||
import {Changes} from "../../Logic/Osm/Changes";
|
||||
import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler";
|
||||
import {State} from "../../State";
|
||||
|
||||
export class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
|
||||
IsKnown(properties: any): boolean {
|
||||
|
@ -27,16 +30,13 @@ class ImageCarouselWithUpload extends TagDependantUIElement {
|
|||
private _imageElement: ImageCarousel;
|
||||
private _pictureUploader: ImageUploadFlow;
|
||||
|
||||
constructor(dependencies: {tags: UIEventSource<any>, changes: Changes}) {
|
||||
constructor(dependencies: Dependencies) {
|
||||
super(dependencies.tags);
|
||||
const tags = dependencies.tags;
|
||||
const changes = dependencies.changes;
|
||||
this._imageElement = new ImageCarousel(tags, changes);
|
||||
const userDetails = changes.login.userDetails;
|
||||
const license = changes.login.GetPreference( "pictures-license");
|
||||
this._pictureUploader = new OsmImageUploadHandler(tags,
|
||||
userDetails, license,
|
||||
changes, this._imageElement.slideshow).getUI();
|
||||
this._imageElement = new ImageCarousel(tags);
|
||||
const userDetails = State.state.osmConnection.userDetails;
|
||||
const license = State.state.osmConnection.GetPreference( "pictures-license");
|
||||
this._pictureUploader = new OsmImageUploadHandler(tags, license, this._imageElement.slideshow).getUI();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import Translations from "./i18n/Translations";
|
|||
import {fail} from "assert";
|
||||
import Combine from "./Base/Combine";
|
||||
import {VerticalCombine} from "./Base/VerticalCombine";
|
||||
import {State} from "../State";
|
||||
|
||||
export class ImageUploadFlow extends UIElement {
|
||||
private _licensePicker: UIElement;
|
||||
|
@ -17,10 +18,8 @@ export class ImageUploadFlow extends UIElement {
|
|||
private _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) };
|
||||
private _userdetails: UIEventSource<UserDetails>;
|
||||
|
||||
constructor(
|
||||
userInfo: UIEventSource<UserDetails>,
|
||||
preferedLicense: UIEventSource<string>,
|
||||
uploadOptions: ((license: string) =>
|
||||
{
|
||||
|
@ -30,9 +29,7 @@ export class ImageUploadFlow extends UIElement {
|
|||
allDone: (() => void)
|
||||
})
|
||||
) {
|
||||
super(undefined);
|
||||
this._userdetails = userInfo;
|
||||
this.ListenTo(userInfo);
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this._uploadOptions = uploadOptions;
|
||||
this.ListenTo(this._isUploading);
|
||||
this.ListenTo(this._didFail);
|
||||
|
@ -56,11 +53,11 @@ export class ImageUploadFlow extends UIElement {
|
|||
InnerRender(): string {
|
||||
|
||||
const t = Translations.t.image;
|
||||
if (this._userdetails === undefined) {
|
||||
if (State.state.osmConnection.userDetails === undefined) {
|
||||
return ""; // No user details -> logging in is probably disabled or smthing
|
||||
}
|
||||
|
||||
if (!this._userdetails.data.loggedIn) {
|
||||
if (!State.state.osmConnection.userDetails.data.loggedIn) {
|
||||
return `<div class='activate-osm-authentication'>${t.pleaseLogin.Render()}</div>`;
|
||||
}
|
||||
|
||||
|
@ -79,6 +76,16 @@ export class ImageUploadFlow extends UIElement {
|
|||
currentState.push(t.uploadDone)
|
||||
}
|
||||
|
||||
let currentStateHtml = "";
|
||||
if (currentState.length > 0) {
|
||||
currentStateHtml = new VerticalCombine(currentState).Render();
|
||||
if (!this._allDone.data) {
|
||||
currentStateHtml = "<span class='alert'>" +
|
||||
currentStateHtml +
|
||||
"</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return "" +
|
||||
"<div class='imageflow'>" +
|
||||
|
||||
|
@ -89,9 +96,9 @@ export class ImageUploadFlow extends UIElement {
|
|||
`<span class='imageflow-add-picture'>${Translations.t.image.addPicture.R()}</span>` +
|
||||
"<div class='break'></div>" +
|
||||
"</div>" +
|
||||
currentStateHtml +
|
||||
Translations.t.image.respectPrivacy.Render() + "<br/>" +
|
||||
this._licensePicker.Render() + "<br/>" +
|
||||
new VerticalCombine(currentState).Render() +
|
||||
"</label>" +
|
||||
"<form id='fileselector-form-" + this.id + "'>" +
|
||||
"<input id='fileselector-" + this.id + "' " +
|
||||
|
@ -106,11 +113,11 @@ export class ImageUploadFlow extends UIElement {
|
|||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
super.InnerUpdate(htmlElement);
|
||||
const user = this._userdetails.data;
|
||||
const user = State.state.osmConnection.userDetails.data;
|
||||
|
||||
htmlElement.onclick = function () {
|
||||
if (!user.loggedIn) {
|
||||
user.osmConnection.AttemptLogin();
|
||||
State.state.osmConnection.AttemptLogin();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,16 +9,13 @@ import {UIEventSource} from "./UIEventSource";
|
|||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import Combine from "./Base/Combine";
|
||||
import {SubtleButton} from "./Base/SubtleButton";
|
||||
import {State} from "../State";
|
||||
|
||||
|
||||
export class MoreScreen extends UIElement {
|
||||
private currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>;
|
||||
private currentLayout: string;
|
||||
|
||||
constructor(currentLayout: string, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) {
|
||||
super(currentLocation);
|
||||
this.currentLayout = currentLayout;
|
||||
this.currentLocation = currentLocation;
|
||||
constructor() {
|
||||
super(State.state.locationControl);
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
@ -30,12 +27,13 @@ export class MoreScreen extends UIElement {
|
|||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
if (layout.name === this.currentLayout) {
|
||||
if (layout.name === State.state.layoutToUse.data.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentLocation = State.state.locationControl.data;
|
||||
const linkText =
|
||||
`https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${this.currentLocation.data.zoom}&lat=${this.currentLocation.data.lat}&lon=${this.currentLocation.data.lon}`
|
||||
`https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${currentLocation.zoom}&lat=${currentLocation.lat}&lon=${currentLocation.lon}`
|
||||
const link =
|
||||
new SubtleButton(layout.icon,
|
||||
new Combine([
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import {UIEventSource} from "./UIEventSource";
|
||||
import {Changes} from "../Logic/Osm/Changes";
|
||||
import {State} from "../State";
|
||||
|
||||
export class PendingChanges extends UIElement {
|
||||
private _pendingChangesCount: UIEventSource<number>;
|
||||
private _countdown: UIEventSource<number>;
|
||||
private _isSaving: UIEventSource<boolean>;
|
||||
|
||||
constructor(changes: Changes,
|
||||
countdown: UIEventSource<number>) {
|
||||
super(changes.pendingChangesES);
|
||||
this.ListenTo(changes.isSaving);
|
||||
this.ListenTo(countdown);
|
||||
this._pendingChangesCount = changes.pendingChangesES;
|
||||
this._countdown = countdown;
|
||||
this._isSaving = changes.isSaving;
|
||||
constructor() {
|
||||
super(State.state.changes.pendingChangesES);
|
||||
this.ListenTo(State.state.changes.isSaving);
|
||||
this.ListenTo(State.state.secondsTillChangesAreSaved);
|
||||
this._pendingChangesCount = State.state.changes.pendingChangesES;
|
||||
this._isSaving = State.state.changes.isSaving;
|
||||
|
||||
this.onClick(() => {
|
||||
changes.uploadAll();
|
||||
State.state.changes.uploadAll();
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -29,7 +27,7 @@ export class PendingChanges extends UIElement {
|
|||
return "";
|
||||
}
|
||||
|
||||
var restingSeconds = this._countdown.data / 1000;
|
||||
var restingSeconds =State.state.secondsTillChangesAreSaved.data / 1000;
|
||||
var dots = "";
|
||||
while (restingSeconds > 0) {
|
||||
dots += ".";
|
||||
|
|
|
@ -8,6 +8,7 @@ import {TextField} from "./Input/TextField";
|
|||
import {Geocoding} from "../Logic/Osm/Geocoding";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {Basemap} from "../Logic/Leaflet/Basemap";
|
||||
import {State} from "../State";
|
||||
|
||||
|
||||
export class SearchAndGo extends UIElement {
|
||||
|
@ -23,12 +24,10 @@ export class SearchAndGo extends UIElement {
|
|||
);
|
||||
|
||||
private _foundEntries = new UIEventSource([]);
|
||||
private _map: Basemap;
|
||||
private _goButton = new FixedUiElement("<img class='search-go' src='./assets/search.svg' alt='GO'>");
|
||||
|
||||
constructor(map: Basemap) {
|
||||
constructor() {
|
||||
super(undefined);
|
||||
this._map = map;
|
||||
this.ListenTo(this._foundEntries);
|
||||
|
||||
const self = this;
|
||||
|
@ -48,7 +47,7 @@ export class SearchAndGo extends UIElement {
|
|||
this._searchField.Clear();
|
||||
this._placeholder.setData(Translations.t.general.search.searching);
|
||||
const self = this;
|
||||
Geocoding.Search(searchString, this._map, (result) => {
|
||||
Geocoding.Search(searchString, (result) => {
|
||||
|
||||
if (result.length == 0) {
|
||||
this._placeholder.setData(Translations.t.general.search.nothing);
|
||||
|
@ -60,7 +59,7 @@ export class SearchAndGo extends UIElement {
|
|||
[bb[0], bb[2]],
|
||||
[bb[1], bb[3]]
|
||||
]
|
||||
self._map.map.fitBounds(bounds);
|
||||
State.state.bm.map.fitBounds(bounds);
|
||||
this._placeholder.setData(Translations.t.general.search.search);
|
||||
},
|
||||
() => {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {CheckBox} from "./Input/CheckBox";
|
|||
import {VerticalCombine} from "./Base/VerticalCombine";
|
||||
import {QueryParameters} from "../Logic/QueryParameters";
|
||||
import {Img} from "./Img";
|
||||
import {State} from "../State";
|
||||
|
||||
export class ShareScreen extends UIElement {
|
||||
|
||||
|
@ -19,7 +20,7 @@ export class ShareScreen extends UIElement {
|
|||
private _link: UIElement;
|
||||
private _linkStatus: UIElement;
|
||||
|
||||
constructor(layout: Layout, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) {
|
||||
constructor() {
|
||||
super(undefined)
|
||||
const tr = Translations.t.general.sharescreen;
|
||||
|
||||
|
@ -32,6 +33,10 @@ export class ShareScreen extends UIElement {
|
|||
true
|
||||
)
|
||||
optionCheckboxes.push(includeLocation);
|
||||
|
||||
const currentLocation = State.state.locationControl;
|
||||
const layout = State.state.layoutToUse.data;
|
||||
|
||||
optionParts.push(includeLocation.isEnabled.map((includeL) => {
|
||||
if (includeL) {
|
||||
return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}`
|
||||
|
|
|
@ -11,6 +11,7 @@ import {VerticalCombine} from "./Base/VerticalCombine";
|
|||
import Locale from "./i18n/Locale";
|
||||
import {Changes} from "../Logic/Osm/Changes";
|
||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
||||
import {State} from "../State";
|
||||
|
||||
export interface Preset {
|
||||
description: string | UIElement,
|
||||
|
@ -24,13 +25,9 @@ export interface Preset {
|
|||
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
|
||||
*/
|
||||
export class SimpleAddUI extends UIElement {
|
||||
private _zoomlevel: UIEventSource<{ zoom: number }>;
|
||||
private _addButtons: UIElement[];
|
||||
private _lastClickLocation: UIEventSource<{ lat: number; lon: number }>;
|
||||
private _changes: Changes;
|
||||
private _selectedElement: UIEventSource<{ feature: any }>;
|
||||
|
||||
private _dataIsLoading: UIEventSource<boolean>;
|
||||
private _userDetails: UIEventSource<UserDetails>;
|
||||
|
||||
private _confirmPreset: UIEventSource<Preset>
|
||||
= new UIEventSource<Preset>(undefined);
|
||||
|
@ -39,24 +36,17 @@ export class SimpleAddUI extends UIElement {
|
|||
private goToInboxButton: UIElement = new SubtleButton("./assets/envelope.svg",
|
||||
Translations.t.general.goToInbox, {url:"https://www.openstreetmap.org/messages/inbox", newTab: false});
|
||||
|
||||
constructor(zoomlevel: UIEventSource<{ zoom: number }>,
|
||||
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
|
||||
changes: Changes,
|
||||
selectedElement: UIEventSource<{ feature: any }>,
|
||||
constructor(
|
||||
dataIsLoading: UIEventSource<boolean>,
|
||||
userDetails: UIEventSource<UserDetails>,
|
||||
addButtons: { description: string | UIElement, name: string | UIElement; icon: string; tags: Tag[]; layerToAddTo: FilteredLayer }[],
|
||||
) {
|
||||
super(zoomlevel);
|
||||
super(State.state.locationControl);
|
||||
this.ListenTo(Locale.language);
|
||||
this._zoomlevel = zoomlevel;
|
||||
this._lastClickLocation = lastClickLocation;
|
||||
this._changes = changes;
|
||||
this._selectedElement = selectedElement;
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
|
||||
this._dataIsLoading = dataIsLoading;
|
||||
this._userDetails = userDetails;
|
||||
this.ListenTo(userDetails);
|
||||
this.ListenTo(dataIsLoading);
|
||||
|
||||
this._addButtons = [];
|
||||
this.ListenTo(this._confirmPreset);
|
||||
this.clss = "add-ui"
|
||||
|
@ -102,26 +92,27 @@ export class SimpleAddUI extends UIElement {
|
|||
const self = this;
|
||||
return () => {
|
||||
|
||||
const loc = self._lastClickLocation.data;
|
||||
let feature = self._changes.createElement(option.tags, loc.lat, loc.lon);
|
||||
const loc = State.state.bm.lastClickLocation.data;
|
||||
let feature = State.state.changes.createElement(option.tags, loc.lat, loc.lon);
|
||||
option.layerToAddTo.AddNewElement(feature);
|
||||
self._selectedElement.setData({feature: feature});
|
||||
State.state.selectedElement.setData({feature: feature});
|
||||
}
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
||||
const userDetails = State.state.osmConnection.userDetails;
|
||||
|
||||
if (this._confirmPreset.data !== undefined) {
|
||||
|
||||
if(this._userDetails.data.dryRun){
|
||||
if(userDetails.data.dryRun){
|
||||
this.CreatePoint(this._confirmPreset.data)();
|
||||
return;
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}),
|
||||
this._userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"",
|
||||
userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"",
|
||||
this.confirmButton,
|
||||
this.cancelButton
|
||||
|
||||
|
@ -134,15 +125,15 @@ export class SimpleAddUI extends UIElement {
|
|||
let header: UIElement = Translations.t.general.add.header;
|
||||
|
||||
|
||||
if(this._userDetails === undefined){
|
||||
if(userDetails === undefined){
|
||||
return header.Render();
|
||||
}
|
||||
|
||||
if (!this._userDetails.data.loggedIn) {
|
||||
if (!userDetails.data.loggedIn) {
|
||||
return new Combine([header, Translations.t.general.add.pleaseLogin]).Render()
|
||||
}
|
||||
|
||||
if (this._userDetails.data.unreadMessages > 0) {
|
||||
if (userDetails.data.unreadMessages > 0) {
|
||||
return new Combine([header, "<span class='alert'>",
|
||||
Translations.t.general.readYourMessages,
|
||||
"</span>",
|
||||
|
@ -150,7 +141,7 @@ export class SimpleAddUI extends UIElement {
|
|||
]).Render();
|
||||
}
|
||||
|
||||
if (this._userDetails.data.dryRun) {
|
||||
if (userDetails.data.dryRun) {
|
||||
header = new Combine([header,
|
||||
"<span class='alert'>",
|
||||
"Test mode - changes won't be saved",
|
||||
|
@ -158,13 +149,13 @@ export class SimpleAddUI extends UIElement {
|
|||
]);
|
||||
}
|
||||
|
||||
if (this._userDetails.data.csCount < 5) {
|
||||
if (userDetails.data.csCount < 5) {
|
||||
return new Combine([header, "<span class='alert'>",
|
||||
Translations.t.general.fewChangesBefore,
|
||||
"</span>"]).Render();
|
||||
}
|
||||
|
||||
if (this._zoomlevel.data.zoom < 19) {
|
||||
if (State.state.locationControl.data.zoom < 19) {
|
||||
return new Combine([header, Translations.t.general.add.zoomInFurther]).Render()
|
||||
}
|
||||
|
||||
|
@ -183,7 +174,7 @@ export class SimpleAddUI extends UIElement {
|
|||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
this._userDetails.data.osmConnection.registerActivateOsmAUthenticationClass();
|
||||
State.state.osmConnection.registerActivateOsmAUthenticationClass();
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,9 @@ import {VariableUiElement} from "./Base/VariableUIElement";
|
|||
import Translations from "./i18n/Translations";
|
||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
||||
import {Basemap} from "../Logic/Leaflet/Basemap";
|
||||
import {State} from "../State";
|
||||
import {PendingChanges} from "./PendingChanges";
|
||||
import Locale from "./i18n/Locale";
|
||||
|
||||
/**
|
||||
* Handles and updates the user badge
|
||||
|
@ -14,27 +17,22 @@ export class UserBadge extends UIElement {
|
|||
private _userDetails: UIEventSource<UserDetails>;
|
||||
private _pendingChanges: UIElement;
|
||||
private _logout: UIElement;
|
||||
private _basemap: Basemap;
|
||||
private _homeButton: UIElement;
|
||||
private _languagePicker: UIElement;
|
||||
|
||||
|
||||
constructor(userDetails: UIEventSource<UserDetails>,
|
||||
pendingChanges: UIElement,
|
||||
languagePicker: UIElement,
|
||||
basemap: Basemap) {
|
||||
super(userDetails);
|
||||
this._userDetails = userDetails;
|
||||
this._pendingChanges = pendingChanges;
|
||||
this._basemap = basemap;
|
||||
this._languagePicker = languagePicker;
|
||||
constructor() {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this._userDetails = State.state.osmConnection.userDetails;
|
||||
this._pendingChanges = new PendingChanges();
|
||||
this._languagePicker = Locale.CreateLanguagePicker();
|
||||
|
||||
this._logout = new FixedUiElement("<img src='assets/logout.svg' class='small-userbadge-icon' alt='logout'>")
|
||||
.onClick(() => {
|
||||
userDetails.data.osmConnection.LogOut();
|
||||
State.state.osmConnection.LogOut();
|
||||
});
|
||||
|
||||
userDetails.addCallback(function () {
|
||||
this._userDetails.addCallback(function () {
|
||||
const profilePic = document.getElementById("profile-pic");
|
||||
if (profilePic) {
|
||||
|
||||
|
@ -45,18 +43,18 @@ export class UserBadge extends UIElement {
|
|||
});
|
||||
|
||||
this._homeButton = new VariableUiElement(
|
||||
userDetails.map((userinfo) => {
|
||||
this._userDetails.map((userinfo) => {
|
||||
if (userinfo.home) {
|
||||
return "<img id='home' src='./assets/home.svg' alt='home' class='small-userbadge-icon'> ";
|
||||
}
|
||||
return "";
|
||||
})
|
||||
).onClick(() => {
|
||||
const home = userDetails.data?.home;
|
||||
const home = State.state.osmConnection.userDetails.data?.home;
|
||||
if (home === undefined) {
|
||||
return;
|
||||
}
|
||||
basemap.map.flyTo([home.lat, home.lon], 18);
|
||||
State.state.bm.map.flyTo([home.lat, home.lon], 18);
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -91,7 +89,7 @@ export class UserBadge extends UIElement {
|
|||
iconSize: [20, 20],
|
||||
iconAnchor: [10, 10]
|
||||
});
|
||||
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(this._basemap.map);
|
||||
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(State.state.bm.map);
|
||||
}
|
||||
|
||||
const settings =
|
||||
|
|
60
UI/WelcomeMessage.ts
Normal file
60
UI/WelcomeMessage.ts
Normal 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()
|
||||
}
|
||||
|
||||
}
|
|
@ -3,14 +3,15 @@ import {LocalStorageSource} from "../../Logic/LocalStorageSource";
|
|||
import {DropDown} from "../Input/DropDown";
|
||||
import {Layout} from "../../Customizations/Layout";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {State} from "../../State";
|
||||
|
||||
|
||||
export default class Locale {
|
||||
public static language: UIEventSource<string> = LocalStorageSource.Get('language', "en");
|
||||
|
||||
public static CreateLanguagePicker(layoutToUse: Layout, label: string | UIElement = "") {
|
||||
public static CreateLanguagePicker(label: string | UIElement = "") {
|
||||
|
||||
return new DropDown(label, layoutToUse.supportedLanguages.map(lang => {
|
||||
return new DropDown(label, State.state.layoutToUse.data.supportedLanguages.map(lang => {
|
||||
return {value: lang, shown: lang}
|
||||
}
|
||||
), Locale.language);
|
||||
|
|
148
index.ts
148
index.ts
|
@ -31,6 +31,7 @@ import {BaseLayers, Basemap} from "./Logic/Leaflet/Basemap";
|
|||
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import {Changes} from "./Logic/Osm/Changes";
|
||||
import {State} from "./State";
|
||||
|
||||
|
||||
// --------------------- Special actions based on the parameters -----------------
|
||||
|
@ -79,70 +80,23 @@ defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data;
|
|||
|
||||
const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"];
|
||||
console.log("Using layout: ", layoutToUse.name);
|
||||
if(layoutToUse === undefined){
|
||||
if (layoutToUse === undefined) {
|
||||
console.log("Incorrect layout")
|
||||
}
|
||||
|
||||
|
||||
// ----------------- Setup a few event sources -------------
|
||||
|
||||
|
||||
// The message that should be shown at the center of the screen
|
||||
const centerMessage = new UIEventSource<string>("");
|
||||
|
||||
// The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource
|
||||
const secondsTillChangesAreSaved = new UIEventSource<number>(0);
|
||||
|
||||
// const leftMessage = new UIEventSource<() => UIElement>(undefined);
|
||||
|
||||
// This message is shown full screen on mobile devices
|
||||
const fullScreenMessage = new UIEventSource<UIElement>(undefined);
|
||||
|
||||
// The latest element that was selected - used to generate the right UI at the right place
|
||||
const selectedElement = new UIEventSource<{ feature: any }>(undefined);
|
||||
|
||||
const zoom = QueryParameters.GetQueryParameter("z", undefined)
|
||||
.syncWith(LocalStorageSource.Get("zoom"));
|
||||
const lat = QueryParameters.GetQueryParameter("lat", undefined)
|
||||
.syncWith(LocalStorageSource.Get("lat"));
|
||||
const lon = QueryParameters.GetQueryParameter("lon", undefined)
|
||||
.syncWith(LocalStorageSource.Get("lon"));
|
||||
|
||||
function featSw(key: string, deflt: boolean): UIEventSource<boolean> {
|
||||
return QueryParameters.GetQueryParameter(key, "" + deflt).map((str) => {
|
||||
return str !== "false";
|
||||
});
|
||||
}
|
||||
|
||||
const featureSwitchUserbadge = featSw("fs-userbadge", layoutToUse.enableUserBadge);
|
||||
const featureSwitchSearch = featSw("fs-search", layoutToUse.enableSearch);
|
||||
const featureSwitchLayers = featSw("fs-layers", layoutToUse.enableLayers);
|
||||
const featureSwitchAddNew = featSw("fs-add-new", layoutToUse.enableAdd);
|
||||
const featureSwitchWelcomeMessage = featSw("fs-welcome-message", true);
|
||||
const featureSwitchIframe = featSw("fs-iframe", false);
|
||||
|
||||
|
||||
const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({
|
||||
zoom: Utils.asFloat(zoom.data) ?? layoutToUse.startzoom,
|
||||
lat: Utils.asFloat(lat.data) ?? layoutToUse.startLat,
|
||||
lon: Utils.asFloat(lon.data) ?? layoutToUse.startLon
|
||||
});
|
||||
|
||||
locationControl.addCallback((latlonz) => {
|
||||
zoom.setData(latlonz.zoom.toString());
|
||||
lat.setData(latlonz.lat.toString().substr(0, 6));
|
||||
lon.setData(latlonz.lon.toString().substr(0, 6));
|
||||
})
|
||||
// Setup the global state
|
||||
State.state = new State(layoutToUse);
|
||||
const state = State.state;
|
||||
|
||||
|
||||
// ----------------- Prepare the important objects -----------------
|
||||
const osmConnection: OsmConnection = new OsmConnection(
|
||||
state.osmConnection = new OsmConnection(
|
||||
QueryParameters.GetQueryParameter("test", "false").data === "true",
|
||||
QueryParameters.GetQueryParameter("oauth_token", undefined)
|
||||
);
|
||||
|
||||
|
||||
Locale.language.syncWith(osmConnection.GetPreference("language"));
|
||||
Locale.language.syncWith(state.osmConnection.GetPreference("language"));
|
||||
|
||||
// @ts-ignore
|
||||
window.setLanguage = function (language: string) {
|
||||
|
@ -158,13 +112,12 @@ Locale.language.addCallback((currentLanguage) => {
|
|||
}).ping()
|
||||
|
||||
|
||||
const saveTimeout = 30000; // After this many milliseconds without changes, saves are sent of to OSM
|
||||
const allElements = new ElementStorage();
|
||||
const changes = new Changes(
|
||||
"Beantwoorden van vragen met #MapComplete voor vragenset #" + layoutToUse.name,
|
||||
osmConnection, allElements);
|
||||
const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
|
||||
locationControl.map((location) => {
|
||||
state.allElements = new ElementStorage();
|
||||
state.changes = new Changes(
|
||||
"Beantwoorden van vragen met #MapComplete voor vragenset #" + state.layoutToUse.data.name,
|
||||
state.osmConnection, state.allElements);
|
||||
state.bm = new Basemap("leafletDiv", state.locationControl, new VariableUiElement(
|
||||
state.locationControl.map((location) => {
|
||||
const mapComplete = "<a href='https://github.com/pietervdvn/MapComplete' target='_blank'>Mapcomple</a> " +
|
||||
" " +
|
||||
"<a href='https://github.com/pietervdvn/MapComplete/issues' target='_blank'><img src='./assets/bug.svg' alt='Report bug' class='small-userbadge-icon'></a>";
|
||||
|
@ -184,9 +137,9 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement(
|
|||
|
||||
// ------------- Setup the layers -------------------------------
|
||||
|
||||
const layerSetup = InitUiElements.InitLayers(layoutToUse, osmConnection, changes, allElements, bm, fullScreenMessage, selectedElement);
|
||||
const layerSetup = InitUiElements.InitLayers();
|
||||
|
||||
const layerUpdater = new LayerUpdater(bm, layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers);
|
||||
const layerUpdater = new LayerUpdater(layerSetup.minZoom, layoutToUse.widenFactor, layerSetup.flayers);
|
||||
|
||||
|
||||
// --------------- Setting up layer selection ui --------
|
||||
|
@ -196,19 +149,21 @@ const closedFilterButton = `<button id="filter__button" class="filter__button sh
|
|||
const openFilterButton = `
|
||||
<button id="filter__button" class="filter__button">${Img.openFilterButton}</button>`;
|
||||
|
||||
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {return {value: layer, shown: layer.name}});
|
||||
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, bm.CurrentLayer), openFilterButton]);
|
||||
let baseLayerOptions = BaseLayers.baseLayers.map((layer) => {
|
||||
return {value: layer, shown: layer.name}
|
||||
});
|
||||
const backgroundMapPicker = new Combine([new DropDown(`Background map`, baseLayerOptions, State.state.bm.CurrentLayer), openFilterButton]);
|
||||
const layerSelection = new Combine([`<p class="filter__label">Maplayers</p>`, new LayerSelection(layerSetup.flayers)]);
|
||||
let layerControl = backgroundMapPicker;
|
||||
if (layerSetup.flayers.length > 1) {
|
||||
layerControl = new Combine([layerSelection, backgroundMapPicker]);
|
||||
}
|
||||
|
||||
InitUiElements.OnlyIf(featureSwitchLayers, () => {
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
||||
|
||||
const checkbox = new CheckBox(layerControl, closedFilterButton);
|
||||
checkbox.AttachTo("filter__selection");
|
||||
bm.Location.addCallback(() => {
|
||||
State.state.bm.Location.addCallback(() => {
|
||||
checkbox.isEnabled.setData(false);
|
||||
});
|
||||
|
||||
|
@ -224,14 +179,10 @@ Locale.language.addCallback(e => {
|
|||
})
|
||||
|
||||
|
||||
InitUiElements.OnlyIf(featureSwitchAddNew, () => {
|
||||
new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => {
|
||||
return new SimpleAddUI(bm.Location,
|
||||
bm.LastClickLocation,
|
||||
changes,
|
||||
selectedElement,
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => {
|
||||
new StrayClickHandler(() => {
|
||||
return new SimpleAddUI(
|
||||
layerUpdater.runningQuery,
|
||||
osmConnection.userDetails,
|
||||
layerSetup.presets);
|
||||
}
|
||||
);
|
||||
|
@ -242,7 +193,7 @@ InitUiElements.OnlyIf(featureSwitchAddNew, () => {
|
|||
* Show the questions and information for the selected element
|
||||
* This is given to the div which renders fullscreen on mobile devices
|
||||
*/
|
||||
selectedElement.addCallback((feature) => {
|
||||
State.state.selectedElement.addCallback((feature) => {
|
||||
if (feature?.feature?.properties === undefined) {
|
||||
return;
|
||||
}
|
||||
|
@ -256,67 +207,54 @@ selectedElement.addCallback((feature) => {
|
|||
|
||||
const featureBox = new FeatureInfoBox(
|
||||
feature.feature,
|
||||
allElements.getElement(data.id),
|
||||
State.state.allElements.getElement(data.id),
|
||||
layer.title,
|
||||
layer.elementsToShow,
|
||||
changes,
|
||||
osmConnection.userDetails
|
||||
);
|
||||
|
||||
fullScreenMessage.setData(featureBox);
|
||||
State.state.fullScreenMessage.setData(featureBox);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
const pendingChanges = new PendingChanges(changes, secondsTillChangesAreSaved,);
|
||||
|
||||
InitUiElements.OnlyIf(featureSwitchUserbadge, () => {
|
||||
|
||||
new UserBadge(osmConnection.userDetails,
|
||||
pendingChanges,
|
||||
Locale.CreateLanguagePicker(layoutToUse),
|
||||
bm)
|
||||
.AttachTo('userbadge');
|
||||
console.log("Enable new:",State.state.featureSwitchAddNew.data,"deafult", layoutToUse.enableAdd)
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => {
|
||||
new UserBadge().AttachTo('userbadge');
|
||||
});
|
||||
|
||||
InitUiElements.OnlyIf((featureSwitchSearch), () => {
|
||||
new SearchAndGo(bm).AttachTo("searchbox");
|
||||
InitUiElements.OnlyIf((State.state.featureSwitchSearch), () => {
|
||||
new SearchAndGo().AttachTo("searchbox");
|
||||
});
|
||||
|
||||
new FullScreenMessageBoxHandler(fullScreenMessage, () => {
|
||||
selectedElement.setData(undefined)
|
||||
new FullScreenMessageBoxHandler(() => {
|
||||
State.state.selectedElement.setData(undefined)
|
||||
}).update();
|
||||
|
||||
InitUiElements.OnlyIf(featureSwitchWelcomeMessage, () => {
|
||||
InitUiElements.InitWelcomeMessage(layoutToUse,
|
||||
featureSwitchUserbadge.data ? osmConnection : undefined, bm, fullScreenMessage)
|
||||
InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => {
|
||||
InitUiElements.InitWelcomeMessage()
|
||||
});
|
||||
|
||||
if ((window != window.top && !featureSwitchWelcomeMessage.data) || featureSwitchIframe.data) {
|
||||
if ((window != window.top && !State.state.featureSwitchWelcomeMessage) || State.state.featureSwitchIframe.data) {
|
||||
new FixedUiElement(`<a href='${window.location}' target='_blank'><span class='iframe-escape'><img src='assets/pop-out.svg'></span></a>`).AttachTo("top-right")
|
||||
}
|
||||
|
||||
|
||||
new CenterMessageBox(
|
||||
layerSetup.minZoom,
|
||||
centerMessage,
|
||||
osmConnection,
|
||||
locationControl,
|
||||
layerUpdater.runningQuery)
|
||||
.AttachTo("centermessage");
|
||||
|
||||
|
||||
Helpers.SetupAutoSave(changes, secondsTillChangesAreSaved, saveTimeout);
|
||||
Helpers.LastEffortSave(changes);
|
||||
|
||||
osmConnection.registerActivateOsmAUthenticationClass();
|
||||
Helpers.SetupAutoSave();
|
||||
Helpers.LastEffortSave();
|
||||
|
||||
|
||||
new GeoLocationHandler(bm).AttachTo("geolocate-button");
|
||||
|
||||
new GeoLocationHandler().AttachTo("geolocate-button");
|
||||
|
||||
|
||||
locationControl.ping()
|
||||
State.state.osmConnection.registerActivateOsmAUthenticationClass();
|
||||
State.state.locationControl.ping()
|
||||
|
||||
|
|
Loading…
Reference in a new issue