More refactoring, stuff kindoff works
This commit is contained in:
parent
62f471df1e
commit
3943100e54
52 changed files with 635 additions and 1010 deletions
|
@ -25,7 +25,6 @@ import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
||||||
import LayerResetter from "./Logic/Actors/LayerResetter";
|
import LayerResetter from "./Logic/Actors/LayerResetter";
|
||||||
import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs";
|
import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs";
|
||||||
import LayerControlPanel from "./UI/BigComponents/LayerControlPanel";
|
import LayerControlPanel from "./UI/BigComponents/LayerControlPanel";
|
||||||
import FeatureSwitched from "./UI/Base/FeatureSwitched";
|
|
||||||
import ShowDataLayer from "./UI/ShowDataLayer";
|
import ShowDataLayer from "./UI/ShowDataLayer";
|
||||||
import Hash from "./Logic/Web/Hash";
|
import Hash from "./Logic/Web/Hash";
|
||||||
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
|
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
|
||||||
|
@ -39,7 +38,6 @@ import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
|
||||||
import AttributionPanel from "./UI/BigComponents/AttributionPanel";
|
import AttributionPanel from "./UI/BigComponents/AttributionPanel";
|
||||||
import ContributorCount from "./Logic/ContributorCount";
|
import ContributorCount from "./Logic/ContributorCount";
|
||||||
import FeatureSource from "./Logic/FeatureSource/FeatureSource";
|
import FeatureSource from "./Logic/FeatureSource/FeatureSource";
|
||||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
|
||||||
import AllKnownLayers from "./Customizations/AllKnownLayers";
|
import AllKnownLayers from "./Customizations/AllKnownLayers";
|
||||||
import LayerConfig from "./Customizations/JSON/LayerConfig";
|
import LayerConfig from "./Customizations/JSON/LayerConfig";
|
||||||
|
|
||||||
|
@ -170,13 +168,14 @@ export class InitUiElements {
|
||||||
marker.addTo(State.state.leafletMap.data)
|
marker.addTo(State.state.leafletMap.data)
|
||||||
});
|
});
|
||||||
|
|
||||||
const geolocationButton = new FeatureSwitched(
|
const geolocationButton = new Toggle(
|
||||||
new MapControlButton(
|
new MapControlButton(
|
||||||
new GeoLocationHandler(
|
new GeoLocationHandler(
|
||||||
State.state.currentGPSLocation,
|
State.state.currentGPSLocation,
|
||||||
State.state.leafletMap,
|
State.state.leafletMap,
|
||||||
State.state.layoutToUse
|
State.state.layoutToUse
|
||||||
)),
|
)),
|
||||||
|
undefined,
|
||||||
State.state.featureSwitchGeolocation);
|
State.state.featureSwitchGeolocation);
|
||||||
|
|
||||||
const plus = new MapControlButton(
|
const plus = new MapControlButton(
|
||||||
|
@ -193,7 +192,7 @@ export class InitUiElements {
|
||||||
State.state.locationControl.ping();
|
State.state.locationControl.ping();
|
||||||
})
|
})
|
||||||
|
|
||||||
new Combine([plus, min, geolocationButton].map(el => el.SetClass("m-1")))
|
new Combine([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1")))
|
||||||
.SetClass("flex flex-col")
|
.SetClass("flex flex-col")
|
||||||
.AttachTo("bottom-right");
|
.AttachTo("bottom-right");
|
||||||
|
|
||||||
|
@ -212,8 +211,6 @@ export class InitUiElements {
|
||||||
// Reset the loading message once things are loaded
|
// Reset the loading message once things are loaded
|
||||||
new CenterMessageBox().AttachTo("centermessage");
|
new CenterMessageBox().AttachTo("centermessage");
|
||||||
|
|
||||||
// At last, zoom to the needed location if the focus is on an element
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import Svg from "../../Svg";
|
||||||
import Img from "../../UI/Base/Img";
|
import Img from "../../UI/Base/Img";
|
||||||
import {LocalStorageSource} from "../Web/LocalStorageSource";
|
import {LocalStorageSource} from "../Web/LocalStorageSource";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
|
import BaseUIElement from "../../UI/BaseUIElement";
|
||||||
|
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
|
||||||
|
|
||||||
export default class GeoLocationHandler extends UIElement {
|
export default class GeoLocationHandler extends UIElement {
|
||||||
|
|
||||||
|
@ -52,19 +54,19 @@ export default class GeoLocationHandler extends UIElement {
|
||||||
private readonly _previousLocationGrant: UIEventSource<string> = LocalStorageSource.Get("geolocation-permissions");
|
private readonly _previousLocationGrant: UIEventSource<string> = LocalStorageSource.Get("geolocation-permissions");
|
||||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||||
|
|
||||||
|
|
||||||
|
private readonly _element: BaseUIElement;
|
||||||
|
|
||||||
constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
||||||
leafletMap: UIEventSource<L.Map>,
|
leafletMap: UIEventSource<L.Map>,
|
||||||
layoutToUse: UIEventSource<LayoutConfig>) {
|
layoutToUse: UIEventSource<LayoutConfig>) {
|
||||||
super(undefined);
|
super();
|
||||||
this._currentGPSLocation = currentGPSLocation;
|
this._currentGPSLocation = currentGPSLocation;
|
||||||
this._leafletMap = leafletMap;
|
this._leafletMap = leafletMap;
|
||||||
this._layoutToUse = layoutToUse;
|
this._layoutToUse = layoutToUse;
|
||||||
this._hasLocation = currentGPSLocation.map((location) => location !== undefined);
|
this._hasLocation = currentGPSLocation.map((location) => location !== undefined);
|
||||||
this.dumbMode = false;
|
|
||||||
const self = this;
|
const self = this;
|
||||||
import("../../vendor/Leaflet.AccuratePosition.js").then(() => {
|
|
||||||
self.init();
|
|
||||||
})
|
|
||||||
|
|
||||||
const currentPointer = this._isActive.map(isActive => {
|
const currentPointer = this._isActive.map(isActive => {
|
||||||
if (isActive && !self._hasLocation.data) {
|
if (isActive && !self._hasLocation.data) {
|
||||||
|
@ -76,60 +78,34 @@ export default class GeoLocationHandler extends UIElement {
|
||||||
self.SetClass(pointerClass);
|
self.SetClass(pointerClass);
|
||||||
self.Update()
|
self.Update()
|
||||||
})
|
})
|
||||||
|
this._element = new VariableUiElement(
|
||||||
|
this._hasLocation.map(hasLocation => {
|
||||||
|
|
||||||
|
if (hasLocation) {
|
||||||
|
return Svg.crosshair_blue_ui()
|
||||||
|
}
|
||||||
|
if (self._isActive.data) {
|
||||||
|
return Svg.crosshair_blue_center_ui();
|
||||||
|
}
|
||||||
|
return Svg.crosshair_ui();
|
||||||
|
}, [this._isActive])
|
||||||
|
);
|
||||||
|
|
||||||
|
this.onClick(() => self.init(true))
|
||||||
|
|
||||||
|
self.init(false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
if (this._hasLocation.data) {
|
protected InnerRender(): string | BaseUIElement {
|
||||||
return Svg.crosshair_blue_img;
|
return this._element
|
||||||
}
|
|
||||||
if (this._isActive.data) {
|
|
||||||
return Svg.crosshair_blue_center_img;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Svg.crosshair_img;
|
private init(askPermission: boolean) {
|
||||||
}
|
|
||||||
|
|
||||||
InnerUpdate(htmlElement: HTMLElement) {
|
|
||||||
super.InnerUpdate(htmlElement);
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
htmlElement.onclick = function () {
|
|
||||||
self.StartGeolocating();
|
|
||||||
}
|
|
||||||
|
|
||||||
htmlElement.oncontextmenu = function (e) {
|
|
||||||
self.StartGeolocating();
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private init() {
|
|
||||||
this.ListenTo(this._hasLocation);
|
|
||||||
this.ListenTo(this._isActive);
|
|
||||||
this.ListenTo(this._permission);
|
|
||||||
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
function onAccuratePositionProgress(e) {
|
|
||||||
self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAccuratePositionFound(e) {
|
|
||||||
self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAccuratePositionError(e) {
|
|
||||||
console.log("onerror", e.message);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const map = this._leafletMap.data;
|
const map = this._leafletMap.data;
|
||||||
map.on('accuratepositionprogress', onAccuratePositionProgress);
|
|
||||||
map.on('accuratepositionfound', onAccuratePositionFound);
|
|
||||||
map.on('accuratepositionerror', onAccuratePositionError);
|
|
||||||
|
|
||||||
|
|
||||||
this._currentGPSLocation.addCallback((location) => {
|
this._currentGPSLocation.addCallback((location) => {
|
||||||
self._previousLocationGrant.setData("granted");
|
self._previousLocationGrant.setData("granted");
|
||||||
|
@ -178,7 +154,9 @@ export default class GeoLocationHandler extends UIElement {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
if (this._previousLocationGrant.data === "granted") {
|
if (askPermission) {
|
||||||
|
self.StartGeolocating(true);
|
||||||
|
} else if (this._previousLocationGrant.data === "granted") {
|
||||||
this._previousLocationGrant.setData("");
|
this._previousLocationGrant.setData("");
|
||||||
self.StartGeolocating(false);
|
self.StartGeolocating(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,9 @@ import {UIEventSource} from "../UIEventSource";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import Translations from "../../UI/i18n/Translations";
|
import Translations from "../../UI/i18n/Translations";
|
||||||
import Locale from "../../UI/i18n/Locale";
|
import Locale from "../../UI/i18n/Locale";
|
||||||
import {UIElement} from "../../UI/UIElement";
|
|
||||||
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer";
|
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer";
|
||||||
import {ElementStorage} from "../ElementStorage";
|
import {ElementStorage} from "../ElementStorage";
|
||||||
import Combine from "../../UI/Base/Combine";
|
import Combine from "../../UI/Base/Combine";
|
||||||
import BaseUIElement from "../../UI/BaseUIElement";
|
|
||||||
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
|
||||||
|
|
||||||
class TitleElement extends UIEventSource<string> {
|
class TitleElement extends UIEventSource<string> {
|
||||||
|
|
||||||
|
@ -42,7 +39,8 @@ class TitleElement extends UIEventSource<string> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (layer.source.osmTags.matchesProperties(tags)) {
|
if (layer.source.osmTags.matchesProperties(tags)) {
|
||||||
const title = new TagRenderingAnswer(tags, layer.title)
|
const tagsSource = allElementsStorage.getEventSourceById(tags.id)
|
||||||
|
const title = new TagRenderingAnswer(tagsSource, layer.title)
|
||||||
return new Combine([defaultTitle, " | ", title]).ConstructElement().innerText;
|
return new Combine([defaultTitle, " | ", title]).ConstructElement().innerText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ export class OsmConnection {
|
||||||
|
|
||||||
public auth;
|
public auth;
|
||||||
public userDetails: UIEventSource<UserDetails>;
|
public userDetails: UIEventSource<UserDetails>;
|
||||||
|
public isLoggedIn: UIEventSource<boolean>
|
||||||
_dryRun: boolean;
|
_dryRun: boolean;
|
||||||
|
|
||||||
public preferencesHandler: OsmPreferences;
|
public preferencesHandler: OsmPreferences;
|
||||||
|
@ -42,6 +43,7 @@ export class OsmConnection {
|
||||||
|
|
||||||
this.userDetails = new UIEventSource<UserDetails>(new UserDetails(), "userDetails");
|
this.userDetails = new UIEventSource<UserDetails>(new UserDetails(), "userDetails");
|
||||||
this.userDetails.data.dryRun = dryRun;
|
this.userDetails.data.dryRun = dryRun;
|
||||||
|
this.isLoggedIn = this.userDetails.map(user => user.loggedIn)
|
||||||
this._dryRun = dryRun;
|
this._dryRun = dryRun;
|
||||||
|
|
||||||
this.updateAuthObject();
|
this.updateAuthObject();
|
||||||
|
|
4
State.ts
4
State.ts
|
@ -70,10 +70,6 @@ export default class State {
|
||||||
readonly layerDef: LayerConfig;
|
readonly layerDef: LayerConfig;
|
||||||
}[]>([])
|
}[]>([])
|
||||||
|
|
||||||
/**
|
|
||||||
* The message that should be shown at the center of the screen
|
|
||||||
*/
|
|
||||||
public readonly centerMessage = new UIEventSource<string>("");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The latest element that was selected
|
The latest element that was selected
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import Locale from "../i18n/Locale";
|
import Locale from "../i18n/Locale";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export class Button extends UIElement {
|
export class Button extends UIElement {
|
||||||
private _text: UIElement;
|
private _text: BaseUIElement;
|
||||||
private _onclick: () => void;
|
private _onclick: () => void;
|
||||||
private _clss: string;
|
private _clss: string;
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
|
|
||||||
export default class FeatureSwitched extends UIElement{
|
|
||||||
private readonly _upstream: UIElement;
|
|
||||||
private readonly _swtch: UIEventSource<boolean>;
|
|
||||||
|
|
||||||
constructor(upstream :UIElement,
|
|
||||||
swtch: UIEventSource<boolean>) {
|
|
||||||
super(swtch);
|
|
||||||
this._upstream = upstream;
|
|
||||||
this._swtch = swtch;
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): UIElement | string {
|
|
||||||
if(this._swtch.data){
|
|
||||||
return this._upstream.Render();
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,10 +1,11 @@
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export class FixedUiElement extends UIElement {
|
export class FixedUiElement extends BaseUIElement {
|
||||||
private _html: string;
|
private _html: string;
|
||||||
|
|
||||||
constructor(html: string) {
|
constructor(html: string) {
|
||||||
super(undefined);
|
super();
|
||||||
this._html = html ?? "";
|
this._html = html ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,4 +13,10 @@ export class FixedUiElement extends UIElement {
|
||||||
return this._html;
|
return this._html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
const e = document.createElement("span")
|
||||||
|
e.innerHTML = this._html
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -23,6 +23,9 @@ export default class Img extends BaseUIElement {
|
||||||
protected InnerConstructElement(): HTMLElement {
|
protected InnerConstructElement(): HTMLElement {
|
||||||
const el = document.createElement("img")
|
const el = document.createElement("img")
|
||||||
el.src = this._src;
|
el.src = this._src;
|
||||||
|
el.onload = () => {
|
||||||
|
el.style.opacity = "1"
|
||||||
|
}
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
|
|
||||||
export default class LazyElement extends UIElement {
|
|
||||||
|
|
||||||
|
|
||||||
public Activate: () => void;
|
|
||||||
private _content: UIElement = undefined;
|
|
||||||
private readonly _loadingContent: string;
|
|
||||||
|
|
||||||
constructor(content: (() => UIElement), loadingContent = "Rendering...") {
|
|
||||||
super();
|
|
||||||
this._loadingContent = loadingContent;
|
|
||||||
this.dumbMode = false;
|
|
||||||
const self = this;
|
|
||||||
this.Activate = () => {
|
|
||||||
if (this._content === undefined) {
|
|
||||||
self._content = content();
|
|
||||||
}
|
|
||||||
self.Update();
|
|
||||||
// @ts-ignore
|
|
||||||
if (this._content.Activate) {
|
|
||||||
// THis is ugly - I know
|
|
||||||
// @ts-ignore
|
|
||||||
this._content.Activate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
if (this._content === undefined) {
|
|
||||||
return this._loadingContent;
|
|
||||||
}
|
|
||||||
return this._content.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -6,18 +6,18 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
export default class Link extends BaseUIElement {
|
export default class Link extends BaseUIElement {
|
||||||
private readonly _element: HTMLElement;
|
private readonly _element: HTMLElement;
|
||||||
|
|
||||||
constructor(embeddedShow: BaseUIElement | string, target: string | UIEventSource<string>, newTab: boolean = false) {
|
constructor(embeddedShow: BaseUIElement | string, href: string | UIEventSource<string>, newTab: boolean = false) {
|
||||||
super();
|
super();
|
||||||
const _embeddedShow = Translations.W(embeddedShow);
|
const _embeddedShow = Translations.W(embeddedShow);
|
||||||
|
|
||||||
|
|
||||||
const el = document.createElement("a")
|
const el = document.createElement("a")
|
||||||
|
|
||||||
if(typeof target === "string"){
|
if(typeof href === "string"){
|
||||||
el.href = target
|
el.href = href
|
||||||
}else{
|
}else{
|
||||||
target.addCallbackAndRun(target => {
|
href.addCallbackAndRun(href => {
|
||||||
el.target = target;
|
el.href = href;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
import Svg from "../../Svg";
|
|
||||||
import State from "../../State";
|
|
||||||
|
|
||||||
export default class Ornament extends UIElement {
|
export default class Ornament extends UIElement {
|
||||||
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
|
|
||||||
export default class PageSplit extends UIElement{
|
|
||||||
private _left: UIElement;
|
|
||||||
private _right: UIElement;
|
|
||||||
private _leftPercentage: number;
|
|
||||||
|
|
||||||
constructor(left: UIElement, right:UIElement,
|
|
||||||
leftPercentage: number = 50) {
|
|
||||||
super();
|
|
||||||
this._left = left;
|
|
||||||
this._right = right;
|
|
||||||
this._leftPercentage = leftPercentage;
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
return `<span class="page-split" style="height: min-content"><span style="flex:0 0 ${this._leftPercentage}%">${this._left.Render()}</span><span style="flex: 0 0 ${100-this._leftPercentage}%">${this._right.Render()}</span></span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -4,18 +4,21 @@ import BaseUIElement from "../BaseUIElement";
|
||||||
import Link from "./Link";
|
import Link from "./Link";
|
||||||
import Img from "./Img";
|
import Img from "./Img";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import {UIElement} from "../UIElement";
|
||||||
|
|
||||||
|
|
||||||
export class SubtleButton extends Combine {
|
export class SubtleButton extends UIElement {
|
||||||
|
|
||||||
|
private readonly _element: BaseUIElement
|
||||||
|
|
||||||
constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined) {
|
constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined) {
|
||||||
super(SubtleButton.generateContent(imageUrl, message, linkTo));
|
super();
|
||||||
|
this._element = SubtleButton.generateContent(imageUrl, message, linkTo)
|
||||||
this.SetClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline")
|
this.SetClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200 link-no-underline")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static generateContent(imageUrl: string | BaseUIElement, messageT: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined): (BaseUIElement )[] {
|
private static generateContent(imageUrl: string | BaseUIElement, messageT: string | BaseUIElement, linkTo: { url: string | UIEventSource<string>, newTab?: boolean } = undefined): BaseUIElement {
|
||||||
const message = Translations.W(messageT);
|
const message = Translations.W(messageT);
|
||||||
let img;
|
let img;
|
||||||
if ((imageUrl ?? "") === "") {
|
if ((imageUrl ?? "") === "") {
|
||||||
|
@ -30,15 +33,14 @@ export class SubtleButton extends Combine {
|
||||||
.SetClass("flex-shrink-0");
|
.SetClass("flex-shrink-0");
|
||||||
|
|
||||||
if (linkTo == undefined) {
|
if (linkTo == undefined) {
|
||||||
return [
|
return new Combine([
|
||||||
image,
|
image,
|
||||||
message,
|
message,
|
||||||
];
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return [
|
return new Link(
|
||||||
new Link(
|
|
||||||
new Combine([
|
new Combine([
|
||||||
image,
|
image,
|
||||||
message?.SetClass("block ml-4 overflow-ellipsis")
|
message?.SetClass("block ml-4 overflow-ellipsis")
|
||||||
|
@ -46,8 +48,10 @@ export class SubtleButton extends Combine {
|
||||||
linkTo.url,
|
linkTo.url,
|
||||||
linkTo.newTab ?? false
|
linkTo.newTab ?? false
|
||||||
)
|
)
|
||||||
];
|
}
|
||||||
|
|
||||||
|
protected InnerRender(): string | BaseUIElement {
|
||||||
|
return this._element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,33 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Combine from "./Combine";
|
import Combine from "./Combine";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {VariableUiElement} from "./VariableUIElement";
|
||||||
|
|
||||||
export class TabbedComponent extends UIElement {
|
export class TabbedComponent extends Combine {
|
||||||
|
|
||||||
private readonly header: UIElement;
|
constructor(elements: { header: BaseUIElement | string, content: BaseUIElement | string }[], openedTab: (UIEventSource<number> | number) = 0) {
|
||||||
private content: UIElement[] = [];
|
|
||||||
|
|
||||||
constructor(elements: { header: UIElement | string, content: UIElement | string }[], openedTab: (UIEventSource<number> | number) = 0) {
|
const openedTabSrc = typeof (openedTab) === "number" ? new UIEventSource(openedTab) : (openedTab ?? new UIEventSource<number>(0))
|
||||||
super(typeof (openedTab) === "number" ? new UIEventSource(openedTab) : (openedTab ?? new UIEventSource<number>(0)));
|
|
||||||
const self = this;
|
|
||||||
const tabs: UIElement[] = []
|
|
||||||
|
|
||||||
|
const tabs: BaseUIElement[] = []
|
||||||
|
const contentElements: BaseUIElement[] = [];
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
let element = elements[i];
|
let element = elements[i];
|
||||||
const header = Translations.W(element.header).onClick(() => self._source.setData(i))
|
const header = Translations.W(element.header).onClick(() => openedTabSrc.setData(i))
|
||||||
const content = Translations.W(element.content)
|
const content = Translations.W(element.content)
|
||||||
this.content.push(content);
|
content.SetClass("tab-content")
|
||||||
if (!this.content[i].IsEmpty()) {
|
contentElements.push(content);
|
||||||
const tab = header.SetClass("block tab-single-header")
|
const tab = header.SetClass("block tab-single-header")
|
||||||
tabs.push(tab)
|
tabs.push(tab)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
this.header = new Combine(tabs).SetClass("block tabs-header-bar")
|
|
||||||
|
|
||||||
|
const header = new Combine(tabs).SetClass("block tabs-header-bar")
|
||||||
|
const actualContent = new VariableUiElement(
|
||||||
|
openedTabSrc.map(i => contentElements[i])
|
||||||
|
)
|
||||||
|
super([header, actualContent])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): UIElement {
|
|
||||||
|
|
||||||
const content = this.content[this._source.data];
|
|
||||||
return new Combine([
|
|
||||||
this.header,
|
|
||||||
content.SetClass("tab-content"),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -56,16 +56,18 @@ export default abstract class BaseUIElement {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Adds all the relevant classes, space seperated
|
* Adds all the relevant classes, space seperated
|
||||||
* @param clss
|
|
||||||
* @constructor
|
|
||||||
*/
|
*/
|
||||||
public SetClass(clss: string) {
|
public SetClass(clss: string) {
|
||||||
const all = clss.split(" ").map(clsName => clsName.trim());
|
const all = clss.split(" ").map(clsName => clsName.trim());
|
||||||
let recordedChange = false;
|
let recordedChange = false;
|
||||||
for (const c of all) {
|
for (let c of all) {
|
||||||
|
c = c.trim();
|
||||||
if (this.clss.has(clss)) {
|
if (this.clss.has(clss)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if(c === undefined || c === ""){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.clss.add(c);
|
this.clss.add(c);
|
||||||
recordedChange = true;
|
recordedChange = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import Link from "../Base/Link";
|
import Link from "../Base/Link";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
|
@ -8,67 +7,57 @@ import Constants from "../../Models/Constants";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import Loc from "../../Models/Loc";
|
import Loc from "../../Models/Loc";
|
||||||
import * as L from "leaflet"
|
import * as L from "leaflet"
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bottom right attribution panel in the leaflet map
|
* The bottom right attribution panel in the leaflet map
|
||||||
*/
|
*/
|
||||||
export default class Attribution extends UIElement {
|
export default class Attribution extends Combine {
|
||||||
|
|
||||||
private readonly _location: UIEventSource<Loc>;
|
|
||||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
|
||||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
|
||||||
private readonly _leafletMap: UIEventSource<L.Map>;
|
|
||||||
|
|
||||||
constructor(location: UIEventSource<Loc>,
|
constructor(location: UIEventSource<Loc>,
|
||||||
userDetails: UIEventSource<UserDetails>,
|
userDetails: UIEventSource<UserDetails>,
|
||||||
layoutToUse: UIEventSource<LayoutConfig>,
|
layoutToUse: UIEventSource<LayoutConfig>,
|
||||||
leafletMap: UIEventSource<L.Map>) {
|
leafletMap: UIEventSource<L.Map>) {
|
||||||
super(location);
|
|
||||||
this._layoutToUse = layoutToUse;
|
|
||||||
this.ListenTo(layoutToUse);
|
|
||||||
this._userDetails = userDetails;
|
|
||||||
this._leafletMap = leafletMap;
|
|
||||||
this.ListenTo(userDetails);
|
|
||||||
this._location = location;
|
|
||||||
this.SetClass("map-attribution");
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
const location: Loc = this._location?.data;
|
|
||||||
const userDetails = this._userDetails?.data;
|
|
||||||
|
|
||||||
const mapComplete = new Link(`Mapcomplete ${Constants.vNumber}`, 'https://github.com/pietervdvn/MapComplete', true);
|
const mapComplete = new Link(`Mapcomplete ${Constants.vNumber}`, 'https://github.com/pietervdvn/MapComplete', true);
|
||||||
const reportBug = new Link(Svg.bug_img, "https://github.com/pietervdvn/MapComplete/issues", true);
|
const reportBug = new Link(Svg.bug_ui().SetClass("small-image"), "https://github.com/pietervdvn/MapComplete/issues", true);
|
||||||
|
|
||||||
const layoutId = this._layoutToUse?.data?.id;
|
const layoutId = layoutToUse?.data?.id;
|
||||||
const osmChaLink = `https://osmcha.org/?filters=%7B%22comment%22%3A%5B%7B%22label%22%3A%22%23${layoutId}%22%2C%22value%22%3A%22%23${layoutId}%22%7D%5D%2C%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22MapComplete%22%2C%22value%22%3A%22MapComplete%22%7D%5D%7D`
|
const osmChaLink = `https://osmcha.org/?filters=%7B%22comment%22%3A%5B%7B%22label%22%3A%22%23${layoutId}%22%2C%22value%22%3A%22%23${layoutId}%22%7D%5D%2C%22date__gte%22%3A%5B%7B%22label%22%3A%222020-07-05%22%2C%22value%22%3A%222020-07-05%22%7D%5D%2C%22editor%22%3A%5B%7B%22label%22%3A%22MapComplete%22%2C%22value%22%3A%22MapComplete%22%7D%5D%7D`
|
||||||
const stats = new Link(Svg.statistics_img, osmChaLink, true)
|
const stats = new Link(Svg.statistics_ui().SetClass("small-image"), osmChaLink, true)
|
||||||
let editHere: (UIElement | string) = "";
|
|
||||||
let mapillary: UIElement = undefined;
|
|
||||||
if (location !== undefined) {
|
|
||||||
const idLink = `https://www.openstreetmap.org/edit?editor=id#map=${location.zoom}/${location.lat}/${location.lon}`
|
|
||||||
editHere = new Link(Svg.pencil_img, idLink, true);
|
|
||||||
|
|
||||||
const mapillaryLink: string = `https://www.mapillary.com/app/?focus=map&lat=${location.lat}&lng=${location.lon}&z=${Math.max(location.zoom - 1, 1)}`;
|
|
||||||
mapillary = new Link(Svg.mapillary_black_img, mapillaryLink, true);
|
|
||||||
|
|
||||||
|
const idLink = location.map(location => `https://www.openstreetmap.org/edit?editor=id#map=${location?.zoom ?? 0}/${location?.lat ?? 0}/${location?.lon ?? 0}`)
|
||||||
|
const editHere = new Link(Svg.pencil_ui().SetClass("small-image"), idLink, true)
|
||||||
|
|
||||||
|
const mapillaryLink = location.map(location => `https://www.mapillary.com/app/?focus=map&lat=${location?.lat ?? 0}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}`)
|
||||||
|
const mapillary = new Link(Svg.mapillary_black_ui().SetClass("small-image"), mapillaryLink, true);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let editWithJosm = new VariableUiElement(
|
||||||
|
userDetails.map(userDetails => {
|
||||||
|
|
||||||
|
if (userDetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const bounds: any = leafletMap?.data?.getBounds();
|
||||||
|
if(bounds === undefined){
|
||||||
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let editWithJosm: (UIElement | string) = ""
|
|
||||||
if (location !== undefined &&
|
|
||||||
this._leafletMap?.data !== undefined &&
|
|
||||||
userDetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) {
|
|
||||||
const bounds: any = this._leafletMap.data.getBounds();
|
|
||||||
const top = bounds.getNorth();
|
const top = bounds.getNorth();
|
||||||
const bottom = bounds.getSouth();
|
const bottom = bounds.getSouth();
|
||||||
const right = bounds.getEast();
|
const right = bounds.getEast();
|
||||||
const left = bounds.getWest();
|
const left = bounds.getWest();
|
||||||
|
|
||||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||||
editWithJosm = new Link(Svg.josm_logo_img, josmLink, true);
|
return new Link(Svg.josm_logo_ui().SetClass("small-image"), josmLink, true);
|
||||||
}
|
},
|
||||||
return new Combine([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary]).Render();
|
[location]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
super([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,8 @@ import SmallLicense from "../../Models/smallLicense";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import Link from "../Base/Link";
|
import Link from "../Base/Link";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import * as contributors from "../../assets/contributors.json"
|
import * as contributors from "../../assets/contributors.json"
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attribution panel shown on mobile
|
* The attribution panel shown on mobile
|
||||||
|
@ -26,7 +26,7 @@ export default class AttributionPanel extends Combine {
|
||||||
((layoutToUse.data.maintainer ?? "") == "") ? "" : Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.data.maintainer}),
|
((layoutToUse.data.maintainer ?? "") == "") ? "" : Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.data.maintainer}),
|
||||||
layoutToUse.data.credits,
|
layoutToUse.data.credits,
|
||||||
"<br/>",
|
"<br/>",
|
||||||
new Attribution(undefined, undefined, State.state.layoutToUse, undefined),
|
new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse, State.state.leafletMap),
|
||||||
"<br/>",
|
"<br/>",
|
||||||
|
|
||||||
new VariableUiElement(contributions.map(contributions => {
|
new VariableUiElement(contributions.map(contributions => {
|
||||||
|
@ -66,7 +66,7 @@ export default class AttributionPanel extends Combine {
|
||||||
this.SetStyle("max-width: calc(100vw - 5em); width: 40em;")
|
this.SetStyle("max-width: calc(100vw - 5em); width: 40em;")
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CodeContributors(): UIElement {
|
private static CodeContributors(): BaseUIElement {
|
||||||
|
|
||||||
const total = contributors.contributors.length;
|
const total = contributors.contributors.length;
|
||||||
let filtered = contributors.contributors
|
let filtered = contributors.contributors
|
||||||
|
@ -87,7 +87,7 @@ export default class AttributionPanel extends Combine {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IconAttribution(iconPath: string): UIElement {
|
private static IconAttribution(iconPath: string): BaseUIElement {
|
||||||
if (iconPath.startsWith("http")) {
|
if (iconPath.startsWith("http")) {
|
||||||
iconPath = "." + new URL(iconPath).pathname;
|
iconPath = "." + new URL(iconPath).pathname;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,35 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {DropDown} from "../Input/DropDown";
|
import {DropDown} from "../Input/DropDown";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
import BaseLayer from "../../Models/BaseLayer";
|
import BaseLayer from "../../Models/BaseLayer";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
|
||||||
export default class BackgroundSelector extends UIElement {
|
export default class BackgroundSelector extends VariableUiElement {
|
||||||
|
|
||||||
private _dropdown: BaseUIElement;
|
|
||||||
private readonly _availableLayers: UIEventSource<BaseLayer[]>;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
const available = State.state.availableBackgroundLayers.map(available => {
|
||||||
const self = this;
|
|
||||||
this._availableLayers = State.state.availableBackgroundLayers;
|
|
||||||
this._availableLayers.addCallbackAndRun(available => self.CreateDropDown(available));
|
|
||||||
}
|
|
||||||
|
|
||||||
private CreateDropDown(available) {
|
|
||||||
if(available.length === 0){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseLayers: { value: BaseLayer, shown: string }[] = [];
|
const baseLayers: { value: BaseLayer, shown: string }[] = [];
|
||||||
for (const i in available) {
|
for (const i in available) {
|
||||||
|
if(!available.hasOwnProperty(i)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const layer: BaseLayer = available[i];
|
const layer: BaseLayer = available[i];
|
||||||
baseLayers.push({value: layer, shown: layer.name ?? "id:" + layer.id});
|
baseLayers.push({value: layer, shown: layer.name ?? "id:" + layer.id});
|
||||||
}
|
}
|
||||||
|
return baseLayers
|
||||||
this._dropdown = new DropDown(Translations.t.general.backgroundMap, baseLayers, State.state.backgroundLayer);
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
super(
|
||||||
|
available.map(baseLayers => {
|
||||||
|
if (baseLayers.length <= 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return new DropDown(Translations.t.general.backgroundMap, baseLayers, State.state.backgroundLayer)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
InnerRender(): BaseUIElement {
|
|
||||||
return this._dropdown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
import * as L from "leaflet"
|
import * as L from "leaflet"
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Loc from "../../Models/Loc";
|
import Loc from "../../Models/Loc";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import BaseLayer from "../../Models/BaseLayer";
|
import BaseLayer from "../../Models/BaseLayer";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export class Basemap {
|
export class Basemap {
|
||||||
|
|
||||||
|
@ -13,13 +13,12 @@ export class Basemap {
|
||||||
location: UIEventSource<Loc>,
|
location: UIEventSource<Loc>,
|
||||||
currentLayer: UIEventSource<BaseLayer>,
|
currentLayer: UIEventSource<BaseLayer>,
|
||||||
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
|
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
|
||||||
extraAttribution: UIElement) {
|
extraAttribution: BaseUIElement) {
|
||||||
this.map = L.map(leafletElementId, {
|
this.map = L.map(leafletElementId, {
|
||||||
center: [location.data.lat ?? 0, location.data.lon ?? 0],
|
center: [location.data.lat ?? 0, location.data.lon ?? 0],
|
||||||
zoom: location.data.zoom ?? 2,
|
zoom: location.data.zoom ?? 2,
|
||||||
layers: [currentLayer.data.layer],
|
layers: [currentLayer.data.layer],
|
||||||
zoomControl: false
|
zoomControl: false,
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
L.control.scale(
|
L.control.scale(
|
||||||
|
@ -36,7 +35,9 @@ export class Basemap {
|
||||||
[[-100, -200], [100, 200]]
|
[[-100, -200], [100, 200]]
|
||||||
);
|
);
|
||||||
this.map.attributionControl.setPrefix(
|
this.map.attributionControl.setPrefix(
|
||||||
extraAttribution.Render() + " | <a href='https://osm.org'>OpenStreetMap</a>");
|
"<span id='leaflet-attribution'></span> | <a href='https://osm.org'>OpenStreetMap</a>");
|
||||||
|
|
||||||
|
extraAttribution.AttachTo('leaflet-attribution')
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
|
|
|
@ -16,21 +16,14 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class FullWelcomePaneWithTabs extends UIElement {
|
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
|
||||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
|
||||||
|
|
||||||
private readonly _component: UIElement;
|
|
||||||
|
|
||||||
constructor(isShown: UIEventSource<boolean>) {
|
constructor(isShown: UIEventSource<boolean>) {
|
||||||
super(State.state.layoutToUse);
|
const layoutToUse = State.state.layoutToUse.data;
|
||||||
this._layoutToUse = State.state.layoutToUse;
|
super (
|
||||||
this._userDetails = State.state.osmConnection.userDetails;
|
|
||||||
const layoutToUse = this._layoutToUse.data;
|
|
||||||
|
|
||||||
|
|
||||||
this._component = new ScrollableFullScreen(
|
|
||||||
() => layoutToUse.title.Clone(),
|
() => layoutToUse.title.Clone(),
|
||||||
() => FullWelcomePaneWithTabs.GenerateContents(layoutToUse, State.state.osmConnection.userDetails),
|
() => FullWelcomePaneWithTabs.GenerateContents(layoutToUse, State.state.osmConnection.userDetails),
|
||||||
"welcome" ,isShown
|
"welcome" ,isShown
|
||||||
|
@ -43,11 +36,11 @@ export default class FullWelcomePaneWithTabs extends UIElement {
|
||||||
if (layoutToUse.id === personal.id) {
|
if (layoutToUse.id === personal.id) {
|
||||||
welcome = new PersonalLayersPanel();
|
welcome = new PersonalLayersPanel();
|
||||||
}
|
}
|
||||||
const tabs = [
|
const tabs : {header: string | BaseUIElement, content: BaseUIElement}[] = [
|
||||||
{header: `<img src='${layoutToUse.icon}'>`, content: welcome},
|
{header: `<img src='${layoutToUse.icon}'>`, content: welcome},
|
||||||
{
|
{
|
||||||
header: Svg.osm_logo_img,
|
header: Svg.osm_logo_img,
|
||||||
content: Translations.t.general.openStreetMapIntro.Clone().SetClass("link-underline") as UIElement
|
content: Translations.t.general.openStreetMapIntro.Clone().SetClass("link-underline")
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -71,18 +64,13 @@ export default class FullWelcomePaneWithTabs extends UIElement {
|
||||||
if (userdetails.csCount < Constants.userJourney.mapCompleteHelpUnlock) {
|
if (userdetails.csCount < Constants.userJourney.mapCompleteHelpUnlock) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return new Combine([Translations.t.general.aboutMapcomplete, "<br/>Version " + Constants.vNumber]).SetClass("link-underline").Render();
|
return new Combine([Translations.t.general.aboutMapcomplete, "<br/>Version " + Constants.vNumber]).SetClass("link-underline");
|
||||||
}, [Locale.language]))
|
}, [Locale.language]))
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab)
|
return new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab);
|
||||||
.ListenTo(userDetails);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): UIElement {
|
|
||||||
return this._component;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,49 +1,36 @@
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the panel with all layers and a toggle for each of them
|
* Shows the panel with all layers and a toggle for each of them
|
||||||
*/
|
*/
|
||||||
export default class LayerSelection extends UIElement {
|
export default class LayerSelection extends Combine {
|
||||||
|
|
||||||
private _checkboxes: UIElement[];
|
|
||||||
private activeLayers: UIEventSource<{
|
|
||||||
readonly isDisplayed: UIEventSource<boolean>,
|
|
||||||
readonly layerDef: LayerConfig;
|
|
||||||
}[]>;
|
|
||||||
|
|
||||||
constructor(activeLayers: UIEventSource<{
|
constructor(activeLayers: UIEventSource<{
|
||||||
readonly isDisplayed: UIEventSource<boolean>,
|
readonly isDisplayed: UIEventSource<boolean>,
|
||||||
readonly layerDef: LayerConfig;
|
readonly layerDef: LayerConfig;
|
||||||
}[]>) {
|
}[]>) {
|
||||||
super(activeLayers);
|
|
||||||
if (activeLayers === undefined) {
|
if (activeLayers === undefined) {
|
||||||
throw "ActiveLayers should be defined..."
|
throw "ActiveLayers should be defined..."
|
||||||
}
|
}
|
||||||
this.activeLayers = activeLayers;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
const checkboxes: BaseUIElement[] = [];
|
||||||
|
|
||||||
this._checkboxes = [];
|
for (const layer of activeLayers.data) {
|
||||||
|
|
||||||
for (const layer of this.activeLayers.data) {
|
|
||||||
const leafletStyle = layer.layerDef.GenerateLeafletStyle(
|
const leafletStyle = layer.layerDef.GenerateLeafletStyle(
|
||||||
new UIEventSource<any>({id: "node/-1"}),
|
new UIEventSource<any>({id: "node/-1"}),
|
||||||
false)
|
false)
|
||||||
const leafletHtml = leafletStyle.icon.html;
|
const icon = new Combine([leafletStyle.icon.html]).SetClass("single-layer-selection-toggle")
|
||||||
const icon =
|
let iconUnselected: BaseUIElement = new Combine([leafletStyle.icon.html])
|
||||||
new FixedUiElement(leafletHtml.Render())
|
|
||||||
.SetClass("single-layer-selection-toggle")
|
|
||||||
let iconUnselected: UIElement = new FixedUiElement(leafletHtml.Render())
|
|
||||||
.SetClass("single-layer-selection-toggle")
|
.SetClass("single-layer-selection-toggle")
|
||||||
.SetStyle("opacity:0.2;");
|
.SetStyle("opacity:0.2;");
|
||||||
|
|
||||||
|
@ -59,13 +46,12 @@ export default class LayerSelection extends UIElement {
|
||||||
return Translations.t.general.layerSelection.zoomInToSeeThisLayer
|
return Translations.t.general.layerSelection.zoomInToSeeThisLayer
|
||||||
.SetClass("alert")
|
.SetClass("alert")
|
||||||
.SetStyle("display: block ruby;width:min-content;")
|
.SetStyle("display: block ruby;width:min-content;")
|
||||||
.Render();
|
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}))
|
}))
|
||||||
const style = "display:flex;align-items:center;"
|
const style = "display:flex;align-items:center;"
|
||||||
const styleWhole = "display:flex; flex-wrap: wrap"
|
const styleWhole = "display:flex; flex-wrap: wrap"
|
||||||
this._checkboxes.push(new Toggle(
|
checkboxes.push(new Toggle(
|
||||||
new Combine([new Combine([icon, name]).SetStyle(style), zoomStatus])
|
new Combine([new Combine([icon, name]).SetStyle(style), zoomStatus])
|
||||||
.SetStyle(styleWhole),
|
.SetStyle(styleWhole),
|
||||||
new Combine([new Combine([iconUnselected, "<del>", name, "</del>"]).SetStyle(style), zoomStatus])
|
new Combine([new Combine([iconUnselected, "<del>", name, "</del>"]).SetStyle(style), zoomStatus])
|
||||||
|
@ -76,9 +62,8 @@ export default class LayerSelection extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new Combine(this._checkboxes)
|
super(checkboxes)
|
||||||
.SetStyle("display:flex;flex-direction:column;")
|
this.SetStyle("display:flex;flex-direction:column;")
|
||||||
.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
|
@ -132,8 +132,16 @@ export default class MoreScreen extends Combine {
|
||||||
linkSuffix = `#${customThemeDefinition}`
|
linkSuffix = `#${customThemeDefinition}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const linkText = currentLocation.map(currentLocation =>
|
const linkText = currentLocation.map(currentLocation => {
|
||||||
`${linkPrefix}z=${currentLocation.zoom ?? 1}&lat=${currentLocation.lat ?? 0}&lon=${currentLocation.lon ?? 0}${linkSuffix}`)
|
const params = [
|
||||||
|
["z", currentLocation?.zoom],
|
||||||
|
["lat", currentLocation?.lat],
|
||||||
|
["lon",currentLocation?.lon]
|
||||||
|
].filter(part => part[1] !== undefined)
|
||||||
|
.map(part => part[0]+"="+part[1])
|
||||||
|
.join("&")
|
||||||
|
return `${linkPrefix}${params}${linkSuffix}`;
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,13 @@ import State from "../../State";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import {SubtleButton} from "../Base/SubtleButton";
|
import {SubtleButton} from "../Base/SubtleButton";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import * as personal from "../../assets/themes/personalLayout/personalLayout.json"
|
import * as personal from "../../assets/themes/personalLayout/personalLayout.json"
|
||||||
import Locale from "../i18n/Locale";
|
import Locale from "../i18n/Locale";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class PersonalLayersPanel extends UIElement {
|
export default class PersonalLayersPanel extends UIElement {
|
||||||
private checkboxes: UIElement[] = [];
|
private checkboxes: BaseUIElement[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(State.state.favouriteLayers);
|
super(State.state.favouriteLayers);
|
||||||
|
@ -60,9 +61,9 @@ export default class PersonalLayersPanel extends UIElement {
|
||||||
if (typeof layer === "string") {
|
if (typeof layer === "string") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let icon :UIElement = layer.GenerateLeafletStyle(new UIEventSource<any>({id:"node/-1"}), false).icon.html
|
let icon :BaseUIElement = layer.GenerateLeafletStyle(new UIEventSource<any>({id:"node/-1"}), false).icon.html
|
||||||
?? Svg.checkmark_svg();
|
?? Svg.checkmark_svg();
|
||||||
let iconUnset =new FixedUiElement(icon.Render());
|
let iconUnset =new Combine([icon]);
|
||||||
icon.SetClass("single-layer-selection-toggle")
|
icon.SetClass("single-layer-selection-toggle")
|
||||||
iconUnset.SetClass("single-layer-selection-toggle")
|
iconUnset.SetClass("single-layer-selection-toggle")
|
||||||
|
|
||||||
|
@ -121,17 +122,17 @@ export default class PersonalLayersPanel extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): BaseUIElement {
|
||||||
const t = Translations.t.favourite;
|
const t = Translations.t.favourite;
|
||||||
const userDetails = State.state.osmConnection.userDetails.data;
|
return new Toggle(
|
||||||
if(!userDetails.loggedIn){
|
new Combine([
|
||||||
return t.loginNeeded.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Combine([
|
|
||||||
t.panelIntro,
|
t.panelIntro,
|
||||||
...this.checkboxes
|
...this.checkboxes
|
||||||
]).Render();
|
]),
|
||||||
|
t.loginNeeded,
|
||||||
|
State.state.osmConnection.isLoggedIn
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Locale from "../i18n/Locale";
|
import Locale from "../i18n/Locale";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {Translation} from "../i18n/Translation";
|
import {Translation} from "../i18n/Translation";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
|
@ -10,59 +9,51 @@ import {Geocoding} from "../../Logic/Osm/Geocoding";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Hash from "../../Logic/Web/Hash";
|
import Hash from "../../Logic/Web/Hash";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
|
||||||
|
|
||||||
export default class SearchAndGo extends UIElement {
|
export default class SearchAndGo extends Combine {
|
||||||
|
|
||||||
private readonly _placeholder = new UIEventSource<Translation>(Translations.t.general.search.search)
|
constructor() {
|
||||||
private readonly _searchField = new TextField({
|
const goButton = Svg.search_ui().SetClass('w-8 h-8 full-rounded border-black float-right');
|
||||||
|
|
||||||
|
const placeholder = new UIEventSource<Translation>(Translations.t.general.search.search)
|
||||||
|
const searchField = new TextField({
|
||||||
placeholder: new VariableUiElement(
|
placeholder: new VariableUiElement(
|
||||||
this._placeholder.map(uiElement => uiElement.InnerRender(), [Locale.language])
|
placeholder.map(uiElement => uiElement, [Locale.language])
|
||||||
),
|
),
|
||||||
value: new UIEventSource<string>("")
|
value: new UIEventSource<string>(""),
|
||||||
|
|
||||||
|
inputStyle: " background: transparent;\n" +
|
||||||
|
" border: none;\n" +
|
||||||
|
" font-size: large;\n" +
|
||||||
|
" width: 100%;\n" +
|
||||||
|
" box-sizing: border-box;\n" +
|
||||||
|
" color: var(--foreground-color);"
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private readonly _foundEntries = new UIEventSource([]);
|
searchField.SetClass("relative float-left mt-0 ml-2")
|
||||||
private readonly _goButton = Svg.search_ui().SetClass('w-8 h-8 full-rounded border-black float-right');
|
searchField.SetStyle("width: calc(100% - 3em)")
|
||||||
private readonly _element: Combine;
|
|
||||||
|
|
||||||
constructor() {
|
super([searchField, goButton])
|
||||||
super(undefined);
|
|
||||||
this.ListenTo(this._foundEntries);
|
|
||||||
|
|
||||||
const self = this;
|
this.SetClass("block h-8")
|
||||||
this._searchField.enterPressed.addCallback(() => {
|
this.SetStyle("background: var(--background-color); color: var(--foreground-color); pointer-evetns:all;")
|
||||||
self.RunSearch();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._goButton.onClick(function () {
|
|
||||||
self.RunSearch();
|
|
||||||
});
|
|
||||||
this._element = new Combine([this._searchField, this._goButton])
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): BaseUIElement
|
|
||||||
{
|
|
||||||
return this._element
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Triggered by 'enter' or onclick
|
// Triggered by 'enter' or onclick
|
||||||
private RunSearch() {
|
function runSearch() {
|
||||||
const searchString = this._searchField.GetValue().data;
|
const searchString = searchField.GetValue().data;
|
||||||
if (searchString === undefined || searchString === "") {
|
if (searchString === undefined || searchString === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._searchField.GetValue().setData("");
|
searchField.GetValue().setData("");
|
||||||
this._placeholder.setData(Translations.t.general.search.searching);
|
placeholder.setData(Translations.t.general.search.searching);
|
||||||
const self = this;
|
|
||||||
Geocoding.Search(searchString, (result) => {
|
Geocoding.Search(searchString, (result) => {
|
||||||
|
|
||||||
console.log("Search result", result)
|
console.log("Search result", result)
|
||||||
if (result.length == 0) {
|
if (result.length == 0) {
|
||||||
self._placeholder.setData(Translations.t.general.search.nothing);
|
placeholder.setData(Translations.t.general.search.nothing);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +66,19 @@ export default class SearchAndGo extends UIElement {
|
||||||
State.state.selectedElement.setData(undefined);
|
State.state.selectedElement.setData(undefined);
|
||||||
Hash.hash.setData(poi.osm_type + "/" + poi.osm_id);
|
Hash.hash.setData(poi.osm_type + "/" + poi.osm_id);
|
||||||
State.state.leafletMap.data.fitBounds(bounds);
|
State.state.leafletMap.data.fitBounds(bounds);
|
||||||
self._placeholder.setData(Translations.t.general.search.search);
|
placeholder.setData(Translations.t.general.search.search);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
self._searchField.GetValue().setData("");
|
searchField.GetValue().setData("");
|
||||||
self._placeholder.setData(Translations.t.general.search.error);
|
placeholder.setData(Translations.t.general.search.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
searchField.enterPressed.addCallback(runSearch);
|
||||||
|
goButton.onClick(runSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import {UIElement} from "../UIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class ShareButton extends UIElement{
|
export default class ShareButton extends BaseUIElement{
|
||||||
private _embedded: UIElement;
|
private _embedded: BaseUIElement;
|
||||||
private _shareData: { text: string; title: string; url: string };
|
private _shareData: { text: string; title: string; url: string };
|
||||||
|
|
||||||
constructor(embedded: UIElement, shareData: {
|
constructor(embedded: BaseUIElement, shareData: {
|
||||||
text: string,
|
text: string,
|
||||||
title: string,
|
title: string,
|
||||||
url: string
|
url: string
|
||||||
|
@ -12,17 +12,17 @@ export default class ShareButton extends UIElement{
|
||||||
super();
|
super();
|
||||||
this._embedded = embedded;
|
this._embedded = embedded;
|
||||||
this._shareData = shareData;
|
this._shareData = shareData;
|
||||||
|
this.SetClass("share-button")
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
protected InnerConstructElement(): HTMLElement {
|
||||||
return `<button type="button" class="share-button" id="${this.id}">${this._embedded.Render()}</button>`
|
const e = document.createElement("button")
|
||||||
}
|
e.type = "button"
|
||||||
|
e.appendChild(this._embedded.ConstructElement())
|
||||||
|
|
||||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
e.addEventListener('click', () => {
|
||||||
const self= this;
|
|
||||||
htmlElement.addEventListener('click', () => {
|
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
navigator.share(self._shareData).then(() => {
|
navigator.share(this._shareData).then(() => {
|
||||||
console.log('Thanks for sharing!');
|
console.log('Thanks for sharing!');
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -32,6 +32,9 @@ export default class ShareButton extends UIElement{
|
||||||
console.log('web share not supported');
|
console.log('web share not supported');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -8,20 +8,20 @@ import Svg from "../../Svg";
|
||||||
import {SubtleButton} from "../Base/SubtleButton";
|
import {SubtleButton} from "../Base/SubtleButton";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
import {Tag} from "../../Logic/Tags/Tag";
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class SimpleAddUI extends UIElement {
|
export default class SimpleAddUI extends UIElement {
|
||||||
private readonly _loginButton: UIElement;
|
private readonly _loginButton: BaseUIElement;
|
||||||
|
|
||||||
private readonly _confirmPreset: UIEventSource<{
|
private readonly _confirmPreset: UIEventSource<{
|
||||||
description: string | UIElement,
|
description: string | BaseUIElement,
|
||||||
name: string | UIElement,
|
name: string | BaseUIElement,
|
||||||
icon: UIElement,
|
icon: BaseUIElement,
|
||||||
tags: Tag[],
|
tags: Tag[],
|
||||||
layerToAddTo: {
|
layerToAddTo: {
|
||||||
layerDef: LayerConfig,
|
layerDef: LayerConfig,
|
||||||
|
@ -30,11 +30,11 @@ export default class SimpleAddUI extends UIElement {
|
||||||
}>
|
}>
|
||||||
= new UIEventSource(undefined);
|
= new UIEventSource(undefined);
|
||||||
|
|
||||||
private _component: UIElement;
|
private _component:BaseUIElement;
|
||||||
|
|
||||||
private readonly openLayerControl: UIElement;
|
private readonly openLayerControl: BaseUIElement;
|
||||||
private readonly cancelButton: UIElement;
|
private readonly cancelButton: BaseUIElement;
|
||||||
private readonly goToInboxButton: UIElement = new SubtleButton(Svg.envelope_ui(),
|
private readonly goToInboxButton: BaseUIElement = new SubtleButton(Svg.envelope_ui(),
|
||||||
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(isShown: UIEventSource<boolean>) {
|
constructor(isShown: UIEventSource<boolean>) {
|
||||||
|
@ -75,16 +75,15 @@ export default class SimpleAddUI extends UIElement {
|
||||||
State.state.LastClickLocation.addCallback(() => {
|
State.state.LastClickLocation.addCallback(() => {
|
||||||
self._confirmPreset.setData(undefined)
|
self._confirmPreset.setData(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
this._component = this.CreateContent();
|
this._component = this.CreateContent();
|
||||||
return this._component.Render();
|
}
|
||||||
|
|
||||||
|
InnerRender(): BaseUIElement {
|
||||||
|
return this._component;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreatePresetsPanel(): UIElement {
|
private CreatePresetsPanel(): BaseUIElement {
|
||||||
const userDetails = State.state.osmConnection.userDetails;
|
const userDetails = State.state.osmConnection.userDetails;
|
||||||
if (userDetails === undefined) {
|
if (userDetails === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -121,21 +120,17 @@ export default class SimpleAddUI extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private CreateContent(): UIElement {
|
private CreateContent(): BaseUIElement {
|
||||||
const confirmPanel = this.CreateConfirmPanel();
|
const confirmPanel = this.CreateConfirmPanel();
|
||||||
if (confirmPanel !== undefined) {
|
if (confirmPanel !== undefined) {
|
||||||
return confirmPanel;
|
return confirmPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
let intro: UIElement = Translations.t.general.add.intro;
|
let intro: BaseUIElement = Translations.t.general.add.intro;
|
||||||
|
|
||||||
let testMode: UIElement = undefined;
|
let testMode: BaseUIElement = undefined;
|
||||||
if (State.state.osmConnection?.userDetails?.data?.dryRun) {
|
if (State.state.osmConnection?.userDetails?.data?.dryRun) {
|
||||||
testMode = new Combine([
|
testMode = Translations.t.general.testing.Clone().SetClass("alert")
|
||||||
"<span class='alert'>",
|
|
||||||
"Test mode - changes won't be saved",
|
|
||||||
"</span>"
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let presets = this.CreatePresetsPanel();
|
let presets = this.CreatePresetsPanel();
|
||||||
|
@ -144,7 +139,7 @@ export default class SimpleAddUI extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreateConfirmPanel(): UIElement {
|
private CreateConfirmPanel(): BaseUIElement {
|
||||||
const preset = this._confirmPreset.data;
|
const preset = this._confirmPreset.data;
|
||||||
if (preset === undefined) {
|
if (preset === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -195,7 +190,7 @@ export default class SimpleAddUI extends UIElement {
|
||||||
const presets = layer.layerDef.presets;
|
const presets = layer.layerDef.presets;
|
||||||
for (const preset of presets) {
|
for (const preset of presets) {
|
||||||
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
const tags = TagUtils.KVtoProperties(preset.tags ?? []);
|
||||||
let icon: UIElement = new FixedUiElement(layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html.Render()).SetClass("simple-add-ui-icon");
|
let icon: BaseUIElement = layer.layerDef.GenerateLeafletStyle(new UIEventSource<any>(tags), false).icon.html.SetClass("simple-add-ui-icon");
|
||||||
|
|
||||||
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
||||||
let tagInfo = undefined;
|
let tagInfo = undefined;
|
||||||
|
|
|
@ -1,27 +1,19 @@
|
||||||
import Locale from "../i18n/Locale";
|
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import LanguagePicker from "../LanguagePicker";
|
import LanguagePicker from "../LanguagePicker";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import Toggle from "../Input/Toggle";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
import BaseUIElement from "../BaseUIElement";
|
|
||||||
|
|
||||||
export default class ThemeIntroductionPanel extends UIElement {
|
|
||||||
private languagePicker: UIElement;
|
|
||||||
|
|
||||||
private readonly loginStatus: UIElement;
|
|
||||||
private _layout: UIEventSource<LayoutConfig>;
|
|
||||||
|
|
||||||
|
export default class ThemeIntroductionPanel extends VariableUiElement {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(State.state.osmConnection.userDetails);
|
|
||||||
this.ListenTo(Locale.language);
|
const languagePicker =
|
||||||
this.languagePicker = LanguagePicker.CreateLanguagePicker(State.state.layoutToUse.data.language, Translations.t.general.pickLanguage);
|
new VariableUiElement(
|
||||||
this._layout = State.state.layoutToUse;
|
State.state.layoutToUse.map(layout => LanguagePicker.CreateLanguagePicker(layout.language, Translations.t.general.pickLanguage))
|
||||||
this.ListenTo(State.state.layoutToUse);
|
)
|
||||||
|
;
|
||||||
|
|
||||||
const plzLogIn =
|
const plzLogIn =
|
||||||
Translations.t.general.loginWithOpenStreetMap
|
Translations.t.general.loginWithOpenStreetMap
|
||||||
|
@ -32,31 +24,28 @@ export default class ThemeIntroductionPanel extends UIElement {
|
||||||
|
|
||||||
const welcomeBack = Translations.t.general.welcomeBack;
|
const welcomeBack = Translations.t.general.welcomeBack;
|
||||||
|
|
||||||
this.loginStatus = new VariableUiElement(
|
const loginStatus =
|
||||||
State.state.osmConnection.userDetails.map(
|
new Toggle(
|
||||||
userdetails => {
|
new Toggle(
|
||||||
if (State.state.featureSwitchUserbadge.data) {
|
welcomeBack,
|
||||||
return "";
|
plzLogIn,
|
||||||
}
|
State.state.osmConnection.isLoggedIn
|
||||||
return (userdetails.loggedIn ? welcomeBack : plzLogIn).Render();
|
),
|
||||||
}
|
undefined,
|
||||||
|
State.state.featureSwitchUserbadge
|
||||||
)
|
)
|
||||||
)
|
|
||||||
this.SetClass("link-underline")
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): BaseUIElement {
|
|
||||||
const layout : LayoutConfig = this._layout.data;
|
super(State.state.layoutToUse.map (layout => new Combine([
|
||||||
return new Combine([
|
|
||||||
layout.description,
|
layout.description,
|
||||||
"<br/><br/>",
|
"<br/><br/>",
|
||||||
this.loginStatus,
|
loginStatus,
|
||||||
layout.descriptionTail,
|
layout.descriptionTail,
|
||||||
"<br/>",
|
"<br/>",
|
||||||
this.languagePicker,
|
languagePicker,
|
||||||
...layout.CustomCodeSnippets()
|
...layout.CustomCodeSnippets()
|
||||||
])
|
])))
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
this.SetClass("link-underline")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Handles and updates the user badge
|
* Handles and updates the user badge
|
||||||
*/
|
*/
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
|
@ -12,45 +9,35 @@ import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import LanguagePicker from "../LanguagePicker";
|
import LanguagePicker from "../LanguagePicker";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Link from "../Base/Link";
|
import Link from "../Base/Link";
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
import Img from "../Base/Img";
|
||||||
|
|
||||||
export default class UserBadge extends UIElement {
|
export default class UserBadge extends Toggle {
|
||||||
private _userDetails: UIEventSource<UserDetails>;
|
|
||||||
private _logout: UIElement;
|
|
||||||
private _homeButton: UIElement;
|
|
||||||
private _languagePicker: UIElement;
|
|
||||||
|
|
||||||
private _loginButton: UIElement;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(State.state.osmConnection.userDetails);
|
|
||||||
this._userDetails = State.state.osmConnection.userDetails;
|
|
||||||
this._languagePicker = (LanguagePicker.CreateLanguagePicker(State.state.layoutToUse.data.language) ?? new FixedUiElement(""))
|
|
||||||
.SetStyle("width:min-content;");
|
|
||||||
|
|
||||||
this._loginButton = Translations.t.general.loginWithOpenStreetMap
|
|
||||||
|
const userDetails = State.state.osmConnection.userDetails;
|
||||||
|
|
||||||
|
const loginButton = Translations.t.general.loginWithOpenStreetMap
|
||||||
.Clone()
|
.Clone()
|
||||||
.SetClass("userbadge-login pt-3 w-full")
|
.SetClass("userbadge-login pt-3 w-full")
|
||||||
.onClick(() => State.state.osmConnection.AttemptLogin());
|
.onClick(() => State.state.osmConnection.AttemptLogin());
|
||||||
this._logout =
|
|
||||||
|
|
||||||
|
const logout =
|
||||||
Svg.logout_svg()
|
Svg.logout_svg()
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
State.state.osmConnection.LogOut();
|
State.state.osmConnection.LogOut();
|
||||||
});
|
});
|
||||||
|
|
||||||
this._userDetails.addCallback(function () {
|
|
||||||
const profilePic = document.getElementById("profile-pic");
|
|
||||||
if (profilePic) {
|
|
||||||
|
|
||||||
profilePic.onload = function () {
|
const userBadge = userDetails.map(user => {
|
||||||
profilePic.style.opacity = "1"
|
{
|
||||||
};
|
const homeButton = new VariableUiElement(
|
||||||
}
|
userDetails.map((userinfo) => {
|
||||||
});
|
|
||||||
|
|
||||||
this._homeButton = new VariableUiElement(
|
|
||||||
this._userDetails.map((userinfo) => {
|
|
||||||
if (userinfo.home) {
|
if (userinfo.home) {
|
||||||
return Svg.home_ui().Render();
|
return Svg.home_ui();
|
||||||
}
|
}
|
||||||
return " ";
|
return " ";
|
||||||
})
|
})
|
||||||
|
@ -62,17 +49,11 @@ export default class UserBadge extends UIElement {
|
||||||
State.state.leafletMap.data.setView([home.lat, home.lon], 16);
|
State.state.leafletMap.data.setView([home.lat, home.lon], 16);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): UIElement {
|
|
||||||
const user = this._userDetails.data;
|
|
||||||
if (!user.loggedIn) {
|
|
||||||
return this._loginButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
const linkStyle = "flex items-baseline"
|
const linkStyle = "flex items-baseline"
|
||||||
|
const languagePicker = (LanguagePicker.CreateLanguagePicker(State.state.layoutToUse.data.language) ?? new FixedUiElement(""))
|
||||||
|
.SetStyle("width:min-content;");
|
||||||
|
|
||||||
let messageSpan: UIElement =
|
let messageSpan =
|
||||||
new Link(
|
new Link(
|
||||||
new Combine([Svg.envelope, "" + user.totalMessages]).SetClass(linkStyle),
|
new Combine([Svg.envelope, "" + user.totalMessages]).SetClass(linkStyle),
|
||||||
'https://www.openstreetmap.org/messages/inbox',
|
'https://www.openstreetmap.org/messages/inbox',
|
||||||
|
@ -95,7 +76,7 @@ export default class UserBadge extends UIElement {
|
||||||
).SetClass("alert")
|
).SetClass("alert")
|
||||||
}
|
}
|
||||||
|
|
||||||
let dryrun: UIElement = new FixedUiElement("");
|
let dryrun = new FixedUiElement("");
|
||||||
if (user.dryRun) {
|
if (user.dryRun) {
|
||||||
dryrun = new FixedUiElement("TESTING").SetClass("alert");
|
dryrun = new FixedUiElement("TESTING").SetClass("alert");
|
||||||
}
|
}
|
||||||
|
@ -107,7 +88,9 @@ export default class UserBadge extends UIElement {
|
||||||
|
|
||||||
|
|
||||||
const userIcon = new Link(
|
const userIcon = new Link(
|
||||||
new FixedUiElement(`<img id='profile-pic' src='${user.img}' alt='profile-pic'/>`),
|
new Img(user.img)
|
||||||
|
.SetClass("rounded-full opacity-0 m-0 p-0 duration-500 w-16 h16 float-left")
|
||||||
|
,
|
||||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}`,
|
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}`,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@ -120,12 +103,12 @@ export default class UserBadge extends UIElement {
|
||||||
|
|
||||||
|
|
||||||
const userStats = new Combine([
|
const userStats = new Combine([
|
||||||
this._homeButton,
|
homeButton,
|
||||||
settings,
|
settings,
|
||||||
messageSpan,
|
messageSpan,
|
||||||
csCount,
|
csCount,
|
||||||
this._languagePicker,
|
languagePicker,
|
||||||
this._logout
|
logout
|
||||||
])
|
])
|
||||||
.SetClass("userstats")
|
.SetClass("userstats")
|
||||||
|
|
||||||
|
@ -138,7 +121,15 @@ export default class UserBadge extends UIElement {
|
||||||
return new Combine([
|
return new Combine([
|
||||||
userIcon,
|
userIcon,
|
||||||
usertext,
|
usertext,
|
||||||
])
|
]).SetClass("h-16")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
super(
|
||||||
|
new VariableUiElement(userBadge),
|
||||||
|
loginButton,
|
||||||
|
State.state.osmConnection.isLoggedIn
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,62 +1,46 @@
|
||||||
import {UIElement} from "./UIElement";
|
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import State from "../State";
|
import State from "../State";
|
||||||
|
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||||
|
|
||||||
export default class CenterMessageBox extends UIElement {
|
export default class CenterMessageBox extends VariableUiElement {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(State.state.centerMessage);
|
const state = State.state;
|
||||||
|
const updater = State.state.layerUpdater;
|
||||||
this.ListenTo(State.state.locationControl);
|
const t = Translations.t.centerMessage;
|
||||||
this.ListenTo(State.state.layerUpdater.timeout);
|
const message = updater.runningQuery.map(
|
||||||
this.ListenTo(State.state.layerUpdater.runningQuery);
|
isRunning => {
|
||||||
this.ListenTo(State.state.layerUpdater.sufficientlyZoomed);
|
if (isRunning) {
|
||||||
|
return {el: t.loadingData};
|
||||||
}
|
}
|
||||||
|
if (!updater.sufficientlyZoomed.data) {
|
||||||
private static prep(): { innerHtml: string | UIElement, done: boolean } {
|
return {el: t.zoomIn}
|
||||||
if (State.state.centerMessage.data != "") {
|
|
||||||
return {innerHtml: State.state.centerMessage.data, done: false};
|
|
||||||
}
|
}
|
||||||
const lu = State.state.layerUpdater;
|
if (updater.timeout.data > 0) {
|
||||||
if (lu.timeout.data > 0) {
|
return {el: t.retrying.Subs({count: "" + updater.timeout.data})}
|
||||||
return {
|
|
||||||
innerHtml: Translations.t.centerMessage.retrying.Subs({count: "" + lu.timeout.data}),
|
|
||||||
done: false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return {el: t.ready, isDone: true}
|
||||||
|
|
||||||
if (lu.runningQuery.data) {
|
},
|
||||||
return {innerHtml: Translations.t.centerMessage.loadingData, done: false};
|
[updater.timeout, updater.sufficientlyZoomed, state.locationControl]
|
||||||
|
)
|
||||||
|
|
||||||
}
|
super(message.map(toShow => toShow.el))
|
||||||
if (!lu.sufficientlyZoomed.data) {
|
|
||||||
return {innerHtml: Translations.t.centerMessage.zoomIn, done: false};
|
this.SetClass("block " +
|
||||||
|
"rounded-3xl bg-white text-xl font-bold text-center pointer-events-none p-4")
|
||||||
|
this.SetStyle("transition: opacity 750ms linear")
|
||||||
|
|
||||||
|
message.addCallbackAndRun(toShow => {
|
||||||
|
const isDone = toShow.isDone ?? false;
|
||||||
|
if (isDone) {
|
||||||
|
this.SetStyle("transition: opacity 750ms linear; opacity: 0")
|
||||||
} else {
|
} else {
|
||||||
return {innerHtml: Translations.t.centerMessage.ready, done: true};
|
this.SetStyle("transition: opacity 750ms linear; opacity: 0.75")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string | UIElement {
|
|
||||||
return CenterMessageBox.prep().innerHtml;
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
InnerUpdate(htmlElement: HTMLElement) {
|
|
||||||
if(htmlElement.parentElement === null){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const pstyle = htmlElement.parentElement.style;
|
|
||||||
if (State.state.centerMessage.data != "") {
|
|
||||||
pstyle.opacity = "1";
|
|
||||||
pstyle.pointerEvents = "all";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pstyle.pointerEvents = "none";
|
|
||||||
|
|
||||||
if (CenterMessageBox.prep().done) {
|
|
||||||
pstyle.opacity = "0";
|
|
||||||
} else {
|
|
||||||
pstyle.opacity = "0.5";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class Attribution extends Combine {
|
export default class Attribution extends Combine {
|
||||||
|
|
||||||
constructor(author: UIElement | string, license: UIElement | string, icon: UIElement) {
|
constructor(author: BaseUIElement | string, license: BaseUIElement | string, icon: BaseUIElement) {
|
||||||
super([
|
super([
|
||||||
icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em"),
|
icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em"),
|
||||||
new Combine([
|
new Combine([
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {Tag} from "../../Logic/Tags/Tag";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
import LicensePicker from "../BigComponents/LicensePicker";
|
import LicensePicker from "../BigComponents/LicensePicker";
|
||||||
import Toggle from "../Input/Toggle";
|
import Toggle from "../Input/Toggle";
|
||||||
import FileSelectorButton from "../Base/FileSelectorButton";
|
import FileSelectorButton from "../Input/FileSelectorButton";
|
||||||
import ImgurUploader from "../../Logic/Web/ImgurUploader";
|
import ImgurUploader from "../../Logic/Web/ImgurUploader";
|
||||||
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
|
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
|
||||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {InputElement} from "./InputElement";
|
import {InputElement} from "./InputElement";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,20 +9,57 @@ import BaseUIElement from "../BaseUIElement";
|
||||||
export default class CheckBoxes extends InputElement<number[]> {
|
export default class CheckBoxes extends InputElement<number[]> {
|
||||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
private readonly value: UIEventSource<number[]>;
|
|
||||||
private readonly _elements: BaseUIElement[]
|
|
||||||
|
|
||||||
|
|
||||||
private readonly _element : HTMLElement
|
private readonly _element : HTMLElement
|
||||||
|
|
||||||
constructor(elements: BaseUIElement[]) {
|
private static _nextId = 0;
|
||||||
|
private readonly value: UIEventSource<number[]>
|
||||||
|
constructor(elements: BaseUIElement[], value =new UIEventSource<number[]>([])) {
|
||||||
super();
|
super();
|
||||||
this._elements = Utils.NoNull(elements);
|
this.value = value;
|
||||||
this.value = new UIEventSource<number[]>([])
|
elements = Utils.NoNull(elements);
|
||||||
|
|
||||||
|
const el = document.createElement("form")
|
||||||
|
|
||||||
|
for (let i = 0; i < elements.length; i++) {
|
||||||
|
|
||||||
|
let inputI = elements[i];
|
||||||
|
const input = document.createElement("input")
|
||||||
|
const id = CheckBoxes._nextId
|
||||||
|
CheckBoxes._nextId ++;
|
||||||
|
input.id = "checkbox"+id
|
||||||
|
|
||||||
|
input.type = "checkbox"
|
||||||
|
const label = document.createElement("label")
|
||||||
|
label.htmlFor = input.id
|
||||||
|
label.appendChild(inputI.ConstructElement())
|
||||||
|
|
||||||
|
value.addCallbackAndRun(selectedValues =>{
|
||||||
|
if(selectedValues === undefined){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(selectedValues.indexOf(i) >= 0){
|
||||||
|
input.checked = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
input.onchange = () => {
|
||||||
|
const index = value.data.indexOf(i);
|
||||||
|
if(input.checked && index < 0){
|
||||||
|
value.data.push(i);
|
||||||
|
value.ping();
|
||||||
|
}else if(index >= 0){
|
||||||
|
value.data.splice(index,1);
|
||||||
|
value.ping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
el.appendChild(input)
|
||||||
|
el.appendChild(document.createElement("br"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const el = document.createElement()
|
|
||||||
this._element = el;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,50 +78,6 @@ private readonly _element : HTMLElement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private IdFor(i) {
|
|
||||||
return 'checkmark-' + this.id + '-' + i;
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
let body = "";
|
|
||||||
for (let i = 0; i < this._elements.length; i++) {
|
|
||||||
let el = this._elements[i];
|
|
||||||
const htmlElement =
|
|
||||||
`<input type="checkbox" id="${this.IdFor(i)}"><label for="${this.IdFor(i)}">${el.Render()}</label><br/>`;
|
|
||||||
body += htmlElement;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<form id='${this.id}'>${body}</form>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
for (let i = 0; i < this._elements.length; i++) {
|
|
||||||
const el = document.getElementById(this.IdFor(i));
|
|
||||||
|
|
||||||
if(this.value.data.indexOf(i) >= 0){
|
|
||||||
// @ts-ignore
|
|
||||||
el.checked = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
el.onchange = () => {
|
|
||||||
const index = self.value.data.indexOf(i);
|
|
||||||
// @ts-ignore
|
|
||||||
if(el.checked && index < 0){
|
|
||||||
self.value.data.push(i);
|
|
||||||
self.value.ping();
|
|
||||||
}else if(index >= 0){
|
|
||||||
self.value.data.splice(index,1);
|
|
||||||
self.value.ping();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -36,10 +36,12 @@ this._values = values;
|
||||||
|
|
||||||
{
|
{
|
||||||
const labelEl = Translations.W(label).ConstructElement()
|
const labelEl = Translations.W(label).ConstructElement()
|
||||||
|
if (labelEl !== undefined) {
|
||||||
const labelHtml = document.createElement("label")
|
const labelHtml = document.createElement("label")
|
||||||
labelHtml.appendChild(labelEl)
|
labelHtml.appendChild(labelEl)
|
||||||
labelHtml.htmlFor = el.id;
|
labelHtml.htmlFor = el.id;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,10 +4,9 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export class TextField extends InputElement<string> {
|
export class TextField extends InputElement<string> {
|
||||||
private readonly value: UIEventSource<string>;
|
|
||||||
public readonly enterPressed = new UIEventSource<string>(undefined);
|
public readonly enterPressed = new UIEventSource<string>(undefined);
|
||||||
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
private readonly value: UIEventSource<string>;
|
||||||
private _element: HTMLElement;
|
private _element: HTMLElement;
|
||||||
private readonly _isValid: (s: string, country?: () => string) => boolean;
|
private readonly _isValid: (s: string, country?: () => string) => boolean;
|
||||||
|
|
||||||
|
@ -19,6 +18,7 @@ export class TextField extends InputElement<string> {
|
||||||
inputMode?: string,
|
inputMode?: string,
|
||||||
label?: BaseUIElement,
|
label?: BaseUIElement,
|
||||||
textAreaRows?: number,
|
textAreaRows?: number,
|
||||||
|
inputStyle?: string,
|
||||||
isValid?: ((s: string, country?: () => string) => boolean)
|
isValid?: ((s: string, country?: () => string) => boolean)
|
||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
|
@ -32,7 +32,6 @@ export class TextField extends InputElement<string> {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const placeholder = Translations.W(options.placeholder ?? "").ConstructElement().innerText.replace("'", "'");
|
const placeholder = Translations.W(options.placeholder ?? "").ConstructElement().innerText.replace("'", "'");
|
||||||
|
|
||||||
this.SetClass("form-text-field")
|
this.SetClass("form-text-field")
|
||||||
|
@ -46,13 +45,15 @@ export class TextField extends InputElement<string> {
|
||||||
inputEl = el;
|
inputEl = el;
|
||||||
} else {
|
} else {
|
||||||
const el = document.createElement("input")
|
const el = document.createElement("input")
|
||||||
el.type = options.htmlType
|
el.type = options.htmlType ?? "text"
|
||||||
el.inputMode = options.inputMode
|
el.inputMode = options.inputMode
|
||||||
el.placeholder = placeholder
|
el.placeholder = placeholder
|
||||||
|
el.style.cssText = options.inputStyle
|
||||||
inputEl = el
|
inputEl = el
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = document.createElement("form")
|
const form = document.createElement("form")
|
||||||
|
form.appendChild(inputEl)
|
||||||
form.onsubmit = () => false;
|
form.onsubmit = () => false;
|
||||||
|
|
||||||
if (options.label) {
|
if (options.label) {
|
||||||
|
@ -121,15 +122,6 @@ export class TextField extends InputElement<string> {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GetValue(): UIEventSource<string> {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerConstructElement(): HTMLElement {
|
|
||||||
return this._element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SetCursorPosition(textfield: HTMLElement, i: number) {
|
private static SetCursorPosition(textfield: HTMLElement, i: number) {
|
||||||
|
@ -146,6 +138,10 @@ export class TextField extends InputElement<string> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GetValue(): UIEventSource<string> {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
IsValid(t: string): boolean {
|
IsValid(t: string): boolean {
|
||||||
if (t === undefined || t === null) {
|
if (t === undefined || t === null) {
|
||||||
return false
|
return false
|
||||||
|
@ -153,4 +149,8 @@ export class TextField extends InputElement<string> {
|
||||||
return this._isValid(t, undefined);
|
return this._isValid(t, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -10,12 +10,13 @@ export default class Toggle extends VariableUiElement{
|
||||||
|
|
||||||
public readonly isEnabled: UIEventSource<boolean>;
|
public readonly isEnabled: UIEventSource<boolean>;
|
||||||
|
|
||||||
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, data: UIEventSource<boolean> = new UIEventSource<boolean>(false)) {
|
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) {
|
||||||
super(
|
super(
|
||||||
data.map(isEnabled => isEnabled ? showEnabled : showDisabled)
|
isEnabled.map(isEnabled => isEnabled ? showEnabled : showDisabled)
|
||||||
);
|
);
|
||||||
|
this.isEnabled = isEnabled
|
||||||
this.onClick(() => {
|
this.onClick(() => {
|
||||||
data.setData(!data.data);
|
isEnabled.setData(!isEnabled.data);
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import {UIElement} from "./UIElement";
|
|
||||||
import {DropDown} from "./Input/DropDown";
|
import {DropDown} from "./Input/DropDown";
|
||||||
import Locale from "./i18n/Locale";
|
import Locale from "./i18n/Locale";
|
||||||
|
import BaseUIElement from "./BaseUIElement";
|
||||||
|
|
||||||
export default class LanguagePicker {
|
export default class LanguagePicker {
|
||||||
|
|
||||||
|
|
||||||
public static CreateLanguagePicker(
|
public static CreateLanguagePicker(
|
||||||
languages : string[] ,
|
languages : string[] ,
|
||||||
label: string | UIElement = "") {
|
label: string | BaseUIElement = "") {
|
||||||
|
|
||||||
if (languages.length <= 1) {
|
if (languages.length <= 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import {UIElement} from "./UIElement";
|
import {UIElement} from "./UIElement";
|
||||||
|
import BaseUIElement from "./BaseUIElement";
|
||||||
|
import Combine from "./Base/Combine";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A button floating above the map, in a uniform style
|
* A button floating above the map, in a uniform style
|
||||||
*/
|
*/
|
||||||
export default class MapControlButton extends UIElement {
|
export default class MapControlButton extends UIElement {
|
||||||
private _contents: UIElement;
|
private _contents: BaseUIElement;
|
||||||
|
|
||||||
constructor(contents: UIElement) {
|
constructor(contents: BaseUIElement) {
|
||||||
super();
|
super();
|
||||||
this._contents = contents;
|
this._contents = new Combine([contents]);
|
||||||
this.SetClass("relative block rounded-full w-10 h-10 p-1 pointer-events-auto z-above-map subtle-background")
|
this.SetClass("relative block rounded-full w-10 h-10 p-1 pointer-events-auto z-above-map subtle-background")
|
||||||
this.SetStyle("box-shadow: 0 0 10px var(--shadow-color);");
|
this.SetStyle("box-shadow: 0 0 10px var(--shadow-color);");
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {OH} from "./OpeningHours";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import opening_hours from "opening_hours";
|
import opening_hours from "opening_hours";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class OpeningHoursVisualization extends UIElement {
|
export default class OpeningHoursVisualization extends UIElement {
|
||||||
private static readonly weekdays = [
|
private static readonly weekdays = [
|
||||||
|
@ -87,7 +88,7 @@ export default class OpeningHoursVisualization extends UIElement {
|
||||||
return new Date(d.setDate(diff));
|
return new Date(d.setDate(diff));
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string | UIElement {
|
InnerRender(): string | BaseUIElement {
|
||||||
|
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
@ -168,13 +169,13 @@ export default class OpeningHoursVisualization extends UIElement {
|
||||||
latestclose = Math.max(19 * 60 * 60, latestclose + 30 * 60)
|
latestclose = Math.max(19 * 60 * 60, latestclose + 30 * 60)
|
||||||
|
|
||||||
|
|
||||||
const rows: UIElement[] = [];
|
const rows: BaseUIElement[] = [];
|
||||||
const availableArea = latestclose - earliestOpen;
|
const availableArea = latestclose - earliestOpen;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const now = (100 * (((new Date() - today) / 1000) - earliestOpen)) / availableArea;
|
const now = (100 * (((new Date() - today) / 1000) - earliestOpen)) / availableArea;
|
||||||
|
|
||||||
|
|
||||||
let header: UIElement[] = [];
|
let header: BaseUIElement[] = [];
|
||||||
|
|
||||||
if (now >= 0 && now <= 100) {
|
if (now >= 0 && now <= 100) {
|
||||||
header.push(new FixedUiElement("").SetStyle(`left:${now}%;`).SetClass("ohviz-now"))
|
header.push(new FixedUiElement("").SetStyle(`left:${now}%;`).SetClass("ohviz-now"))
|
||||||
|
@ -218,7 +219,7 @@ export default class OpeningHoursVisualization extends UIElement {
|
||||||
dateToShow = "" + day.getDate() + "/" + (day.getMonth() + 1);
|
dateToShow = "" + day.getDate() + "/" + (day.getMonth() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let innerContent: (string | UIElement)[] = [];
|
let innerContent: (string | BaseUIElement)[] = [];
|
||||||
|
|
||||||
// Add the lines
|
// Add the lines
|
||||||
for (const changeMoment of changeHours) {
|
for (const changeMoment of changeHours) {
|
||||||
|
@ -265,7 +266,7 @@ export default class OpeningHoursVisualization extends UIElement {
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
"<table class='ohviz' style='width:100%; word-break: normal; word-wrap: normal'>",
|
"<table class='ohviz' style='width:100%; word-break: normal; word-wrap: normal'>",
|
||||||
...rows.map(el => "<tr>" + el.Render() + "</tr>"),
|
...rows.map(el => new Combine(["<tr>" ,el , "</tr>"])),
|
||||||
"</table>"
|
"</table>"
|
||||||
]).SetClass("ohviz-container");
|
]).SetClass("ohviz-container");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,47 @@
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
|
||||||
export class SaveButton extends UIElement {
|
export class SaveButton extends UIElement {
|
||||||
|
|
||||||
private readonly _value: UIEventSource<any>;
|
|
||||||
private readonly _friendlyLogin: UIElement;
|
private readonly _element: BaseUIElement;
|
||||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
|
||||||
|
|
||||||
constructor(value: UIEventSource<any>, osmConnection: OsmConnection) {
|
constructor(value: UIEventSource<any>, osmConnection: OsmConnection) {
|
||||||
super(value);
|
super(value);
|
||||||
this._userDetails = osmConnection?.userDetails;
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
throw "No event source for savebutton, something is wrong"
|
throw "No event source for savebutton, something is wrong"
|
||||||
}
|
}
|
||||||
this._value = value;
|
|
||||||
this._friendlyLogin = Translations.t.general.loginToStart.Clone()
|
const pleaseLogin = Translations.t.general.loginToStart.Clone()
|
||||||
.SetClass("login-button-friendly")
|
.SetClass("login-button-friendly")
|
||||||
.onClick(() => osmConnection?.AttemptLogin())
|
.onClick(() => osmConnection?.AttemptLogin())
|
||||||
|
|
||||||
|
|
||||||
|
const isSaveable = value.map(v => v !== false && (v ?? "") !== "")
|
||||||
|
|
||||||
|
|
||||||
|
const saveEnabled = Translations.t.general.save.Clone().SetClass(`btn`);
|
||||||
|
const saveDisabled = Translations.t.general.save.Clone().SetClass(`btn btn-disabled`);
|
||||||
|
const save = new Toggle(
|
||||||
|
saveEnabled,
|
||||||
|
saveDisabled,
|
||||||
|
isSaveable
|
||||||
|
)
|
||||||
|
this._element = new Toggle(
|
||||||
|
save
|
||||||
|
, pleaseLogin,
|
||||||
|
osmConnection?.userDetails?.map(userDetails => userDetails.loggedIn) ?? new UIEventSource<any>(false)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender() {
|
InnerRender(): BaseUIElement {
|
||||||
if(this._userDetails != undefined && !this._userDetails.data.loggedIn){
|
return this._element
|
||||||
return this._friendlyLogin;
|
|
||||||
}
|
|
||||||
let inactive_class = ''
|
|
||||||
if (this._value.data === false || (this._value.data ?? "") === "") {
|
|
||||||
inactive_class = "btn-disabled";
|
|
||||||
}
|
|
||||||
return Translations.t.general.save.Clone().SetClass(`btn ${inactive_class}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -19,8 +19,9 @@ export default class TagRenderingAnswer extends UIElement {
|
||||||
private _contentStyle: string;
|
private _contentStyle: string;
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>, configuration: TagRenderingConfig, contentClasses: string = "", contentStyle: string = "") {
|
constructor(tags: UIEventSource<any>, configuration: TagRenderingConfig, contentClasses: string = "", contentStyle: string = "") {
|
||||||
super(tags);
|
super();
|
||||||
this._tags = tags;
|
this._tags = tags;
|
||||||
|
this.ListenTo(tags)
|
||||||
this._configuration = configuration;
|
this._configuration = configuration;
|
||||||
this._contentClass = contentClasses;
|
this._contentClass = contentClasses;
|
||||||
this._contentStyle = contentStyle;
|
this._contentStyle = contentStyle;
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
||||||
import {Tag} from "../../Logic/Tags/Tag";
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
import {And} from "../../Logic/Tags/And";
|
import {And} from "../../Logic/Tags/And";
|
||||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the question element.
|
* Shows the question element.
|
||||||
|
@ -35,7 +36,7 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
|
|
||||||
private _inputElement: InputElement<TagsFilter>;
|
private _inputElement: InputElement<TagsFilter>;
|
||||||
private _cancelButton: UIElement;
|
private _cancelButton: UIElement;
|
||||||
private _appliedTags: UIElement;
|
private _appliedTags: BaseUIElement;
|
||||||
private _question: UIElement;
|
private _question: UIElement;
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>,
|
constructor(tags: UIEventSource<any>,
|
||||||
|
@ -82,16 +83,19 @@ export default class TagRenderingQuestion extends UIElement {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (tags === undefined) {
|
if (tags === undefined) {
|
||||||
return Translations.t.general.noTagsSelected.SetClass("subtle").Render();
|
return Translations.t.general.noTagsSelected.SetClass("subtle");
|
||||||
}
|
}
|
||||||
if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) {
|
if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) {
|
||||||
const tagsStr = tags.asHumanString(false, true, self._tags.data);
|
const tagsStr = tags.asHumanString(false, true, self._tags.data);
|
||||||
return new FixedUiElement(tagsStr).SetClass("subtle").Render();
|
return new FixedUiElement(tagsStr).SetClass("subtle");
|
||||||
}
|
}
|
||||||
return tags.asHumanString(true, true, self._tags.data);
|
return tags.asHumanString(true, true, self._tags.data);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).SetClass("block")
|
).SetClass("block")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender() {
|
InnerRender() {
|
||||||
|
|
|
@ -128,7 +128,7 @@ export default class ShowDataLayer {
|
||||||
const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0));
|
const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0));
|
||||||
return L.marker(latLng, {
|
return L.marker(latLng, {
|
||||||
icon: L.divIcon({
|
icon: L.divIcon({
|
||||||
html: style.icon.html.Render(),
|
html: style.icon.html.ConstructElement(),
|
||||||
className: style.icon.className,
|
className: style.icon.className,
|
||||||
iconAnchor: style.icon.iconAnchor,
|
iconAnchor: style.icon.iconAnchor,
|
||||||
iconUrl: style.icon.iconUrl,
|
iconUrl: style.icon.iconUrl,
|
||||||
|
|
|
@ -21,6 +21,7 @@ export abstract class UIElement extends BaseUIElement{
|
||||||
if (source === undefined) {
|
if (source === undefined) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
console.trace("Got a listenTo in ", this.constructor.name)
|
||||||
const self = this;
|
const self = this;
|
||||||
source.addCallback(() => {
|
source.addCallback(() => {
|
||||||
self.lastInnerRender = undefined;
|
self.lastInnerRender = undefined;
|
||||||
|
@ -39,7 +40,7 @@ export abstract class UIElement extends BaseUIElement{
|
||||||
}
|
}
|
||||||
|
|
||||||
Render(): string {
|
Render(): string {
|
||||||
return "Don't use Render!"
|
return this.InnerRenderAsString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,11 +53,6 @@ export abstract class UIElement extends BaseUIElement{
|
||||||
return rendered
|
return rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
public IsEmpty(): boolean {
|
|
||||||
return this.InnerRender() === undefined || this.InnerRender() === "";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be overridden for specific HTML functionality
|
* Should be overridden for specific HTML functionality
|
||||||
|
|
|
@ -55,17 +55,6 @@
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-pic {
|
|
||||||
float: left;
|
|
||||||
width: 4em;
|
|
||||||
height: 4em;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 500ms linear;
|
|
||||||
border-radius: 999em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usertext {
|
.usertext {
|
||||||
display: block;
|
display: block;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
|
|
66
index.css
66
index.css
|
@ -324,52 +324,6 @@ li::marker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.activate-osm-authentication {
|
|
||||||
cursor: pointer;
|
|
||||||
color: blue;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#searchbox {
|
|
||||||
display: inline-block;
|
|
||||||
text-align: left;
|
|
||||||
background-color: var(--background-color);
|
|
||||||
color: var(--foreground-color);
|
|
||||||
|
|
||||||
transition: all 500ms linear;
|
|
||||||
pointer-events: all;
|
|
||||||
margin: 0 0 0.5em;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
height: 2em;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchbox {
|
|
||||||
width: 100%
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchbox .form-text-field {
|
|
||||||
position: relative;
|
|
||||||
float: left;
|
|
||||||
margin-top: 0.2em;
|
|
||||||
margin-left: 1em;
|
|
||||||
width: calc(100% - 4em)
|
|
||||||
}
|
|
||||||
|
|
||||||
#searchbox input[type="text"] {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
font-size: large;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: var(--foreground-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**************************************/
|
/**************************************/
|
||||||
|
|
||||||
|
@ -409,25 +363,9 @@ li::marker {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#centermessage {
|
|
||||||
z-index: 4000;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 500ms linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/***************** Info box (box containing features and questions ******************/
|
/***************** Info box (box containing features and questions ******************/
|
||||||
|
|
||||||
|
|
||||||
.map-attribution img {
|
|
||||||
width: 1em;
|
|
||||||
height: 1em;
|
|
||||||
fill: black;
|
|
||||||
border-radius: 0;
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-popup-content {
|
.leaflet-popup-content {
|
||||||
width: 45em !important;
|
width: 45em !important;
|
||||||
}
|
}
|
||||||
|
@ -461,3 +399,7 @@ li::marker {
|
||||||
max-width: 1em;
|
max-width: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small-image {
|
||||||
|
height: 1em;
|
||||||
|
max-width: 1em;
|
||||||
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@
|
||||||
<div id="bottom-right" class="absolute bottom-3 right-2 rounded-3xl z-above-map"></div>
|
<div id="bottom-right" class="absolute bottom-3 right-2 rounded-3xl z-above-map"></div>
|
||||||
|
|
||||||
<div id="centermessage"
|
<div id="centermessage"
|
||||||
class="clutter absolute rounded-3xl h-24 left-24 right-24 top-56 bg-white p-3 pt-5 sm:pt-8 text-xl font-bold text-center">
|
class="clutter absolute h-24 left-24 right-24 top-56" style="z-index: 4000">
|
||||||
Loading MapComplete, hang on...
|
Loading MapComplete, hang on...
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
1
index.ts
1
index.ts
|
@ -98,7 +98,6 @@ new Combine(["Initializing... <br/>",
|
||||||
|
|
||||||
})])
|
})])
|
||||||
.AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong
|
.AttachTo("centermessage"); // Add an initialization and reset button if something goes wrong
|
||||||
|
|
||||||
document.getElementById("decoration-desktop").remove();
|
document.getElementById("decoration-desktop").remove();
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
"loginWithOpenStreetMap": "Login with OpenStreetMap",
|
"loginWithOpenStreetMap": "Login with OpenStreetMap",
|
||||||
"welcomeBack": "You are logged in, welcome back!",
|
"welcomeBack": "You are logged in, welcome back!",
|
||||||
"loginToStart": "Login to answer this question",
|
"loginToStart": "Login to answer this question",
|
||||||
|
"testing":"Testing - changes won't be saved",
|
||||||
"search": {
|
"search": {
|
||||||
"search": "Search a location",
|
"search": "Search a location",
|
||||||
"searching": "Searching…",
|
"searching": "Searching…",
|
||||||
|
|
17
test.ts
17
test.ts
|
@ -1,15 +1,6 @@
|
||||||
import {Translation} from "./UI/i18n/Translation";
|
import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler";
|
||||||
import Locale from "./UI/i18n/Locale";
|
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
|
||||||
import Combine from "./UI/Base/Combine";
|
import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
|
|
||||||
|
|
||||||
new Combine(["Some language:",new Translation({en:"English",nl:"Nederlands",fr:"Françcais"})]).AttachTo("maindiv")
|
new GeoLocationHandler(new UIEventSource<{latlng: any; accuracy: number}>(undefined), undefined, new UIEventSource<LayoutConfig>(undefined)).AttachTo("maindiv")
|
||||||
|
|
||||||
Locale.language.setData("nl")
|
|
||||||
window.setTimeout(() => {
|
|
||||||
Locale.language.setData("en")
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
window.setTimeout(() => {
|
|
||||||
Locale.language.setData("fr")
|
|
||||||
}, 5000)
|
|
134
vendor/Leaflet.AccuratePosition.js
vendored
134
vendor/Leaflet.AccuratePosition.js
vendored
|
@ -1,134 +0,0 @@
|
||||||
/**
|
|
||||||
* Leaflet.AccuratePosition aims to provide an accurate device location when simply calling map.locate() doesn’t.
|
|
||||||
* https://github.com/m165437/Leaflet.AccuratePosition
|
|
||||||
*
|
|
||||||
* Greg Wilson's getAccurateCurrentPosition() forked to be a Leaflet plugin
|
|
||||||
* https://github.com/gwilson/getAccurateCurrentPosition
|
|
||||||
*
|
|
||||||
* Copyright (C) 2013 Greg Wilson, 2014 Michael Schmidt-Voigt
|
|
||||||
*/
|
|
||||||
|
|
||||||
L.Map.include({
|
|
||||||
_defaultAccuratePositionOptions: {
|
|
||||||
maxWait: 10000,
|
|
||||||
desiredAccuracy: 20
|
|
||||||
},
|
|
||||||
|
|
||||||
findAccuratePosition: function (options) {
|
|
||||||
|
|
||||||
if (!navigator.geolocation) {
|
|
||||||
this._handleAccuratePositionError({
|
|
||||||
code: 0,
|
|
||||||
message: 'Geolocation not supported.'
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._accuratePositionEventCount = 0;
|
|
||||||
this._accuratePositionOptions = L.extend(this._defaultAccuratePositionOptions, options);
|
|
||||||
this._accuratePositionOptions.enableHighAccuracy = true;
|
|
||||||
this._accuratePositionOptions.maximumAge = 0;
|
|
||||||
|
|
||||||
if (!this._accuratePositionOptions.timeout)
|
|
||||||
this._accuratePositionOptions.timeout = this._accuratePositionOptions.maxWait;
|
|
||||||
|
|
||||||
var onResponse = L.bind(this._checkAccuratePosition, this),
|
|
||||||
onError = L.bind(this._handleAccuratePositionError, this),
|
|
||||||
onTimeout = L.bind(this._handleAccuratePositionTimeout, this);
|
|
||||||
|
|
||||||
this._accuratePositionWatchId = navigator.geolocation.watchPosition(
|
|
||||||
onResponse,
|
|
||||||
onError,
|
|
||||||
this._accuratePositionOptions);
|
|
||||||
|
|
||||||
this._accuratePositionTimerId = setTimeout(
|
|
||||||
onTimeout,
|
|
||||||
this._accuratePositionOptions.maxWait);
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleAccuratePositionTimeout: function() {
|
|
||||||
navigator.geolocation.clearWatch(this._accuratePositionWatchId);
|
|
||||||
|
|
||||||
if (typeof this._lastCheckedAccuratePosition !== 'undefined') {
|
|
||||||
this._handleAccuratePositionResponse(this._lastCheckedAccuratePosition);
|
|
||||||
} else {
|
|
||||||
this._handleAccuratePositionError({
|
|
||||||
code: 3,
|
|
||||||
message: 'Timeout expired'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
_cleanUpAccuratePositioning: function () {
|
|
||||||
clearTimeout(this._accuratePositionTimerId);
|
|
||||||
navigator.geolocation.clearWatch(this._accuratePositionWatchId);
|
|
||||||
},
|
|
||||||
|
|
||||||
_checkAccuratePosition: function (pos) {
|
|
||||||
var accuracyReached = pos.coords.accuracy <= this._accuratePositionOptions.desiredAccuracy;
|
|
||||||
|
|
||||||
this._lastCheckedAccuratePosition = pos;
|
|
||||||
this._accuratePositionEventCount = this._accuratePositionEventCount + 1;
|
|
||||||
|
|
||||||
if (accuracyReached && (this._accuratePositionEventCount > 1)) {
|
|
||||||
this._cleanUpAccuratePositioning();
|
|
||||||
this._handleAccuratePositionResponse(pos);
|
|
||||||
} else {
|
|
||||||
this._handleAccuratePositionProgress(pos);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_prepareAccuratePositionData: function (pos) {
|
|
||||||
var lat = pos.coords.latitude,
|
|
||||||
lng = pos.coords.longitude,
|
|
||||||
latlng = new L.LatLng(lat, lng),
|
|
||||||
|
|
||||||
latAccuracy = 180 * pos.coords.accuracy / 40075017,
|
|
||||||
lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * lat),
|
|
||||||
|
|
||||||
bounds = L.latLngBounds(
|
|
||||||
[lat - latAccuracy, lng - lngAccuracy],
|
|
||||||
[lat + latAccuracy, lng + lngAccuracy]);
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
latlng: latlng,
|
|
||||||
bounds: bounds,
|
|
||||||
timestamp: pos.timestamp
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var i in pos.coords) {
|
|
||||||
if (typeof pos.coords[i] === 'number') {
|
|
||||||
data[i] = pos.coords[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleAccuratePositionProgress: function (pos) {
|
|
||||||
var data = this._prepareAccuratePositionData(pos);
|
|
||||||
this.fire('accuratepositionprogress', data);
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleAccuratePositionResponse: function (pos) {
|
|
||||||
var data = this._prepareAccuratePositionData(pos);
|
|
||||||
this.fire('accuratepositionfound', data);
|
|
||||||
},
|
|
||||||
|
|
||||||
_handleAccuratePositionError: function (error) {
|
|
||||||
var c = error.code,
|
|
||||||
message = error.message ||
|
|
||||||
(c === 1 ? 'permission denied' :
|
|
||||||
(c === 2 ? 'position unavailable' : 'timeout'));
|
|
||||||
|
|
||||||
this._cleanUpAccuratePositioning();
|
|
||||||
|
|
||||||
this.fire('accuratepositionerror', {
|
|
||||||
code: c,
|
|
||||||
message: 'Geolocation error: ' + message + '.'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log("Find accurate position script loaded");
|
|
Loading…
Reference in a new issue