Some refactoring, more work on the custom theme generator

This commit is contained in:
Pieter Vander Vennet 2020-08-17 17:23:15 +02:00
parent c4b5f180a6
commit 146552e62c
104 changed files with 382 additions and 1590 deletions

View file

@ -1,7 +1,6 @@
import {UIElement} from "../UIElement";
import Translations from "../i18n/Translations";
import Combine from "./Combine";
import {link} from "fs";
export class SubtleButton extends UIElement{

View file

@ -1,7 +1,6 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import Translations from "../i18n/Translations";
import {UIEventSource} from "../../Logic/UIEventSource";
export class TabbedComponent extends UIElement {

View file

@ -1,5 +1,5 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import {UIEventSource} from "../../Logic/UIEventSource";
export class VariableUiElement extends UIElement {
private _html: UIEventSource<string>;

View file

@ -1,8 +1,8 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {OsmConnection} from "../Logic/Osm/OsmConnection";
import Translations from "./i18n/Translations";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
export class CenterMessageBox extends UIElement {
@ -46,7 +46,6 @@ export class CenterMessageBox extends UIElement {
if (State.state.centerMessage.data != "") {
pstyle.opacity = "1";
pstyle.pointerEvents = "all";
State.state.osmConnection.registerActivateOsmAUthenticationClass();
return;
}
pstyle.pointerEvents = "none";

View file

@ -1,7 +1,7 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {FixedUiElement} from "./Base/FixedUiElement";
import {VariableUiElement} from "./Base/VariableUIElement";
import {UIEventSource} from "../Logic/UIEventSource";
export class ConfirmDialog extends UIElement {

View file

@ -0,0 +1,34 @@
import {LayoutConfigJson} from "../../Customizations/JSON/CustomLayoutFromJSON";
import {UIEventSource} from "../../Logic/UIEventSource";
import {UIElement} from "../UIElement";
import Combine from "../Base/Combine";
export class Preview extends UIElement {
private url: UIEventSource<string>;
private config: UIEventSource<LayoutConfigJson>;
constructor(url: UIEventSource<string>, config: UIEventSource<LayoutConfigJson>) {
super(url);
this.config = config;
this.url = url;
}
InnerRender(): string {
const url = this.url.data;
return new Combine([
`<iframe width="99%" height="70%" src="${this.url.data}"></iframe>`,
'<p class="alert">The above preview is in testmode. Changes will not be sent to OSM, so feel free to add points and answer questions</p> ',
`<h2>Your link</h2>`,
'<span class="alert">Bookmark the link below</span><br/>',
'MapComplete has no backend. The <i>entire</i> theme configuration is saved in the following URL. This means that this URL is needed to revive and change your MapComplete instance.<br/>',
`<a target='_blank' href='${this.url.data}'>${this.url.data}</a><br/>`,
'<h2>JSON-configuration</h2>',
'You can see the configuration in JSON format below.<br/>',
'<span class=\'literal-code iframe-code-block\' style="width:95%">',
JSON.stringify(this.config.data, null, 2).replace(/\n/g, "<br/>").replace(/ /g, "&nbsp;"),
'</span>'
]).Render();
}
}

View file

@ -0,0 +1,391 @@
import {UIElement} from "../UIElement";
import {VerticalCombine} from "../Base/VerticalCombine";
import {VariableUiElement} from "../Base/VariableUIElement";
import Combine from "../Base/Combine";
import {
LayerConfigJson,
LayoutConfigJson,
TagRenderingConfigJson
} from "../../Customizations/JSON/CustomLayoutFromJSON";
import {TabbedComponent} from "../Base/TabbedComponent";
import {UIEventSource} from "../../Logic/UIEventSource";
import {OsmConnection, UserDetails} from "../../Logic/Osm/OsmConnection";
import {Button} from "../Base/Button";
import {FixedUiElement} from "../Base/FixedUiElement";
import {TextField} from "../Input/TextField";
function TagsToString(tags: string | string [] | { k: string, v: string }[]) {
if (tags === undefined) {
return undefined;
}
if (typeof (tags) == "string") {
return tags;
}
const newTags = [];
console.log(tags)
for (const tag of tags) {
if (typeof (tag) == "string") {
newTags.push(tag)
} else {
newTags.push(tag.k + "=" + tag.v);
}
}
return newTags.join(",");
}
let createFieldUI: (label: string, key: string, root: any, options?: { deflt?: string }) => UIElement;
class MappingGenerator extends UIElement {
private elements: UIElement[];
constructor(fullConfig: UIEventSource<LayoutConfigJson>,
layerConfig: LayerConfigJson,
tagRendering: TagRenderingConfigJson,
mapping: { if: string | string[] | { k: string, v: string }[] }) {
super(undefined);
this.CreateElements(fullConfig, layerConfig, tagRendering, mapping)
}
private CreateElements(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson,
tagRendering: TagRenderingConfigJson,
mapping) {
{
const self = this;
this.elements = [
createFieldUI("If these tags apply", "if", mapping),
createFieldUI("Then: show this text", "then", mapping),
new Button("Remove this mapping", () => {
for (let i = 0; i < tagRendering.mappings.length; i++) {
if (tagRendering.mappings[i] === mapping) {
tagRendering.mappings.splice(i, 1);
self.elements = [
new FixedUiElement("Tag mapping removed")
]
self.Update();
break;
}
}
})
];
}
}
InnerRender(): string {
const combine = new VerticalCombine(this.elements);
combine.clss = "bordered";
return combine.Render();
}
}
class TagRenderingGenerator
extends UIElement {
private elements: UIElement[];
constructor(fullConfig: UIEventSource<LayoutConfigJson>,
layerConfig: LayerConfigJson,
tagRendering: TagRenderingConfigJson,
isTitle: boolean = false) {
super(undefined);
this.CreateElements(fullConfig, layerConfig, tagRendering, isTitle)
}
private CreateElements(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson, tagRendering: TagRenderingConfigJson, isTitle: boolean) {
const self = this;
this.elements = [
new FixedUiElement(isTitle ? "<h3>Popup title</h3>" : "<h3>TagRendering/TagQuestion</h3>"),
createFieldUI("Key", "key", tagRendering),
createFieldUI("Rendering", "render", tagRendering),
createFieldUI("Type", "type", tagRendering),
createFieldUI("Question", "question", tagRendering),
createFieldUI("Extra tags", "addExtraTags", tagRendering),
...(tagRendering.mappings ?? []).map((mapping) => {
return new MappingGenerator(fullConfig, layerConfig, tagRendering, mapping)
}),
new Button("Add mapping", () => {
if (tagRendering.mappings === undefined) {
tagRendering.mappings = []
}
tagRendering.mappings.push({if: "", then: ""});
self.CreateElements(fullConfig, layerConfig, tagRendering, isTitle);
self.Update();
})
]
if (!isTitle) {
const b = new Button("Remove this preset", () => {
for (let i = 0; i < layerConfig.tagRenderings.length; i++) {
if (layerConfig.tagRenderings[i] === tagRendering) {
layerConfig.tagRenderings.splice(i, 1);
self.elements = [
new FixedUiElement("Tag rendering removed")
]
self.Update();
break;
}
}
});
this.elements.push(b);
}
}
InnerRender(): string {
const combine = new VerticalCombine(this.elements);
combine.clss = "bordered";
return combine.Render();
}
}
class PresetGenerator extends UIElement {
private elements: UIElement[];
constructor(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson,
preset0: { title?: string, description?: string, icon?: string, tags?: string | string[] | { k: string, v: string }[] }) {
super(undefined);
const self = this;
this.elements = [
new FixedUiElement("<h3>Preset</h3>"),
createFieldUI("Title", "title", preset0),
createFieldUI("Description", "description", preset0, {deflt: layerConfig.description}),
createFieldUI("icon", "icon", preset0, {deflt: layerConfig.icon}),
createFieldUI("tags", "tags", preset0, {deflt: TagsToString(layerConfig.overpassTags)}),
new Button("Remove this preset", () => {
for (let i = 0; i < layerConfig.presets.length; i++) {
if (layerConfig.presets[i] === preset0) {
layerConfig.presets.splice(i, 1);
self.elements = [
new FixedUiElement("Preset removed")
]
self.Update();
break;
}
}
})
]
}
InnerRender(): string {
const combine = new VerticalCombine(this.elements);
combine.clss = "bordered";
return combine.Render();
}
}
class LayerGenerator extends UIElement {
private fullConfig: UIEventSource<LayoutConfigJson>;
private layerConfig: UIEventSource<LayerConfigJson>;
private generateField: ((label: string, key: string, root: any, deflt?: string) => UIElement);
private uielements: UIElement[];
constructor(fullConfig: UIEventSource<LayoutConfigJson>,
layerConfig: LayerConfigJson) {
super(undefined);
this.layerConfig = new UIEventSource<LayerConfigJson>(layerConfig);
this.fullConfig = fullConfig;
this.CreateElements(fullConfig, layerConfig)
}
private CreateElements(fullConfig: UIEventSource<LayoutConfigJson>, layerConfig: LayerConfigJson) {
const self = this;
this.uielements = [
createFieldUI("The name of this layer", "id", layerConfig),
createFieldUI("A description of objects for this layer", "description", layerConfig),
createFieldUI("The icon of this layer, either a URL or a base64-encoded svg", "icon", layerConfig),
createFieldUI("The default stroke color", "color", layerConfig),
createFieldUI("The minimal needed zoom to start loading", "minzoom", layerConfig),
createFieldUI("The tags to load from overpass", "overpassTags", layerConfig),
...layerConfig.presets.map(preset => new PresetGenerator(fullConfig, layerConfig, preset)),
new Button("Add a preset", () => {
layerConfig.presets.push({
icon: undefined,
title: "",
description: "",
tags: TagsToString(layerConfig.overpassTags)
});
self.CreateElements(fullConfig, layerConfig);
self.Update();
}),
new TagRenderingGenerator(fullConfig, layerConfig, layerConfig.title ?? {
key: "",
addExtraTags: "",
mappings: [],
question: "",
render: "Title",
type: "text"
}, true),
...layerConfig.tagRenderings.map(tr => new TagRenderingGenerator(fullConfig, layerConfig, tr)),
new Button("Add a tag rendering", () => {
layerConfig.tagRenderings.push({
key: "",
addExtraTags: "",
mappings: [],
question: "",
render: "",
type: "text"
});
self.CreateElements(fullConfig, layerConfig);
self.Update();
}),
]
}
InnerRender(): string {
return new VerticalCombine(this.uielements).Render();
}
}
class AllLayerComponent extends UIElement {
private tabs: TabbedComponent;
private config: UIEventSource<LayoutConfigJson>;
constructor(config: UIEventSource<LayoutConfigJson>) {
super(undefined);
this.config = config;
const self = this;
let previousLayerAmount = config.data.layers.length;
config.addCallback((data) => {
if (data.layers.length != previousLayerAmount) {
previousLayerAmount = data.layers.length;
self.UpdateTabs();
self.Update();
}
});
this.UpdateTabs();
}
private UpdateTabs() {
const layerPanes: { header: UIElement | string, content: UIElement | string }[] = [];
const config = this.config;
for (const layer of this.config.data.layers) {
const header = this.config.map(() => {
return `<img src="${layer?.icon ?? "./assets/help.svg"}">`
});
layerPanes.push({
header: new VariableUiElement(header),
content: new LayerGenerator(config, layer)
})
}
layerPanes.push({
header: "<img src='./assets/add.svg'>",
content: new Button("Add a new layer", () => {
config.data.layers.push({
id: "",
title: {
render: "Title"
},
icon: "./assets/bug.svg",
color: "",
description: "",
minzoom: 12,
overpassTags: "",
presets: [{}],
tagRenderings: []
});
config.ping();
})
})
this.tabs = new TabbedComponent(layerPanes);
}
InnerRender(): string {
return this.tabs.Render();
}
}
export class ThemeGenerator extends UIElement {
private readonly userDetails: UIEventSource<UserDetails>;
public readonly themeObject: UIEventSource<LayoutConfigJson>;
private readonly allQuestionFields: UIElement[];
public url: UIEventSource<string>;
constructor(connection: OsmConnection, windowHash) {
super(connection.userDetails);
this.userDetails = connection.userDetails;
const defaultTheme = {layers: [], icon: "./assets/bug.svg"};
let loadedTheme = undefined;
if (windowHash !== undefined && windowHash.length > 4) {
loadedTheme = JSON.parse(atob(windowHash));
}
this.themeObject = new UIEventSource<LayoutConfigJson>(loadedTheme ?? defaultTheme);
const jsonObjectRoot = this.themeObject.data;
const base64 = this.themeObject.map(JSON.stringify).map(btoa);
this.url = base64.map((data) => `https://pietervdvn.github.io/MapComplete/index.html?test=true&userlayout=true#` + data);
const self = this;
createFieldUI = (label, key, root, options) => {
const value = new UIEventSource<string>(TagsToString(root[key]) ?? options?.deflt);
value.addCallback((v) => {
root[key] = v;
self.themeObject.ping(); // We assume the root is a part of the themeObject
})
return new Combine([
label,
new TextField<string>({
fromString: (str) => str,
toString: (str) => str,
value: value
})]);
}
this.allQuestionFields = [
createFieldUI("Name of this theme", "name", jsonObjectRoot),
createFieldUI("Title (shown in the window and in the welcome message)", "title", jsonObjectRoot),
createFieldUI("Description (shown in the welcome message and various other places)", "description", jsonObjectRoot),
createFieldUI("The supported language", "language", jsonObjectRoot),
createFieldUI("startLat", "startLat", jsonObjectRoot),
createFieldUI("startLon", "startLon", jsonObjectRoot),
createFieldUI("startzoom", "startZoom", jsonObjectRoot),
createFieldUI("icon: either a URL to an image file, a relative url to a MapComplete asset ('./asset/help.svg') or a base64-encoded value (including 'data:image/svg+xml;base64,'", "icon", jsonObjectRoot, {deflt: "./assets/bug.svg"}),
new AllLayerComponent(this.themeObject)
]
}
InnerRender(): string {
if (!this.userDetails.data.loggedIn) {
return "Not logged in. You need to be logged in to create a theme."
}
if (this.userDetails.data.csCount < 500) {
return "You need at least 500 changesets to create your own theme.";
}
return new VerticalCombine([
// new VariableUiElement(this.themeObject.map(JSON.stringify)),
// new VariableUiElement(this.url.map((url) => `Current URL: <a href="${url}" target="_blank">Click here to open</a>`)),
...this.allQuestionFields,
]).Render();
}
}

View file

@ -1,5 +1,4 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {ImageCarousel} from "./Image/ImageCarousel";
import {VerticalCombine} from "./Base/VerticalCombine";
import {OsmLink} from "../Customizations/Questions/OsmLink";

View file

@ -1,8 +1,8 @@
import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
/**
* Handles the full screen popup on mobile

View file

@ -1,16 +1,15 @@
import {UIElement} from "../UIElement";
import {ImageSearcher} from "../../Logic/ImageSearcher";
import {UIEventSource} from "../UIEventSource";
import {SlideShow} from "../SlideShow";
import {FixedUiElement} from "../Base/FixedUiElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import {ConfirmDialog} from "../ConfirmDialog";
import {UIEventSource} from "../../Logic/UIEventSource";
import {
Dependencies,
TagDependantUIElement,
TagDependantUIElementConstructor
} from "../../Customizations/UIElementConstructor";
import {Changes} from "../../Logic/Osm/Changes";
import {State} from "../../State";
export class ImageCarouselConstructor implements TagDependantUIElementConstructor{

View file

@ -1,7 +1,7 @@
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../UIElement";
import {LicenseInfo} from "../../Logic/Wikimedia";
import {Imgur} from "../../Logic/Imgur";
import {UIEventSource} from "../../Logic/UIEventSource";
import {LicenseInfo} from "../../Logic/Web/Wikimedia";
import {Imgur} from "../../Logic/Web/Imgur";
export class ImgurImage extends UIElement {

View file

@ -1,5 +1,5 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import {UIEventSource} from "../../Logic/UIEventSource";
export class SimpleImageElement extends UIElement {

View file

@ -1,6 +1,6 @@
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../UIElement";
import {LicenseInfo, Wikimedia} from "../../Logic/Wikimedia";
import {LicenseInfo, Wikimedia} from "../../Logic/Web/Wikimedia";
import {UIEventSource} from "../../Logic/UIEventSource";
export class WikimediaImage extends UIElement {

View file

@ -1,7 +1,5 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import $ from "jquery"
import {Imgur} from "../Logic/Imgur";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {DropDown} from "./Input/DropDown";
import {VariableUiElement} from "./Base/VariableUIElement";
@ -10,6 +8,8 @@ import {fail} from "assert";
import Combine from "./Base/Combine";
import {VerticalCombine} from "./Base/VerticalCombine";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
import {Imgur} from "../Logic/Web/Imgur";
export class ImageUploadFlow extends UIElement {
private _licensePicker: UIElement;

View file

@ -1,9 +1,7 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import { FilteredLayer } from "../../Logic/FilteredLayer";
import Translations from "../../UI/i18n/Translations";
import instantiate = WebAssembly.instantiate;
import {UIEventSource} from "../../Logic/UIEventSource";
export class CheckBox extends UIElement{
public readonly isEnabled: UIEventSource<boolean>;

View file

@ -1,9 +1,7 @@
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../UIElement";
import {InputElement} from "./InputElement";
import instantiate = WebAssembly.instantiate;
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
import {UIEventSource} from "../../Logic/UIEventSource";
export class DropDown<T> extends InputElement<T> {

View file

@ -1,8 +1,7 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../UIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import {UIEventSource} from "../../Logic/UIEventSource";
export class FixedInputElement<T> extends InputElement<T> {
private rendering: UIElement;

View file

@ -1,7 +1,6 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import {FixedUiElement} from "../Base/FixedUiElement";
import {UIEventSource} from "../../Logic/UIEventSource";
export abstract class InputElement<T> extends UIElement{
abstract GetValue() : UIEventSource<T>;

View file

@ -1,9 +1,8 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../UIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
import {UIEventSource} from "../../Logic/UIEventSource";
export class InputElementWrapper<T> extends InputElement<T>{
private pre: UIElement ;

View file

@ -1,6 +1,5 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
export class RadioButton<T> extends InputElement<T> {

View file

@ -1,8 +1,7 @@
import {UIElement} from "../UIElement";
import {UIEventSource} from "../UIEventSource";
import {InputElement} from "./InputElement";
import Translations from "../i18n/Translations";
import {UIEventSource} from "../../Logic/UIEventSource";
export class TextField<T> extends InputElement<T> {

View file

@ -62,8 +62,7 @@ export class MoreScreen extends UIElement {
tr.intro,
tr.requestATheme,
new VerticalCombine(els),
tr.streetcomplete,
new FixedUiElement(State.vNumber)
tr.streetcomplete
]).Render();
}

View file

@ -1,7 +1,6 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {Changes} from "../Logic/Osm/Changes";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
export class PendingChanges extends UIElement {
private _pendingChangesCount: UIEventSource<number>;

View file

@ -1,6 +1,6 @@
import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import Translations from "./i18n/Translations";
import {UIEventSource} from "../Logic/UIEventSource";
export class SaveButton extends UIElement {
private _value: UIEventSource<any>;

View file

@ -1,5 +1,4 @@
import Locale from "./i18n/Locale";
import {UIEventSource} from "./UIEventSource";
import {UIElement} from "./UIElement";
import Translation from "./i18n/Translation";
import {VariableUiElement} from "./Base/VariableUIElement";
@ -7,9 +6,9 @@ import {FixedUiElement} from "./Base/FixedUiElement";
import {TextField} from "./Input/TextField";
import {Geocoding} from "../Logic/Osm/Geocoding";
import Translations from "./i18n/Translations";
import {Basemap} from "../Logic/Leaflet/Basemap";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
export class SearchAndGo extends UIElement {

View file

@ -1,18 +1,16 @@
import {UIElement} from "./UIElement";
import {Layout} from "../Customizations/Layout";
import Translations from "./i18n/Translations";
import {FixedUiElement} from "./Base/FixedUiElement";
import Combine from "./Base/Combine";
import {VariableUiElement} from "./Base/VariableUIElement";
import {UIEventSource} from "./UIEventSource";
import {CheckBox} from "./Input/CheckBox";
import {VerticalCombine} from "./Base/VerticalCombine";
import {QueryParameters} from "../Logic/QueryParameters";
import {Img} from "./Img";
import {State} from "../State";
import {Basemap} from "../Logic/Leaflet/Basemap";
import {FilteredLayer} from "../Logic/FilteredLayer";
import {Utils} from "../Utils";
import {UIEventSource} from "../Logic/UIEventSource";
export class ShareScreen extends UIElement {
@ -147,7 +145,7 @@ export class ShareScreen extends UIElement {
this._iframeCode = new VariableUiElement(
url.map((url) => {
return `<span class='literal-code iframe-code-block'>
&lt;iframe src="${url}" width="100%" height="100%" title="${layout.name} with MapComplete"&gt;&lt;/iframe&gt
&lt;iframe src="${url}" width="100%" height="100%" title="${layout.title.InnerRender()} with MapComplete"&gt;&lt;/iframe&gt
</span>`
})
);

View file

@ -1,18 +1,13 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {Tag} from "../Logic/TagsFilter";
import {FilteredLayer} from "../Logic/FilteredLayer";
import {FixedUiElement} from "./Base/FixedUiElement";
import {Button} from "./Base/Button";
import Translations from "./i18n/Translations";
import Combine from "./Base/Combine";
import {SubtleButton} from "./Base/SubtleButton";
import {VerticalCombine} from "./Base/VerticalCombine";
import Locale from "./i18n/Locale";
import {Changes} from "../Logic/Osm/Changes";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {State} from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
/**
* Asks to add a feature at the last clicked location, at least if zoom is sufficient

View file

@ -1,6 +1,6 @@
import {UIElement} from "./UIElement";
import {UIEventSource} from "./UIEventSource";
import {FixedUiElement} from "./Base/FixedUiElement";
import {UIEventSource} from "../Logic/UIEventSource";
export class SlideShow extends UIElement {

View file

@ -1,5 +1,4 @@
import {UIEventSource} from "./UIEventSource";
import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
import {UIEventSource} from "../Logic/UIEventSource";
export abstract class UIElement extends UIEventSource<string>{
@ -11,7 +10,11 @@ export abstract class UIElement extends UIEventSource<string>{
private _hideIfEmpty = false;
// WOrkaround as document is not defined
/**
* In the 'deploy'-step, some code needs to be run by ts-node.
* However, ts-node crashes when it sees 'document'. When running from console, we flag this and disable all code where document is needed.
* This is a workaround and yet another hack
*/
public static runningFromConsole = false;
protected constructor(source: UIEventSource<any>) {

View file

@ -1,90 +0,0 @@
export class UIEventSource<T>{
public data: T;
private _callbacks = [];
constructor(data: T) {
this.data = data;
}
public addCallback(callback: ((latestData : T) => void)) {
this._callbacks.push(callback);
return this;
}
public setData(t: T): void {
if (this.data === t) {
return;
}
this.data = t;
this.ping();
}
public ping(): void {
for (const callback of this._callbacks) {
callback(this.data);
}
}
public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources: UIEventSource<any>[]): UIEventSource<X> {
const sink = new UIEventSource<X>(source.data?.data);
source.addCallback((latestData) => {
sink.setData(latestData?.data);
});
for (const possibleSource of possibleSources) {
possibleSource.addCallback(() => {
sink.setData(source.data?.data);
})
}
return sink;
}
public map<J>(f: ((T) => J),
extraSources: UIEventSource<any>[] = [],
g: ((J) => T) = undefined ): UIEventSource<J> {
const self = this;
const newSource = new UIEventSource<J>(
f(this.data)
);
const update = function () {
newSource.setData(f(self.data));
newSource.ping();
}
this.addCallback(update);
for (const extraSource of extraSources) {
extraSource.addCallback(update);
}
if(g !== undefined) {
newSource.addCallback((latest) => {
self.setData((g(latest)));
})
}
return newSource;
}
public syncWith(otherSource: UIEventSource<T>) : UIEventSource<T>{
this.addCallback((latest) => otherSource.setData(latest));
const self = this;
otherSource.addCallback((latest) => self.setData(latest));
if(this.data === undefined){
this.setData(otherSource.data);
}else{
otherSource.setData(this.data);
}
return this;
}
}

View file

@ -5,11 +5,11 @@ import {FixedUiElement} from "./Base/FixedUiElement";
import {VariableUiElement} from "./Base/VariableUIElement";
import Translations from "./i18n/Translations";
import {UserDetails} from "../Logic/Osm/OsmConnection";
import {Basemap} from "../Logic/Leaflet/Basemap";
import {State} from "../State";
import {PendingChanges} from "./PendingChanges";
import Locale from "./i18n/Locale";
import {Utils} from "../Utils";
// @ts-ignore
import {UIEventSource} from "../Logic/UIEventSource";
/**
* Handles and updates the user badge

View file

@ -1,5 +1,4 @@
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";
@ -7,6 +6,8 @@ import {Layout} from "../Customizations/Layout";
import Translations from "./i18n/Translations";
import {VariableUiElement} from "./Base/VariableUIElement";
import {Utils} from "../Utils";
import {UIEventSource} from "../Logic/UIEventSource";
export class WelcomeMessage extends UIElement {
private readonly layout: Layout;

View file

@ -1,6 +1,6 @@
import {UIEventSource} from "../UIEventSource";
import {LocalStorageSource} from "../../Logic/LocalStorageSource";
import {UIEventSource} from "../../Logic/UIEventSource";
import {UIElement} from "../UIElement";
import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource";
export default class Locale {