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({
|
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}"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
10
Helpers.ts
10
Helpers.ts
|
@ -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) {
|
||||||
|
|
|
@ -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 ?? []) {
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = "";
|
||||||
|
|
|
@ -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=" +
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
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 {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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
|
|
|
@ -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();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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([
|
||||||
|
|
|
@ -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 += ".";
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -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}`
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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
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 {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
148
index.ts
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue