forked from MapComplete/MapComplete
More refactoring, stuff kindoff works
This commit is contained in:
parent
62f471df1e
commit
3943100e54
52 changed files with 635 additions and 1010 deletions
|
@ -1,4 +1,3 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import Link from "../Base/Link";
|
||||
import Svg from "../../Svg";
|
||||
import Combine from "../Base/Combine";
|
||||
|
@ -8,67 +7,57 @@ import Constants from "../../Models/Constants";
|
|||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||
import Loc from "../../Models/Loc";
|
||||
import * as L from "leaflet"
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
|
||||
/**
|
||||
* The bottom right attribution panel in the leaflet map
|
||||
*/
|
||||
export default class Attribution extends UIElement {
|
||||
|
||||
private readonly _location: UIEventSource<Loc>;
|
||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
||||
private readonly _leafletMap: UIEventSource<L.Map>;
|
||||
export default class Attribution extends Combine {
|
||||
|
||||
constructor(location: UIEventSource<Loc>,
|
||||
userDetails: UIEventSource<UserDetails>,
|
||||
layoutToUse: UIEventSource<LayoutConfig>,
|
||||
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 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 stats = new Link(Svg.statistics_img, 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 stats = new Link(Svg.statistics_ui().SetClass("small-image"), osmChaLink, true)
|
||||
|
||||
|
||||
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 bottom = bounds.getSouth();
|
||||
const right = bounds.getEast();
|
||||
const left = bounds.getWest();
|
||||
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
|
||||
}
|
||||
const top = bounds.getNorth();
|
||||
const bottom = bounds.getSouth();
|
||||
const right = bounds.getEast();
|
||||
const left = bounds.getWest();
|
||||
|
||||
const josmLink = `http://127.0.0.1:8111/load_and_zoom?left=${left}&right=${right}&top=${top}&bottom=${bottom}`
|
||||
return new Link(Svg.josm_logo_ui().SetClass("small-image"), josmLink, true);
|
||||
},
|
||||
[location]
|
||||
)
|
||||
)
|
||||
super([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary]);
|
||||
|
||||
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 Combine([mapComplete, reportBug, stats, editHere, editWithJosm, mapillary]).Render();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ import SmallLicense from "../../Models/smallLicense";
|
|||
import {Utils} from "../../Utils";
|
||||
import Link from "../Base/Link";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {UIElement} from "../UIElement";
|
||||
import * as contributors from "../../assets/contributors.json"
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
/**
|
||||
* 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.credits,
|
||||
"<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/>",
|
||||
|
||||
new VariableUiElement(contributions.map(contributions => {
|
||||
|
@ -66,7 +66,7 @@ export default class AttributionPanel extends Combine {
|
|||
this.SetStyle("max-width: calc(100vw - 5em); width: 40em;")
|
||||
}
|
||||
|
||||
private static CodeContributors(): UIElement {
|
||||
private static CodeContributors(): BaseUIElement {
|
||||
|
||||
const total = contributors.contributors.length;
|
||||
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")) {
|
||||
iconPath = "." + new URL(iconPath).pathname;
|
||||
}
|
||||
|
|
|
@ -1,39 +1,35 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {DropDown} from "../Input/DropDown";
|
||||
import Translations from "../i18n/Translations";
|
||||
import State from "../../State";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
|
||||
export default class BackgroundSelector extends UIElement {
|
||||
|
||||
private _dropdown: BaseUIElement;
|
||||
private readonly _availableLayers: UIEventSource<BaseLayer[]>;
|
||||
export default class BackgroundSelector extends VariableUiElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const self = this;
|
||||
this._availableLayers = State.state.availableBackgroundLayers;
|
||||
this._availableLayers.addCallbackAndRun(available => self.CreateDropDown(available));
|
||||
}
|
||||
const available = State.state.availableBackgroundLayers.map(available => {
|
||||
const baseLayers: { value: BaseLayer, shown: string }[] = [];
|
||||
for (const i in available) {
|
||||
if(!available.hasOwnProperty(i)){
|
||||
continue;
|
||||
}
|
||||
const layer: BaseLayer = available[i];
|
||||
baseLayers.push({value: layer, shown: layer.name ?? "id:" + layer.id});
|
||||
}
|
||||
return baseLayers
|
||||
}
|
||||
)
|
||||
|
||||
private CreateDropDown(available) {
|
||||
if(available.length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
const baseLayers: { value: BaseLayer, shown: string }[] = [];
|
||||
for (const i in available) {
|
||||
const layer: BaseLayer = available[i];
|
||||
baseLayers.push({value: layer, shown: layer.name ?? "id:" + layer.id});
|
||||
}
|
||||
super(
|
||||
available.map(baseLayers => {
|
||||
if (baseLayers.length <= 1) {
|
||||
return undefined;
|
||||
}
|
||||
return new DropDown(Translations.t.general.backgroundMap, baseLayers, State.state.backgroundLayer)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
this._dropdown = 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 {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Loc from "../../Models/Loc";
|
||||
import {UIElement} from "../UIElement";
|
||||
import BaseLayer from "../../Models/BaseLayer";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export class Basemap {
|
||||
|
||||
|
@ -13,13 +13,12 @@ export class Basemap {
|
|||
location: UIEventSource<Loc>,
|
||||
currentLayer: UIEventSource<BaseLayer>,
|
||||
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
|
||||
extraAttribution: UIElement) {
|
||||
extraAttribution: BaseUIElement) {
|
||||
this.map = L.map(leafletElementId, {
|
||||
center: [location.data.lat ?? 0, location.data.lon ?? 0],
|
||||
zoom: location.data.zoom ?? 2,
|
||||
layers: [currentLayer.data.layer],
|
||||
zoomControl: false
|
||||
|
||||
zoomControl: false,
|
||||
});
|
||||
|
||||
L.control.scale(
|
||||
|
@ -36,8 +35,10 @@ export class Basemap {
|
|||
[[-100, -200], [100, 200]]
|
||||
);
|
||||
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;
|
||||
|
||||
let previousLayer = currentLayer.data;
|
||||
|
|
|
@ -16,21 +16,14 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export default class FullWelcomePaneWithTabs extends UIElement {
|
||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
||||
export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
|
||||
|
||||
private readonly _component: UIElement;
|
||||
|
||||
constructor(isShown: UIEventSource<boolean>) {
|
||||
super(State.state.layoutToUse);
|
||||
this._layoutToUse = State.state.layoutToUse;
|
||||
this._userDetails = State.state.osmConnection.userDetails;
|
||||
const layoutToUse = this._layoutToUse.data;
|
||||
|
||||
|
||||
this._component = new ScrollableFullScreen(
|
||||
const layoutToUse = State.state.layoutToUse.data;
|
||||
super (
|
||||
() => layoutToUse.title.Clone(),
|
||||
() => FullWelcomePaneWithTabs.GenerateContents(layoutToUse, State.state.osmConnection.userDetails),
|
||||
"welcome" ,isShown
|
||||
|
@ -43,11 +36,11 @@ export default class FullWelcomePaneWithTabs extends UIElement {
|
|||
if (layoutToUse.id === personal.id) {
|
||||
welcome = new PersonalLayersPanel();
|
||||
}
|
||||
const tabs = [
|
||||
const tabs : {header: string | BaseUIElement, content: BaseUIElement}[] = [
|
||||
{header: `<img src='${layoutToUse.icon}'>`, content: welcome},
|
||||
{
|
||||
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) {
|
||||
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]))
|
||||
}
|
||||
);
|
||||
|
||||
return new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab)
|
||||
.ListenTo(userDetails);
|
||||
return new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab);
|
||||
}
|
||||
|
||||
InnerRender(): UIElement {
|
||||
return this._component;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,56 +1,43 @@
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import State from "../../State";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import Combine from "../Base/Combine";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
/**
|
||||
* 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<{
|
||||
readonly isDisplayed: UIEventSource<boolean>,
|
||||
readonly layerDef: LayerConfig;
|
||||
}[]>) {
|
||||
super(activeLayers);
|
||||
if(activeLayers === undefined){
|
||||
|
||||
if (activeLayers === undefined) {
|
||||
throw "ActiveLayers should be defined..."
|
||||
}
|
||||
this.activeLayers = activeLayers;
|
||||
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
const checkboxes: BaseUIElement[] = [];
|
||||
|
||||
this._checkboxes = [];
|
||||
|
||||
for (const layer of this.activeLayers.data) {
|
||||
for (const layer of activeLayers.data) {
|
||||
const leafletStyle = layer.layerDef.GenerateLeafletStyle(
|
||||
new UIEventSource<any>({id: "node/-1"}),
|
||||
false)
|
||||
const leafletHtml = leafletStyle.icon.html;
|
||||
const icon =
|
||||
new FixedUiElement(leafletHtml.Render())
|
||||
.SetClass("single-layer-selection-toggle")
|
||||
let iconUnselected: UIElement = new FixedUiElement(leafletHtml.Render())
|
||||
const icon = new Combine([leafletStyle.icon.html]).SetClass("single-layer-selection-toggle")
|
||||
let iconUnselected: BaseUIElement = new Combine([leafletStyle.icon.html])
|
||||
.SetClass("single-layer-selection-toggle")
|
||||
.SetStyle("opacity:0.2;");
|
||||
|
||||
const name = Translations.WT(layer.layerDef.name)?.Clone()
|
||||
?.SetStyle("font-size:large;margin-left: 0.5em;");
|
||||
|
||||
if((name ?? "") === ""){
|
||||
if ((name ?? "") === "") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -59,13 +46,12 @@ export default class LayerSelection extends UIElement {
|
|||
return Translations.t.general.layerSelection.zoomInToSeeThisLayer
|
||||
.SetClass("alert")
|
||||
.SetStyle("display: block ruby;width:min-content;")
|
||||
.Render();
|
||||
}
|
||||
return ""
|
||||
}))
|
||||
const style = "display:flex;align-items:center;"
|
||||
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])
|
||||
.SetStyle(styleWhole),
|
||||
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)
|
||||
.SetStyle("display:flex;flex-direction:column;")
|
||||
.Render();
|
||||
}
|
||||
super(checkboxes)
|
||||
this.SetStyle("display:flex;flex-direction:column;")
|
||||
|
||||
}
|
||||
}
|
|
@ -132,8 +132,16 @@ export default class MoreScreen extends Combine {
|
|||
linkSuffix = `#${customThemeDefinition}`
|
||||
}
|
||||
|
||||
const linkText = currentLocation.map(currentLocation =>
|
||||
`${linkPrefix}z=${currentLocation.zoom ?? 1}&lat=${currentLocation.lat ?? 0}&lon=${currentLocation.lon ?? 0}${linkSuffix}`)
|
||||
const linkText = currentLocation.map(currentLocation => {
|
||||
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 Toggle from "../Input/Toggle";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
import * as personal from "../../assets/themes/personalLayout/personalLayout.json"
|
||||
import Locale from "../i18n/Locale";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export default class PersonalLayersPanel extends UIElement {
|
||||
private checkboxes: UIElement[] = [];
|
||||
private checkboxes: BaseUIElement[] = [];
|
||||
|
||||
constructor() {
|
||||
super(State.state.favouriteLayers);
|
||||
|
@ -60,9 +61,9 @@ export default class PersonalLayersPanel extends UIElement {
|
|||
if (typeof layer === "string") {
|
||||
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();
|
||||
let iconUnset =new FixedUiElement(icon.Render());
|
||||
let iconUnset =new Combine([icon]);
|
||||
icon.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 userDetails = State.state.osmConnection.userDetails.data;
|
||||
if(!userDetails.loggedIn){
|
||||
return t.loginNeeded.Render();
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
t.panelIntro,
|
||||
...this.checkboxes
|
||||
]).Render();
|
||||
return new Toggle(
|
||||
new Combine([
|
||||
t.panelIntro,
|
||||
...this.checkboxes
|
||||
]),
|
||||
t.loginNeeded,
|
||||
State.state.osmConnection.isLoggedIn
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import Locale from "../i18n/Locale";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import Svg from "../../Svg";
|
||||
|
@ -10,78 +9,75 @@ import {Geocoding} from "../../Logic/Osm/Geocoding";
|
|||
import Translations from "../i18n/Translations";
|
||||
import Hash from "../../Logic/Web/Hash";
|
||||
import Combine from "../Base/Combine";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export default class SearchAndGo extends UIElement {
|
||||
|
||||
private readonly _placeholder = new UIEventSource<Translation>(Translations.t.general.search.search)
|
||||
private readonly _searchField = new TextField({
|
||||
placeholder: new VariableUiElement(
|
||||
this._placeholder.map(uiElement => uiElement.InnerRender(), [Locale.language])
|
||||
),
|
||||
value: new UIEventSource<string>("")
|
||||
}
|
||||
);
|
||||
|
||||
private readonly _foundEntries = new UIEventSource([]);
|
||||
private readonly _goButton = Svg.search_ui().SetClass('w-8 h-8 full-rounded border-black float-right');
|
||||
private readonly _element: Combine;
|
||||
export default class SearchAndGo extends Combine {
|
||||
|
||||
constructor() {
|
||||
super(undefined);
|
||||
this.ListenTo(this._foundEntries);
|
||||
const goButton = Svg.search_ui().SetClass('w-8 h-8 full-rounded border-black float-right');
|
||||
|
||||
const self = this;
|
||||
this._searchField.enterPressed.addCallback(() => {
|
||||
self.RunSearch();
|
||||
});
|
||||
const placeholder = new UIEventSource<Translation>(Translations.t.general.search.search)
|
||||
const searchField = new TextField({
|
||||
placeholder: new VariableUiElement(
|
||||
placeholder.map(uiElement => uiElement, [Locale.language])
|
||||
),
|
||||
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);"
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
searchField.SetClass("relative float-left mt-0 ml-2")
|
||||
searchField.SetStyle("width: calc(100% - 3em)")
|
||||
|
||||
this._goButton.onClick(function () {
|
||||
self.RunSearch();
|
||||
});
|
||||
this._element = new Combine([this._searchField, this._goButton])
|
||||
super([searchField, goButton])
|
||||
|
||||
}
|
||||
this.SetClass("block h-8")
|
||||
this.SetStyle("background: var(--background-color); color: var(--foreground-color); pointer-evetns:all;")
|
||||
|
||||
InnerRender(): BaseUIElement
|
||||
{
|
||||
return this._element
|
||||
|
||||
}
|
||||
// Triggered by 'enter' or onclick
|
||||
function runSearch() {
|
||||
const searchString = searchField.GetValue().data;
|
||||
if (searchString === undefined || searchString === "") {
|
||||
return;
|
||||
}
|
||||
searchField.GetValue().setData("");
|
||||
placeholder.setData(Translations.t.general.search.searching);
|
||||
Geocoding.Search(searchString, (result) => {
|
||||
|
||||
console.log("Search result", result)
|
||||
if (result.length == 0) {
|
||||
placeholder.setData(Translations.t.general.search.nothing);
|
||||
return;
|
||||
}
|
||||
|
||||
const poi = result[0];
|
||||
const bb = poi.boundingbox;
|
||||
const bounds: [[number, number], [number, number]] = [
|
||||
[bb[0], bb[2]],
|
||||
[bb[1], bb[3]]
|
||||
]
|
||||
State.state.selectedElement.setData(undefined);
|
||||
Hash.hash.setData(poi.osm_type + "/" + poi.osm_id);
|
||||
State.state.leafletMap.data.fitBounds(bounds);
|
||||
placeholder.setData(Translations.t.general.search.search);
|
||||
},
|
||||
() => {
|
||||
searchField.GetValue().setData("");
|
||||
placeholder.setData(Translations.t.general.search.error);
|
||||
});
|
||||
|
||||
// Triggered by 'enter' or onclick
|
||||
private RunSearch() {
|
||||
const searchString = this._searchField.GetValue().data;
|
||||
if (searchString === undefined || searchString === "") {
|
||||
return;
|
||||
}
|
||||
this._searchField.GetValue().setData("");
|
||||
this._placeholder.setData(Translations.t.general.search.searching);
|
||||
const self = this;
|
||||
Geocoding.Search(searchString, (result) => {
|
||||
|
||||
console.log("Search result", result)
|
||||
if (result.length == 0) {
|
||||
self._placeholder.setData(Translations.t.general.search.nothing);
|
||||
return;
|
||||
}
|
||||
|
||||
const poi = result[0];
|
||||
const bb = poi.boundingbox;
|
||||
const bounds: [[number, number], [number, number]] = [
|
||||
[bb[0], bb[2]],
|
||||
[bb[1], bb[3]]
|
||||
]
|
||||
State.state.selectedElement. setData(undefined);
|
||||
Hash.hash.setData(poi.osm_type+"/"+poi.osm_id);
|
||||
State.state.leafletMap.data.fitBounds(bounds);
|
||||
self._placeholder.setData(Translations.t.general.search.search);
|
||||
},
|
||||
() => {
|
||||
self._searchField.GetValue().setData("");
|
||||
self._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{
|
||||
private _embedded: UIElement;
|
||||
export default class ShareButton extends BaseUIElement{
|
||||
private _embedded: BaseUIElement;
|
||||
private _shareData: { text: string; title: string; url: string };
|
||||
|
||||
constructor(embedded: UIElement, shareData: {
|
||||
constructor(embedded: BaseUIElement, shareData: {
|
||||
text: string,
|
||||
title: string,
|
||||
url: string
|
||||
|
@ -12,17 +12,17 @@ export default class ShareButton extends UIElement{
|
|||
super();
|
||||
this._embedded = embedded;
|
||||
this._shareData = shareData;
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return `<button type="button" class="share-button" id="${this.id}">${this._embedded.Render()}</button>`
|
||||
this.SetClass("share-button")
|
||||
}
|
||||
|
||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
||||
const self= this;
|
||||
htmlElement.addEventListener('click', () => {
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const e = document.createElement("button")
|
||||
e.type = "button"
|
||||
e.appendChild(this._embedded.ConstructElement())
|
||||
|
||||
e.addEventListener('click', () => {
|
||||
if (navigator.share) {
|
||||
navigator.share(self._shareData).then(() => {
|
||||
navigator.share(this._shareData).then(() => {
|
||||
console.log('Thanks for sharing!');
|
||||
})
|
||||
.catch(err => {
|
||||
|
@ -32,6 +32,9 @@ export default class ShareButton extends UIElement{
|
|||
console.log('web share not supported');
|
||||
}
|
||||
});
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -8,20 +8,20 @@ import Svg from "../../Svg";
|
|||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import State from "../../State";
|
||||
import Combine from "../Base/Combine";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Constants from "../../Models/Constants";
|
||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export default class SimpleAddUI extends UIElement {
|
||||
private readonly _loginButton: UIElement;
|
||||
private readonly _loginButton: BaseUIElement;
|
||||
|
||||
private readonly _confirmPreset: UIEventSource<{
|
||||
description: string | UIElement,
|
||||
name: string | UIElement,
|
||||
icon: UIElement,
|
||||
description: string | BaseUIElement,
|
||||
name: string | BaseUIElement,
|
||||
icon: BaseUIElement,
|
||||
tags: Tag[],
|
||||
layerToAddTo: {
|
||||
layerDef: LayerConfig,
|
||||
|
@ -30,11 +30,11 @@ export default class SimpleAddUI extends UIElement {
|
|||
}>
|
||||
= new UIEventSource(undefined);
|
||||
|
||||
private _component: UIElement;
|
||||
private _component:BaseUIElement;
|
||||
|
||||
private readonly openLayerControl: UIElement;
|
||||
private readonly cancelButton: UIElement;
|
||||
private readonly goToInboxButton: UIElement = new SubtleButton(Svg.envelope_ui(),
|
||||
private readonly openLayerControl: BaseUIElement;
|
||||
private readonly cancelButton: BaseUIElement;
|
||||
private readonly goToInboxButton: BaseUIElement = new SubtleButton(Svg.envelope_ui(),
|
||||
Translations.t.general.goToInbox, {url: "https://www.openstreetmap.org/messages/inbox", newTab: false});
|
||||
|
||||
constructor(isShown: UIEventSource<boolean>) {
|
||||
|
@ -75,16 +75,15 @@ export default class SimpleAddUI extends UIElement {
|
|||
State.state.LastClickLocation.addCallback(() => {
|
||||
self._confirmPreset.setData(undefined)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
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;
|
||||
if (userDetails === undefined) {
|
||||
return undefined;
|
||||
|
@ -121,21 +120,17 @@ export default class SimpleAddUI extends UIElement {
|
|||
}
|
||||
|
||||
|
||||
private CreateContent(): UIElement {
|
||||
private CreateContent(): BaseUIElement {
|
||||
const confirmPanel = this.CreateConfirmPanel();
|
||||
if (confirmPanel !== undefined) {
|
||||
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) {
|
||||
testMode = new Combine([
|
||||
"<span class='alert'>",
|
||||
"Test mode - changes won't be saved",
|
||||
"</span>"
|
||||
]);
|
||||
testMode = Translations.t.general.testing.Clone().SetClass("alert")
|
||||
}
|
||||
|
||||
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;
|
||||
if (preset === undefined) {
|
||||
return undefined;
|
||||
|
@ -195,7 +190,7 @@ export default class SimpleAddUI extends UIElement {
|
|||
const presets = layer.layerDef.presets;
|
||||
for (const preset of presets) {
|
||||
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;
|
||||
let tagInfo = undefined;
|
||||
|
|
|
@ -1,62 +1,51 @@
|
|||
import Locale from "../i18n/Locale";
|
||||
import {UIElement} from "../UIElement";
|
||||
import State from "../../State";
|
||||
import Combine from "../Base/Combine";
|
||||
import LanguagePicker from "../LanguagePicker";
|
||||
import Translations from "../i18n/Translations";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||
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>;
|
||||
import Toggle from "../Input/Toggle";
|
||||
|
||||
export default class ThemeIntroductionPanel extends VariableUiElement {
|
||||
|
||||
constructor() {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this.ListenTo(Locale.language);
|
||||
this.languagePicker = LanguagePicker.CreateLanguagePicker(State.state.layoutToUse.data.language, Translations.t.general.pickLanguage);
|
||||
this._layout = State.state.layoutToUse;
|
||||
this.ListenTo(State.state.layoutToUse);
|
||||
|
||||
const languagePicker =
|
||||
new VariableUiElement(
|
||||
State.state.layoutToUse.map(layout => LanguagePicker.CreateLanguagePicker(layout.language, Translations.t.general.pickLanguage))
|
||||
)
|
||||
;
|
||||
|
||||
const plzLogIn =
|
||||
Translations.t.general.loginWithOpenStreetMap
|
||||
.onClick(() => {
|
||||
State.state.osmConnection.AttemptLogin()
|
||||
});
|
||||
|
||||
|
||||
const welcomeBack = Translations.t.general.welcomeBack;
|
||||
|
||||
this.loginStatus = new VariableUiElement(
|
||||
State.state.osmConnection.userDetails.map(
|
||||
userdetails => {
|
||||
if (State.state.featureSwitchUserbadge.data) {
|
||||
return "";
|
||||
}
|
||||
return (userdetails.loggedIn ? welcomeBack : plzLogIn).Render();
|
||||
}
|
||||
)
|
||||
)
|
||||
this.SetClass("link-underline")
|
||||
}
|
||||
|
||||
InnerRender(): BaseUIElement {
|
||||
const layout : LayoutConfig = this._layout.data;
|
||||
return new Combine([
|
||||
|
||||
const welcomeBack = Translations.t.general.welcomeBack;
|
||||
|
||||
const loginStatus =
|
||||
new Toggle(
|
||||
new Toggle(
|
||||
welcomeBack,
|
||||
plzLogIn,
|
||||
State.state.osmConnection.isLoggedIn
|
||||
),
|
||||
undefined,
|
||||
State.state.featureSwitchUserbadge
|
||||
)
|
||||
|
||||
|
||||
super(State.state.layoutToUse.map (layout => new Combine([
|
||||
layout.description,
|
||||
"<br/><br/>",
|
||||
this.loginStatus,
|
||||
loginStatus,
|
||||
layout.descriptionTail,
|
||||
"<br/>",
|
||||
this.languagePicker,
|
||||
languagePicker,
|
||||
...layout.CustomCodeSnippets()
|
||||
])
|
||||
])))
|
||||
|
||||
this.SetClass("link-underline")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
/**
|
||||
* Handles and updates the user badge
|
||||
*/
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||
import Svg from "../../Svg";
|
||||
import State from "../../State";
|
||||
import Combine from "../Base/Combine";
|
||||
|
@ -12,133 +9,127 @@ import {FixedUiElement} from "../Base/FixedUiElement";
|
|||
import LanguagePicker from "../LanguagePicker";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Link from "../Base/Link";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import Img from "../Base/Img";
|
||||
|
||||
export default class UserBadge extends UIElement {
|
||||
private _userDetails: UIEventSource<UserDetails>;
|
||||
private _logout: UIElement;
|
||||
private _homeButton: UIElement;
|
||||
private _languagePicker: UIElement;
|
||||
|
||||
private _loginButton: UIElement;
|
||||
export default class UserBadge extends Toggle {
|
||||
|
||||
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()
|
||||
.SetClass("userbadge-login pt-3 w-full")
|
||||
.onClick(() => State.state.osmConnection.AttemptLogin());
|
||||
this._logout =
|
||||
|
||||
|
||||
const logout =
|
||||
Svg.logout_svg()
|
||||
.onClick(() => {
|
||||
State.state.osmConnection.LogOut();
|
||||
});
|
||||
|
||||
this._userDetails.addCallback(function () {
|
||||
const profilePic = document.getElementById("profile-pic");
|
||||
if (profilePic) {
|
||||
|
||||
profilePic.onload = function () {
|
||||
profilePic.style.opacity = "1"
|
||||
};
|
||||
}
|
||||
});
|
||||
const userBadge = userDetails.map(user => {
|
||||
{
|
||||
const homeButton = new VariableUiElement(
|
||||
userDetails.map((userinfo) => {
|
||||
if (userinfo.home) {
|
||||
return Svg.home_ui();
|
||||
}
|
||||
return " ";
|
||||
})
|
||||
).onClick(() => {
|
||||
const home = State.state.osmConnection.userDetails.data?.home;
|
||||
if (home === undefined) {
|
||||
return;
|
||||
}
|
||||
State.state.leafletMap.data.setView([home.lat, home.lon], 16);
|
||||
});
|
||||
|
||||
this._homeButton = new VariableUiElement(
|
||||
this._userDetails.map((userinfo) => {
|
||||
if (userinfo.home) {
|
||||
return Svg.home_ui().Render();
|
||||
const linkStyle = "flex items-baseline"
|
||||
const languagePicker = (LanguagePicker.CreateLanguagePicker(State.state.layoutToUse.data.language) ?? new FixedUiElement(""))
|
||||
.SetStyle("width:min-content;");
|
||||
|
||||
let messageSpan =
|
||||
new Link(
|
||||
new Combine([Svg.envelope, "" + user.totalMessages]).SetClass(linkStyle),
|
||||
'https://www.openstreetmap.org/messages/inbox',
|
||||
true
|
||||
)
|
||||
|
||||
|
||||
const csCount =
|
||||
new Link(
|
||||
new Combine([Svg.star, "" + user.csCount]).SetClass(linkStyle),
|
||||
`https://www.openstreetmap.org/user/${user.name}/history`,
|
||||
true);
|
||||
|
||||
|
||||
if (user.unreadMessages > 0) {
|
||||
messageSpan = new Link(
|
||||
new Combine([Svg.envelope, "" + user.unreadMessages]),
|
||||
'https://www.openstreetmap.org/messages/inbox',
|
||||
true
|
||||
).SetClass("alert")
|
||||
}
|
||||
return " ";
|
||||
})
|
||||
).onClick(() => {
|
||||
const home = State.state.osmConnection.userDetails.data?.home;
|
||||
if (home === undefined) {
|
||||
return;
|
||||
|
||||
let dryrun = new FixedUiElement("");
|
||||
if (user.dryRun) {
|
||||
dryrun = new FixedUiElement("TESTING").SetClass("alert");
|
||||
}
|
||||
|
||||
const settings =
|
||||
new Link(Svg.gear_svg(),
|
||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`,
|
||||
true)
|
||||
|
||||
|
||||
const userIcon = new Link(
|
||||
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)}`,
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
const userName = new Link(
|
||||
new FixedUiElement(user.name),
|
||||
`https://www.openstreetmap.org/user/${user.name}`,
|
||||
true);
|
||||
|
||||
|
||||
const userStats = new Combine([
|
||||
homeButton,
|
||||
settings,
|
||||
messageSpan,
|
||||
csCount,
|
||||
languagePicker,
|
||||
logout
|
||||
])
|
||||
.SetClass("userstats")
|
||||
|
||||
const usertext = new Combine([
|
||||
userName,
|
||||
dryrun,
|
||||
userStats
|
||||
]).SetClass("usertext")
|
||||
|
||||
return new Combine([
|
||||
userIcon,
|
||||
usertext,
|
||||
]).SetClass("h-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"
|
||||
|
||||
let messageSpan: UIElement =
|
||||
new Link(
|
||||
new Combine([Svg.envelope, "" + user.totalMessages]).SetClass(linkStyle),
|
||||
'https://www.openstreetmap.org/messages/inbox',
|
||||
true
|
||||
)
|
||||
|
||||
|
||||
const csCount =
|
||||
new Link(
|
||||
new Combine([Svg.star, "" + user.csCount]).SetClass(linkStyle),
|
||||
`https://www.openstreetmap.org/user/${user.name}/history`,
|
||||
true);
|
||||
|
||||
|
||||
if (user.unreadMessages > 0) {
|
||||
messageSpan = new Link(
|
||||
new Combine([Svg.envelope, "" + user.unreadMessages]),
|
||||
'https://www.openstreetmap.org/messages/inbox',
|
||||
true
|
||||
).SetClass("alert")
|
||||
}
|
||||
|
||||
let dryrun: UIElement = new FixedUiElement("");
|
||||
if (user.dryRun) {
|
||||
dryrun = new FixedUiElement("TESTING").SetClass("alert");
|
||||
}
|
||||
|
||||
const settings =
|
||||
new Link(Svg.gear_svg(),
|
||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`,
|
||||
true)
|
||||
|
||||
|
||||
const userIcon = new Link(
|
||||
new FixedUiElement(`<img id='profile-pic' src='${user.img}' alt='profile-pic'/>`),
|
||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}`,
|
||||
true
|
||||
);
|
||||
|
||||
|
||||
const userName = new Link(
|
||||
new FixedUiElement(user.name),
|
||||
`https://www.openstreetmap.org/user/${user.name}`,
|
||||
true);
|
||||
|
||||
|
||||
const userStats = new Combine([
|
||||
this._homeButton,
|
||||
settings,
|
||||
messageSpan,
|
||||
csCount,
|
||||
this._languagePicker,
|
||||
this._logout
|
||||
])
|
||||
.SetClass("userstats")
|
||||
|
||||
const usertext = new Combine([
|
||||
userName,
|
||||
dryrun,
|
||||
userStats
|
||||
]).SetClass("usertext")
|
||||
|
||||
return new Combine([
|
||||
userIcon,
|
||||
usertext,
|
||||
])
|
||||
super(
|
||||
new VariableUiElement(userBadge),
|
||||
loginButton,
|
||||
State.state.osmConnection.isLoggedIn
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue