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