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
				
			
		|  | @ -25,7 +25,6 @@ import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; | |||
| import LayerResetter from "./Logic/Actors/LayerResetter"; | ||||
| import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs"; | ||||
| import LayerControlPanel from "./UI/BigComponents/LayerControlPanel"; | ||||
| import FeatureSwitched from "./UI/Base/FeatureSwitched"; | ||||
| import ShowDataLayer from "./UI/ShowDataLayer"; | ||||
| import Hash from "./Logic/Web/Hash"; | ||||
| import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; | ||||
|  | @ -39,7 +38,6 @@ import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson"; | |||
| import AttributionPanel from "./UI/BigComponents/AttributionPanel"; | ||||
| import ContributorCount from "./Logic/ContributorCount"; | ||||
| import FeatureSource from "./Logic/FeatureSource/FeatureSource"; | ||||
| import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | ||||
| import AllKnownLayers from "./Customizations/AllKnownLayers"; | ||||
| import LayerConfig from "./Customizations/JSON/LayerConfig"; | ||||
| 
 | ||||
|  | @ -170,13 +168,14 @@ export class InitUiElements { | |||
|                 marker.addTo(State.state.leafletMap.data) | ||||
|             }); | ||||
| 
 | ||||
|         const geolocationButton = new FeatureSwitched( | ||||
|         const geolocationButton = new Toggle( | ||||
|             new MapControlButton( | ||||
|                 new GeoLocationHandler( | ||||
|                     State.state.currentGPSLocation, | ||||
|                     State.state.leafletMap, | ||||
|                     State.state.layoutToUse | ||||
|                 )), | ||||
|             undefined, | ||||
|             State.state.featureSwitchGeolocation); | ||||
| 
 | ||||
|         const plus = new MapControlButton( | ||||
|  | @ -193,7 +192,7 @@ export class InitUiElements { | |||
|             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") | ||||
|             .AttachTo("bottom-right"); | ||||
| 
 | ||||
|  | @ -212,8 +211,6 @@ export class InitUiElements { | |||
|         // Reset the loading message once things are loaded
 | ||||
|         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 {LocalStorageSource} from "../Web/LocalStorageSource"; | ||||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| import {VariableUiElement} from "../../UI/Base/VariableUIElement"; | ||||
| 
 | ||||
| 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 _layoutToUse: UIEventSource<LayoutConfig>; | ||||
| 
 | ||||
| 
 | ||||
|     private readonly _element: BaseUIElement; | ||||
| 
 | ||||
|     constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, | ||||
|                 leafletMap: UIEventSource<L.Map>, | ||||
|                 layoutToUse: UIEventSource<LayoutConfig>) { | ||||
|         super(undefined); | ||||
|         super(); | ||||
|         this._currentGPSLocation = currentGPSLocation; | ||||
|         this._leafletMap = leafletMap; | ||||
|         this._layoutToUse = layoutToUse; | ||||
|         this._hasLocation = currentGPSLocation.map((location) => location !== undefined); | ||||
|         this.dumbMode = false; | ||||
| 
 | ||||
|         const self = this; | ||||
|         import("../../vendor/Leaflet.AccuratePosition.js").then(() => { | ||||
|             self.init(); | ||||
|         }) | ||||
| 
 | ||||
|         const currentPointer = this._isActive.map(isActive => { | ||||
|             if (isActive && !self._hasLocation.data) { | ||||
|  | @ -76,60 +78,34 @@ export default class GeoLocationHandler extends UIElement { | |||
|             self.SetClass(pointerClass); | ||||
|             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) { | ||||
|             return Svg.crosshair_blue_img; | ||||
|         } | ||||
|         if (this._isActive.data) { | ||||
|             return Svg.crosshair_blue_center_img; | ||||
|         } | ||||
| 
 | ||||
|         return Svg.crosshair_img; | ||||
|     protected InnerRender(): string | BaseUIElement { | ||||
|         return this._element | ||||
|     } | ||||
| 
 | ||||
|     InnerUpdate(htmlElement: HTMLElement) { | ||||
|         super.InnerUpdate(htmlElement); | ||||
|     private init(askPermission: boolean) { | ||||
| 
 | ||||
|         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; | ||||
|         map.on('accuratepositionprogress', onAccuratePositionProgress); | ||||
|         map.on('accuratepositionfound', onAccuratePositionFound); | ||||
|         map.on('accuratepositionerror', onAccuratePositionError); | ||||
| 
 | ||||
| 
 | ||||
|         this._currentGPSLocation.addCallback((location) => { | ||||
|             self._previousLocationGrant.setData("granted"); | ||||
|  | @ -178,7 +154,9 @@ export default class GeoLocationHandler extends UIElement { | |||
|         } catch (e) { | ||||
|             console.error(e) | ||||
|         } | ||||
|         if (this._previousLocationGrant.data === "granted") { | ||||
|         if (askPermission) { | ||||
|             self.StartGeolocating(true); | ||||
|         } else if (this._previousLocationGrant.data === "granted") { | ||||
|             this._previousLocationGrant.setData(""); | ||||
|             self.StartGeolocating(false); | ||||
|         } | ||||
|  | @ -210,7 +188,7 @@ export default class GeoLocationHandler extends UIElement { | |||
|     private MoveToCurrentLoction(targetZoom = 16) { | ||||
|         const location = this._currentGPSLocation.data; | ||||
|         this._lastUserRequest = undefined; | ||||
|          | ||||
| 
 | ||||
| 
 | ||||
|         if (this._currentGPSLocation.data.latlng[0] === 0 && this._currentGPSLocation.data.latlng[1] === 0) { | ||||
|             console.debug("Not moving to GPS-location: it is null island") | ||||
|  |  | |||
|  | @ -2,12 +2,9 @@ import {UIEventSource} from "../UIEventSource"; | |||
| import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | ||||
| import Translations from "../../UI/i18n/Translations"; | ||||
| import Locale from "../../UI/i18n/Locale"; | ||||
| import {UIElement} from "../../UI/UIElement"; | ||||
| import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer"; | ||||
| import {ElementStorage} from "../ElementStorage"; | ||||
| import Combine from "../../UI/Base/Combine"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| import {FixedUiElement} from "../../UI/Base/FixedUiElement"; | ||||
| 
 | ||||
| class TitleElement extends UIEventSource<string> { | ||||
|      | ||||
|  | @ -42,7 +39,8 @@ class TitleElement extends UIEventSource<string> { | |||
|                             continue; | ||||
|                         } | ||||
|                         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; | ||||
|                         } | ||||
|                     } | ||||
|  |  | |||
|  | @ -24,6 +24,7 @@ export class OsmConnection { | |||
| 
 | ||||
|     public auth; | ||||
|     public userDetails: UIEventSource<UserDetails>; | ||||
|     public isLoggedIn: UIEventSource<boolean> | ||||
|     _dryRun: boolean; | ||||
| 
 | ||||
|     public preferencesHandler: OsmPreferences; | ||||
|  | @ -42,6 +43,7 @@ export class OsmConnection { | |||
| 
 | ||||
|         this.userDetails = new UIEventSource<UserDetails>(new UserDetails(), "userDetails"); | ||||
|         this.userDetails.data.dryRun = dryRun; | ||||
|         this.isLoggedIn = this.userDetails.map(user => user.loggedIn) | ||||
|         this._dryRun = dryRun; | ||||
|          | ||||
|         this.updateAuthObject(); | ||||
|  |  | |||
							
								
								
									
										4
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -70,10 +70,6 @@ export default class State { | |||
|         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 | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Locale from "../i18n/Locale"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export class Button extends UIElement { | ||||
|     private _text: UIElement; | ||||
|     private _text: BaseUIElement; | ||||
|     private _onclick: () => void; | ||||
|     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 BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export class FixedUiElement extends UIElement { | ||||
| export class FixedUiElement extends BaseUIElement { | ||||
|     private _html: string; | ||||
| 
 | ||||
|     constructor(html: string) { | ||||
|         super(undefined); | ||||
|         super(); | ||||
|         this._html = html ?? ""; | ||||
|     } | ||||
|      | ||||
|  | @ -12,4 +13,10 @@ export class FixedUiElement extends UIElement { | |||
|         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 { | ||||
|         const el = document.createElement("img") | ||||
|         el.src = this._src; | ||||
|         el.onload = () => { | ||||
|             el.style.opacity = "1" | ||||
|         } | ||||
|         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 { | ||||
|     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(); | ||||
|         const _embeddedShow = Translations.W(embeddedShow); | ||||
| 
 | ||||
| 
 | ||||
|         const el = document.createElement("a") | ||||
|          | ||||
|         if(typeof target === "string"){ | ||||
|             el.href = target | ||||
|         if(typeof href === "string"){ | ||||
|             el.href = href | ||||
|         }else{ | ||||
|             target.addCallbackAndRun(target => { | ||||
|                 el.target = target; | ||||
|             href.addCallbackAndRun(href => { | ||||
|                 el.href = href; | ||||
|             }) | ||||
|         } | ||||
|         if (newTab) { | ||||
|  |  | |||
|  | @ -1,7 +1,4 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Svg from "../../Svg"; | ||||
| import State from "../../State"; | ||||
| 
 | ||||
| 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 Img from "./Img"; | ||||
| 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) { | ||||
|         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") | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|         let img; | ||||
|         if ((imageUrl ?? "") === "") { | ||||
|  | @ -28,26 +31,27 @@ export class SubtleButton extends Combine { | |||
|         img?.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0") | ||||
|         const image = new Combine([img]) | ||||
|             .SetClass("flex-shrink-0"); | ||||
|          | ||||
| 
 | ||||
|         if (linkTo == undefined) { | ||||
|             return [ | ||||
|             return new Combine([ | ||||
|                 image, | ||||
|                 message, | ||||
|             ]; | ||||
|             ]); | ||||
|         } | ||||
|          | ||||
|          | ||||
|         return [ | ||||
|             new Link( | ||||
|                 new Combine([ | ||||
|                     image, | ||||
|                     message?.SetClass("block ml-4 overflow-ellipsis") | ||||
|                 ]).SetClass("flex group"), | ||||
|                 linkTo.url, | ||||
|                 linkTo.newTab ?? false | ||||
|             ) | ||||
|         ]; | ||||
| 
 | ||||
| 
 | ||||
|         return new Link( | ||||
|             new Combine([ | ||||
|                 image, | ||||
|                 message?.SetClass("block ml-4 overflow-ellipsis") | ||||
|             ]).SetClass("flex group"), | ||||
|             linkTo.url, | ||||
|             linkTo.newTab ?? false | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     protected InnerRender(): string | BaseUIElement { | ||||
|         return this._element; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,41 +1,33 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| 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; | ||||
|     private content: UIElement[] = []; | ||||
| 
 | ||||
|     constructor(elements: { header: UIElement | string, content: UIElement | string }[], openedTab: (UIEventSource<number> | number) = 0) { | ||||
|         super(typeof (openedTab) === "number" ? new UIEventSource(openedTab) : (openedTab ?? new UIEventSource<number>(0))); | ||||
|         const self = this; | ||||
|         const tabs: UIElement[] = [] | ||||
|     constructor(elements: { header: BaseUIElement | string, content: BaseUIElement | string }[], openedTab: (UIEventSource<number> | number) = 0) { | ||||
| 
 | ||||
|         const openedTabSrc = typeof (openedTab) === "number" ? new UIEventSource(openedTab) : (openedTab ?? new UIEventSource<number>(0)) | ||||
|              | ||||
|         const tabs: BaseUIElement[] = [] | ||||
|         const contentElements: BaseUIElement[] = []; | ||||
|         for (let i = 0; i < elements.length; 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) | ||||
|             this.content.push(content); | ||||
|             if (!this.content[i].IsEmpty()) { | ||||
|                 const tab = header.SetClass("block tab-single-header") | ||||
|                 tabs.push(tab) | ||||
|             } | ||||
|             content.SetClass("tab-content") | ||||
|             contentElements.push(content); | ||||
|             const tab = header.SetClass("block tab-single-header") | ||||
|             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 | ||||
|      * @param clss | ||||
|      * @constructor | ||||
|      */ | ||||
|     public SetClass(clss: string) { | ||||
|         const all = clss.split(" ").map(clsName => clsName.trim()); | ||||
|         let recordedChange = false; | ||||
|         for (const c of all) { | ||||
|         for (let c of all) { | ||||
|             c = c.trim(); | ||||
|             if (this.clss.has(clss)) { | ||||
|                 continue; | ||||
|             } | ||||
|             if(c === undefined || c === ""){ | ||||
|                 continue; | ||||
|             } | ||||
|             this.clss.add(c); | ||||
|             recordedChange = true; | ||||
|         } | ||||
|  |  | |||
|  | @ -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 | ||||
|         ) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,62 +1,46 @@ | |||
| import {UIElement} from "./UIElement"; | ||||
| import Translations from "./i18n/Translations"; | ||||
| import State from "../State"; | ||||
| import {VariableUiElement} from "./Base/VariableUIElement"; | ||||
| 
 | ||||
| export default class CenterMessageBox extends UIElement { | ||||
| export default class CenterMessageBox extends VariableUiElement { | ||||
| 
 | ||||
|     constructor() { | ||||
|         super(State.state.centerMessage); | ||||
|         const state = State.state; | ||||
|         const updater = State.state.layerUpdater; | ||||
|         const t = Translations.t.centerMessage; | ||||
|         const message = updater.runningQuery.map( | ||||
|             isRunning => { | ||||
|                 if (isRunning) { | ||||
|                     return {el: t.loadingData}; | ||||
|                 } | ||||
|                 if (!updater.sufficientlyZoomed.data) { | ||||
|                     return {el: t.zoomIn} | ||||
|                 } | ||||
|                 if (updater.timeout.data > 0) { | ||||
|                     return {el: t.retrying.Subs({count: "" + updater.timeout.data})} | ||||
|                 } | ||||
|                 return {el: t.ready, isDone: true} | ||||
| 
 | ||||
|         this.ListenTo(State.state.locationControl); | ||||
|         this.ListenTo(State.state.layerUpdater.timeout); | ||||
|         this.ListenTo(State.state.layerUpdater.runningQuery); | ||||
|         this.ListenTo(State.state.layerUpdater.sufficientlyZoomed); | ||||
|     } | ||||
|             }, | ||||
|             [updater.timeout, updater.sufficientlyZoomed, state.locationControl] | ||||
|         ) | ||||
|          | ||||
|         super(message.map(toShow => toShow.el)) | ||||
|          | ||||
|         this.SetClass("block " + | ||||
|             "rounded-3xl bg-white text-xl font-bold text-center pointer-events-none p-4") | ||||
|         this.SetStyle("transition: opacity 750ms linear") | ||||
| 
 | ||||
|     private static prep(): { innerHtml: string | UIElement, done: boolean } { | ||||
|         if (State.state.centerMessage.data != "") { | ||||
|             return {innerHtml: State.state.centerMessage.data, done: false}; | ||||
|         } | ||||
|         const lu = State.state.layerUpdater; | ||||
|         if (lu.timeout.data > 0) { | ||||
|             return { | ||||
|                 innerHtml: Translations.t.centerMessage.retrying.Subs({count: "" + lu.timeout.data}), | ||||
|                 done: false | ||||
|             }; | ||||
|         } | ||||
|         message.addCallbackAndRun(toShow => { | ||||
|             const isDone = toShow.isDone ?? false; | ||||
|             if (isDone) { | ||||
|                 this.SetStyle("transition: opacity 750ms linear; opacity: 0") | ||||
|             } else { | ||||
|                 this.SetStyle("transition: opacity 750ms linear; opacity: 0.75") | ||||
| 
 | ||||
|         if (lu.runningQuery.data) { | ||||
|             return {innerHtml: Translations.t.centerMessage.loadingData, done: false}; | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         } | ||||
|         if (!lu.sufficientlyZoomed.data) { | ||||
|             return {innerHtml: Translations.t.centerMessage.zoomIn, done: false}; | ||||
|         } else { | ||||
|             return {innerHtml: Translations.t.centerMessage.ready, done: true}; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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 Combine from "../Base/Combine"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| 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([ | ||||
|             icon?.SetClass("block left").SetStyle("height: 2em; width: 2em; padding-right: 0.5em"), | ||||
|             new Combine([ | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ import {Tag} from "../../Logic/Tags/Tag"; | |||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import LicensePicker from "../BigComponents/LicensePicker"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import FileSelectorButton from "../Base/FileSelectorButton"; | ||||
| import FileSelectorButton from "../Input/FileSelectorButton"; | ||||
| import ImgurUploader from "../../Logic/Web/ImgurUploader"; | ||||
| import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI"; | ||||
| import LayerConfig from "../../Customizations/JSON/LayerConfig"; | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import {InputElement} from "./InputElement"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| /** | ||||
|  | @ -10,20 +9,57 @@ import BaseUIElement from "../BaseUIElement"; | |||
| export default class CheckBoxes extends InputElement<number[]> { | ||||
|     IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
| 
 | ||||
|     private readonly value: UIEventSource<number[]>; | ||||
|     private readonly _elements: BaseUIElement[] | ||||
|      | ||||
|      | ||||
| private readonly _element : HTMLElement | ||||
| 
 | ||||
|     constructor(elements: BaseUIElement[]) { | ||||
|      | ||||
|     private static _nextId = 0; | ||||
| private readonly value: UIEventSource<number[]> | ||||
|     constructor(elements: BaseUIElement[], value =new UIEventSource<number[]>([])) { | ||||
|         super(); | ||||
|         this._elements = Utils.NoNull(elements); | ||||
|         this.value = new UIEventSource<number[]>([]) | ||||
|         this.value = value; | ||||
|         elements = Utils.NoNull(elements); | ||||
|          | ||||
|          | ||||
|         const el = document.createElement() | ||||
|         this._element = el; | ||||
|         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")) | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|  | @ -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(); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -21,7 +21,7 @@ export class DropDown<T> extends InputElement<T> { | |||
|                 } | ||||
|     ) { | ||||
|         super(); | ||||
| this._values = values; | ||||
|         this._values = values; | ||||
|         if (values.length <= 1) { | ||||
|             return; | ||||
|         } | ||||
|  | @ -36,9 +36,11 @@ this._values = values; | |||
| 
 | ||||
|         { | ||||
|             const labelEl = Translations.W(label).ConstructElement() | ||||
|             const labelHtml = document.createElement("label") | ||||
|             labelHtml.appendChild(labelEl) | ||||
|             labelHtml.htmlFor = el.id; | ||||
|             if (labelEl !== undefined) { | ||||
|                 const labelHtml = document.createElement("label") | ||||
|                 labelHtml.appendChild(labelEl) | ||||
|                 labelHtml.htmlFor = el.id; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -56,14 +58,14 @@ this._values = values; | |||
|                 var index = select.selectedIndex; | ||||
|                 value.setData(values[index].value); | ||||
|             }); | ||||
|              | ||||
| 
 | ||||
|             value.addCallbackAndRun(selected => { | ||||
|                 for (let i = 0; i < values.length; i++) { | ||||
|                     const value = values[i].value; | ||||
|                     if (value === selected) { | ||||
|                         select.selectedIndex = i; | ||||
|                     } | ||||
|                 }  | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,10 +4,9 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | |||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export class TextField extends InputElement<string> { | ||||
|     private readonly value: UIEventSource<string>; | ||||
|     public readonly enterPressed = new UIEventSource<string>(undefined); | ||||
|     public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||
|      | ||||
|     private readonly value: UIEventSource<string>; | ||||
|     private _element: HTMLElement; | ||||
|     private readonly _isValid: (s: string, country?: () => string) => boolean; | ||||
| 
 | ||||
|  | @ -19,6 +18,7 @@ export class TextField extends InputElement<string> { | |||
|         inputMode?: string, | ||||
|         label?: BaseUIElement, | ||||
|         textAreaRows?: number, | ||||
|         inputStyle?: string, | ||||
|         isValid?: ((s: string, country?: () => string) => boolean) | ||||
|     }) { | ||||
|         super(); | ||||
|  | @ -26,39 +26,40 @@ export class TextField extends InputElement<string> { | |||
|         options = options ?? {}; | ||||
|         this.value = options?.value ?? new UIEventSource<string>(undefined); | ||||
|         this._isValid = options.isValid ?? (_ => true); | ||||
|          | ||||
| 
 | ||||
|         this.onClick(() => { | ||||
|             self.IsSelected.setData(true) | ||||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         const placeholder = Translations.W(options.placeholder ?? "").ConstructElement().innerText.replace("'", "'"); | ||||
| 
 | ||||
|         const placeholder = Translations.W(options. placeholder ?? "").ConstructElement().innerText.replace("'", "'"); | ||||
|          | ||||
|         this.SetClass("form-text-field") | ||||
|         let inputEl : HTMLElement | ||||
|         if(options.htmlType === "area"){ | ||||
|         let inputEl: HTMLElement | ||||
|         if (options.htmlType === "area") { | ||||
|             const el = document.createElement("textarea") | ||||
|             el.placeholder = placeholder | ||||
|             el.rows = options.textAreaRows | ||||
|             el.cols = 50 | ||||
|             el.style.cssText = "max-width: 100%; width: 100%; box-sizing: border-box" | ||||
|             inputEl = el; | ||||
|         }else{ | ||||
|         } else { | ||||
|             const el = document.createElement("input") | ||||
|             el.type = options.htmlType | ||||
|                 el.inputMode = options.inputMode | ||||
|             el.type = options.htmlType ?? "text" | ||||
|             el.inputMode = options.inputMode | ||||
|             el.placeholder = placeholder | ||||
|             el.style.cssText = options.inputStyle | ||||
|             inputEl = el | ||||
|         } | ||||
| 
 | ||||
|         const form = document.createElement("form") | ||||
|         form.appendChild(inputEl) | ||||
|         form.onsubmit = () => false; | ||||
|         | ||||
|        if(options.label){ | ||||
|            form.appendChild(options.label.ConstructElement()) | ||||
|        } | ||||
|          | ||||
| 
 | ||||
|         if (options.label) { | ||||
|             form.appendChild(options.label.ConstructElement()) | ||||
|         } | ||||
| 
 | ||||
|         this._element = form; | ||||
| 
 | ||||
|         const field = inputEl; | ||||
|  | @ -70,9 +71,9 @@ export class TextField extends InputElement<string> { | |||
|             } | ||||
|             // @ts-ignore
 | ||||
|             field.value = value; | ||||
|             if(self.IsValid(value)){ | ||||
|             if (self.IsValid(value)) { | ||||
|                 self.RemoveClass("invalid") | ||||
|             }else{ | ||||
|             } else { | ||||
|                 self.SetClass("invalid") | ||||
|             } | ||||
| 
 | ||||
|  | @ -82,7 +83,7 @@ export class TextField extends InputElement<string> { | |||
| 
 | ||||
|             // How much characters are on the right, not including spaces?
 | ||||
|             // @ts-ignore
 | ||||
|             const endDistance = field.value.substring(field.selectionEnd).replace(/ /g,'').length; | ||||
|             const endDistance = field.value.substring(field.selectionEnd).replace(/ /g, '').length; | ||||
|             // @ts-ignore
 | ||||
|             let val: string = field.value; | ||||
|             if (!self.IsValid(val)) { | ||||
|  | @ -97,18 +98,18 @@ export class TextField extends InputElement<string> { | |||
|             // @ts-ignore
 | ||||
|             val = field.value; | ||||
|             let newCursorPos = val.length - endDistance; | ||||
|             while(newCursorPos >= 0 && | ||||
|             while (newCursorPos >= 0 && | ||||
|                 // We count the number of _actual_ characters (non-space characters) on the right of the new value
 | ||||
|                 // This count should become bigger then the end distance
 | ||||
|                 val.substr(newCursorPos).replace(/ /g, '').length < endDistance | ||||
|                 ){ | ||||
|                 newCursorPos --; | ||||
|                 ) { | ||||
|                 newCursorPos--; | ||||
|             } | ||||
|             // @ts-ignore
 | ||||
|             TextField.SetCursorPosition(newCursorPos); | ||||
|         }; | ||||
| 
 | ||||
|          | ||||
| 
 | ||||
|         field.addEventListener("focusin", () => self.IsSelected.setData(true)); | ||||
|         field.addEventListener("focusout", () => self.IsSelected.setData(false)); | ||||
| 
 | ||||
|  | @ -118,22 +119,13 @@ export class TextField extends InputElement<string> { | |||
|                 // @ts-ignore
 | ||||
|                 self.enterPressed.setData(field.value); | ||||
|             } | ||||
|         });         | ||||
|          | ||||
|          | ||||
|          | ||||
|     } | ||||
|         }); | ||||
| 
 | ||||
|     GetValue(): UIEventSource<string> { | ||||
|         return this.value; | ||||
|     } | ||||
| 
 | ||||
|     protected InnerConstructElement(): HTMLElement { | ||||
|         return this._element; | ||||
|     } | ||||
| 
 | ||||
|     private static SetCursorPosition(textfield: HTMLElement, i: number) { | ||||
|         if(textfield === undefined || textfield === null){ | ||||
|         if (textfield === undefined || textfield === null) { | ||||
|             return; | ||||
|         } | ||||
|         if (i === -1) { | ||||
|  | @ -146,6 +138,10 @@ export class TextField extends InputElement<string> { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     GetValue(): UIEventSource<string> { | ||||
|         return this.value; | ||||
|     } | ||||
| 
 | ||||
|     IsValid(t: string): boolean { | ||||
|         if (t === undefined || t === null) { | ||||
|             return false | ||||
|  | @ -153,4 +149,8 @@ export class TextField extends InputElement<string> { | |||
|         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>; | ||||
| 
 | ||||
|     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( | ||||
|             data.map(isEnabled => isEnabled ? showEnabled : showDisabled) | ||||
|             isEnabled.map(isEnabled => isEnabled ? showEnabled : showDisabled) | ||||
|         ); | ||||
|         this.isEnabled = isEnabled | ||||
|         this.onClick(() => { | ||||
|             data.setData(!data.data); | ||||
|             isEnabled.setData(!isEnabled.data); | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,13 +1,13 @@ | |||
| import {UIElement} from "./UIElement"; | ||||
| import {DropDown} from "./Input/DropDown"; | ||||
| import Locale from "./i18n/Locale"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| 
 | ||||
| export default class LanguagePicker { | ||||
| 
 | ||||
| 
 | ||||
|     public static CreateLanguagePicker( | ||||
|         languages : string[] , | ||||
|         label: string | UIElement = "") { | ||||
|         label: string | BaseUIElement = "") { | ||||
| 
 | ||||
|         if (languages.length <= 1) { | ||||
|             return undefined; | ||||
|  |  | |||
|  | @ -1,14 +1,16 @@ | |||
| import {UIElement} from "./UIElement"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| import Combine from "./Base/Combine"; | ||||
| 
 | ||||
| /** | ||||
|  * A button floating above the map, in a uniform style | ||||
|  */ | ||||
| export default class MapControlButton extends UIElement { | ||||
|     private _contents: UIElement; | ||||
|     private _contents: BaseUIElement; | ||||
|      | ||||
|     constructor(contents: UIElement) { | ||||
|     constructor(contents: BaseUIElement) { | ||||
|         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.SetStyle("box-shadow: 0 0 10px var(--shadow-color);"); | ||||
|     } | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import {OH} from "./OpeningHours"; | |||
| import Translations from "../i18n/Translations"; | ||||
| import Constants from "../../Models/Constants"; | ||||
| import opening_hours from "opening_hours"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| export default class OpeningHoursVisualization extends UIElement { | ||||
|     private static readonly weekdays = [ | ||||
|  | @ -87,7 +88,7 @@ export default class OpeningHoursVisualization extends UIElement { | |||
|         return new Date(d.setDate(diff)); | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string | UIElement { | ||||
|     InnerRender(): string | BaseUIElement { | ||||
| 
 | ||||
| 
 | ||||
|         const today = new Date(); | ||||
|  | @ -168,13 +169,13 @@ export default class OpeningHoursVisualization extends UIElement { | |||
|         latestclose = Math.max(19 * 60 * 60, latestclose + 30 * 60) | ||||
| 
 | ||||
| 
 | ||||
|         const rows: UIElement[] = []; | ||||
|         const rows: BaseUIElement[] = []; | ||||
|         const availableArea = latestclose - earliestOpen; | ||||
|         // @ts-ignore
 | ||||
|         const now = (100 * (((new Date() - today) / 1000) - earliestOpen)) / availableArea; | ||||
| 
 | ||||
| 
 | ||||
|         let header: UIElement[] = []; | ||||
|         let header: BaseUIElement[] = []; | ||||
| 
 | ||||
|         if (now >= 0 && now <= 100) { | ||||
|             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); | ||||
|             } | ||||
| 
 | ||||
|             let innerContent: (string | UIElement)[] = []; | ||||
|             let innerContent: (string | BaseUIElement)[] = []; | ||||
| 
 | ||||
|             // Add the lines
 | ||||
|             for (const changeMoment of changeHours) { | ||||
|  | @ -265,7 +266,7 @@ export default class OpeningHoursVisualization extends UIElement { | |||
| 
 | ||||
|         return new Combine([ | ||||
|             "<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>" | ||||
|         ]).SetClass("ohviz-container"); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,35 +1,47 @@ | |||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import {UIElement} from "../UIElement"; | ||||
| 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 { | ||||
| 
 | ||||
|     private readonly _value: UIEventSource<any>; | ||||
|     private readonly _friendlyLogin: UIElement; | ||||
|     private readonly _userDetails: UIEventSource<UserDetails>; | ||||
| 
 | ||||
|     private readonly _element: BaseUIElement; | ||||
| 
 | ||||
|     constructor(value: UIEventSource<any>, osmConnection: OsmConnection) { | ||||
|         super(value); | ||||
|         this._userDetails = osmConnection?.userDetails; | ||||
|         if(value === undefined){ | ||||
|         if (value === undefined) { | ||||
|             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") | ||||
|             .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() { | ||||
|         if(this._userDetails != undefined &&  !this._userDetails.data.loggedIn){ | ||||
|             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}`); | ||||
|     InnerRender(): BaseUIElement { | ||||
|         return this._element | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -19,8 +19,9 @@ export default class TagRenderingAnswer extends UIElement { | |||
|     private _contentStyle: string; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, configuration: TagRenderingConfig, contentClasses: string = "", contentStyle: string = "") { | ||||
|         super(tags); | ||||
|         super(); | ||||
|         this._tags = tags; | ||||
|         this.ListenTo(tags) | ||||
|         this._configuration = configuration; | ||||
|         this._contentClass = contentClasses; | ||||
|         this._contentStyle = contentStyle; | ||||
|  |  | |||
|  | @ -22,6 +22,7 @@ import {TagsFilter} from "../../Logic/Tags/TagsFilter"; | |||
| import {Tag} from "../../Logic/Tags/Tag"; | ||||
| import {And} from "../../Logic/Tags/And"; | ||||
| import {TagUtils} from "../../Logic/Tags/TagUtils"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| 
 | ||||
| /** | ||||
|  * Shows the question element. | ||||
|  | @ -35,7 +36,7 @@ export default class TagRenderingQuestion extends UIElement { | |||
| 
 | ||||
|     private _inputElement: InputElement<TagsFilter>; | ||||
|     private _cancelButton: UIElement; | ||||
|     private _appliedTags: UIElement; | ||||
|     private _appliedTags: BaseUIElement; | ||||
|     private _question: UIElement; | ||||
| 
 | ||||
|     constructor(tags: UIEventSource<any>, | ||||
|  | @ -82,16 +83,19 @@ export default class TagRenderingQuestion extends UIElement { | |||
|                         return ""; | ||||
|                     } | ||||
|                     if (tags === undefined) { | ||||
|                         return Translations.t.general.noTagsSelected.SetClass("subtle").Render(); | ||||
|                         return Translations.t.general.noTagsSelected.SetClass("subtle"); | ||||
|                     } | ||||
|                     if (csCount < Constants.userJourney.tagsVisibleAndWikiLinked) { | ||||
|                         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); | ||||
|                 } | ||||
|             ) | ||||
|         ).SetClass("block") | ||||
|          | ||||
|          | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     InnerRender() { | ||||
|  |  | |||
|  | @ -128,7 +128,7 @@ export default class ShowDataLayer { | |||
|         const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); | ||||
|         return L.marker(latLng, { | ||||
|             icon: L.divIcon({ | ||||
|                 html: style.icon.html.Render(), | ||||
|                 html: style.icon.html.ConstructElement(), | ||||
|                 className: style.icon.className, | ||||
|                 iconAnchor: style.icon.iconAnchor, | ||||
|                 iconUrl: style.icon.iconUrl, | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ export abstract class UIElement extends BaseUIElement{ | |||
|         if (source === undefined) { | ||||
|             return this; | ||||
|         } | ||||
|         console.trace("Got a listenTo in ", this.constructor.name) | ||||
|         const self = this; | ||||
|         source.addCallback(() => { | ||||
|             self.lastInnerRender = undefined; | ||||
|  | @ -39,7 +40,7 @@ export abstract class UIElement extends BaseUIElement{ | |||
|     } | ||||
| 
 | ||||
|     Render(): string { | ||||
|         return "Don't use Render!" | ||||
|         return this.InnerRenderAsString() | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -52,11 +53,6 @@ export abstract class UIElement extends BaseUIElement{ | |||
|         return rendered | ||||
|     } | ||||
| 
 | ||||
|     public IsEmpty(): boolean { | ||||
|         return this.InnerRender() === undefined || this.InnerRender() === ""; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Should be overridden for specific HTML functionality | ||||
|  |  | |||
|  | @ -55,17 +55,6 @@ | |||
|     display: block; | ||||
| } | ||||
| 
 | ||||
| #profile-pic { | ||||
|     float: left; | ||||
|     width: 4em; | ||||
|     height: 4em; | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
|     opacity: 0; | ||||
|     transition: opacity 500ms linear; | ||||
|     border-radius: 999em; | ||||
| } | ||||
| 
 | ||||
| .usertext { | ||||
|     display: block; | ||||
|     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 ******************/ | ||||
| 
 | ||||
| 
 | ||||
| .map-attribution img { | ||||
|     width: 1em; | ||||
|     height: 1em; | ||||
|     fill: black; | ||||
|     border-radius: 0; | ||||
|     display: inline; | ||||
| } | ||||
| 
 | ||||
| .leaflet-popup-content { | ||||
|     width: 45em !important; | ||||
| } | ||||
|  | @ -461,3 +399,7 @@ li::marker { | |||
|     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="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... | ||||
| </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
 | ||||
| 
 | ||||
| document.getElementById("decoration-desktop").remove(); | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ | |||
|         "loginWithOpenStreetMap": "Login with OpenStreetMap", | ||||
|         "welcomeBack": "You are logged in, welcome back!", | ||||
|         "loginToStart": "Login to answer this question", | ||||
|         "testing":"Testing - changes won't be saved", | ||||
|         "search": { | ||||
|             "search": "Search a location", | ||||
|             "searching": "Searching…", | ||||
|  |  | |||
							
								
								
									
										17
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										17
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,15 +1,6 @@ | |||
| import {Translation} from "./UI/i18n/Translation"; | ||||
| import Locale from "./UI/i18n/Locale"; | ||||
| import Combine from "./UI/Base/Combine"; | ||||
| import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler"; | ||||
| import LayoutConfig from "./Customizations/JSON/LayoutConfig"; | ||||
| import {UIEventSource} from "./Logic/UIEventSource"; | ||||
| 
 | ||||
| 
 | ||||
| new Combine(["Some language:",new Translation({en:"English",nl:"Nederlands",fr:"Françcais"})]).AttachTo("maindiv") | ||||
| 
 | ||||
| Locale.language.setData("nl") | ||||
| window.setTimeout(() => { | ||||
|     Locale.language.setData("en") | ||||
| }, 1000) | ||||
| 
 | ||||
| window.setTimeout(() => { | ||||
|     Locale.language.setData("fr") | ||||
| }, 5000) | ||||
| new GeoLocationHandler(new UIEventSource<{latlng: any; accuracy: number}>(undefined), undefined, new UIEventSource<LayoutConfig>(undefined)).AttachTo("maindiv") | ||||
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue