forked from MapComplete/MapComplete
Some refactoring, more work on the custom theme generator
This commit is contained in:
parent
c4b5f180a6
commit
146552e62c
104 changed files with 382 additions and 1590 deletions
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 {
|
||||
|
|
34
UI/CustomThemeGenerator/Preview.ts
Normal file
34
UI/CustomThemeGenerator/Preview.ts
Normal 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, " "),
|
||||
'</span>'
|
||||
|
||||
]).Render();
|
||||
}
|
||||
|
||||
}
|
391
UI/CustomThemeGenerator/ThemeGenerator.ts
Normal file
391
UI/CustomThemeGenerator/ThemeGenerator.ts
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
|
||||
export class SimpleImageElement extends UIElement {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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> {
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 ;
|
||||
|
|
|
@ -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> {
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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'>
|
||||
<iframe src="${url}" width="100%" height="100%" title="${layout.name} with MapComplete"></iframe>
|
||||
<iframe src="${url}" width="100%" height="100%" title="${layout.title.InnerRender()} with MapComplete"></iframe>
|
||||
</span>`
|
||||
})
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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>) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue