forked from MapComplete/MapComplete
		
	Refactoring fullscreenhandling
This commit is contained in:
		
							parent
							
								
									e1a4c75391
								
							
						
					
					
						commit
						00f610c589
					
				
					 23 changed files with 346 additions and 245 deletions
				
			
		|  | @ -12,7 +12,6 @@ import CenterMessageBox from "./UI/CenterMessageBox"; | |||
| import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; | ||||
| import UserBadge from "./UI/BigComponents/UserBadge"; | ||||
| import SearchAndGo from "./UI/BigComponents/SearchAndGo"; | ||||
| import FullScreenMessageBox from "./UI/FullScreenMessageBoxHandler"; | ||||
| import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler"; | ||||
| import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; | ||||
| import {Utils} from "./Utils"; | ||||
|  | @ -33,7 +32,6 @@ import FeatureSwitched from "./UI/Base/FeatureSwitched"; | |||
| import LayerConfig from "./Customizations/JSON/LayerConfig"; | ||||
| import ShowDataLayer from "./UI/ShowDataLayer"; | ||||
| import Hash from "./Logic/Web/Hash"; | ||||
| import HistoryHandling from "./Logic/Actors/HistoryHandling"; | ||||
| import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; | ||||
| 
 | ||||
| export class InitUiElements { | ||||
|  | @ -118,8 +116,6 @@ export class InitUiElements { | |||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         new HistoryHandling(Hash.hash, State.state.fullScreenMessage); | ||||
| 
 | ||||
|         InitUiElements.OnlyIf(State.state.featureSwitchUserbadge, () => { | ||||
|             new UserBadge().AttachTo('userbadge'); | ||||
|         }); | ||||
|  | @ -129,9 +125,6 @@ export class InitUiElements { | |||
|         }); | ||||
| 
 | ||||
| 
 | ||||
|         new FullScreenMessageBox().AttachTo("messagesboxmobile"); | ||||
| 
 | ||||
| 
 | ||||
|         InitUiElements.OnlyIf(State.state.featureSwitchWelcomeMessage, () => { | ||||
|             InitUiElements.InitWelcomeMessage() | ||||
|         }); | ||||
|  | @ -214,18 +207,18 @@ export class InitUiElements { | |||
|     private static InitWelcomeMessage() { | ||||
| 
 | ||||
|         const isOpened = new UIEventSource<boolean>(true); | ||||
|         const fullOptions = new FullWelcomePaneWithTabs(() => isOpened.setData(false)); | ||||
|         const fullOptions = new FullWelcomePaneWithTabs(() => { | ||||
|             console.log("Closing the welcome message...") | ||||
|             isOpened.setData(false); | ||||
|         }); | ||||
| 
 | ||||
|         // ?-Button on Desktop, opens panel with close-X.
 | ||||
|         const help = Svg.help_svg().SetClass("open-welcome-button block"); | ||||
|         const close = Svg.close_svg().SetClass("close-welcome-button"); | ||||
|         const checkbox = new CheckBox( | ||||
|             new Combine([ | ||||
|                 close, | ||||
|                 fullOptions | ||||
|                     .SetClass("welcomeMessage") | ||||
|                     .onClick(() => {/*Catch the click*/ | ||||
|                     })]), | ||||
|                     }), | ||||
|             help | ||||
|             , isOpened | ||||
|         ).AttachTo("messagesbox"); | ||||
|  | @ -252,15 +245,8 @@ export class InitUiElements { | |||
|             const layerControlPanel = new LayerControlPanel( | ||||
|                 () => State.state.layerControlIsOpened.setData(false)) | ||||
|                 .SetClass("block p-1 rounded-full"); | ||||
|             const closeButton = Svg.close_svg() | ||||
|                 .SetClass("layer-selection-toggle") | ||||
|                 .SetStyle("  background: var(--subtle-detail-color);") | ||||
|             const checkbox = new CheckBox( | ||||
|                 new Combine([ | ||||
|                     closeButton, | ||||
|                     layerControlPanel]) | ||||
|                     .SetClass("flex flex-row") | ||||
|                 , | ||||
|               const checkbox = new CheckBox( | ||||
|                     layerControlPanel, | ||||
|                 Svg.layers_svg().SetClass("layer-selection-toggle"), | ||||
|                 State.state.layerControlIsOpened | ||||
|             ).AttachTo("layer-selection"); | ||||
|  | @ -388,7 +374,6 @@ export class InitUiElements { | |||
|                 State.state.selectedElement, | ||||
|                 State.state.filteredLayers, | ||||
|                 State.state.leafletMap, | ||||
|                 State.state.fullScreenMessage, | ||||
|                 () => { | ||||
|                     return new SimpleAddUI( | ||||
|                         () => State.state.LastClickLocation.setData(undefined) | ||||
|  |  | |||
|  | @ -17,7 +17,6 @@ export default class StrayClickHandler { | |||
|         selectedElement: UIEventSource<string>, | ||||
|         filteredLayers: UIEventSource<{ readonly isDisplayed: UIEventSource<boolean> }[]>, | ||||
|         leafletMap: UIEventSource<L.Map>, | ||||
|         fullscreenMessage: UIEventSource<{content: UIElement, hashText: string}>, | ||||
|         uiToShow: (() => UIElement)) { | ||||
|         this._uiToShow = uiToShow; | ||||
|         const self = this; | ||||
|  |  | |||
|  | @ -3,31 +3,70 @@ import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; | |||
| import Translations from "../../UI/i18n/Translations"; | ||||
| import Locale from "../../UI/i18n/Locale"; | ||||
| import {UIElement} from "../../UI/UIElement"; | ||||
| import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer"; | ||||
| import {ElementStorage} from "../ElementStorage"; | ||||
| import Combine from "../../UI/Base/Combine"; | ||||
| 
 | ||||
| export default class TitleHandler{ | ||||
|     constructor(layoutToUse: UIEventSource<LayoutConfig>, fullScreenMessage: UIEventSource<{ content: UIElement, hashText: string, titleText?: UIElement }>) { | ||||
| class TitleElement extends UIElement { | ||||
|     private readonly _layoutToUse: UIEventSource<LayoutConfig>; | ||||
|     private readonly _selectedFeature: UIEventSource<any>; | ||||
|     private readonly _allElementsStorage: ElementStorage; | ||||
|      | ||||
|     constructor(layoutToUse: UIEventSource<LayoutConfig>, | ||||
|                 selectedFeature: UIEventSource<any>, | ||||
|                 allElementsStorage : ElementStorage) { | ||||
|         super(layoutToUse); | ||||
|         this._layoutToUse = layoutToUse; | ||||
|         this._selectedFeature = selectedFeature; | ||||
|         this._allElementsStorage = allElementsStorage; | ||||
|         this.ListenTo(Locale.language); | ||||
|         this.dumbMode = false; | ||||
|     } | ||||
|      | ||||
|     InnerRender(): string { | ||||
|          | ||||
|         const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ?? "MapComplete" | ||||
|         const feature = this._selectedFeature.data; | ||||
| 
 | ||||
|         if(feature === undefined){ | ||||
|             return defaultTitle; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         layoutToUse.map((layoutToUse) => { | ||||
|                 return Translations.WT(layoutToUse?.title)?.txt ?? "MapComplete" | ||||
|             }, [Locale.language] | ||||
|         ).addCallbackAndRun((title) => { | ||||
|             document.title = title | ||||
|         }); | ||||
|         const layout = this._layoutToUse.data; | ||||
|         const properties = this._selectedFeature.data.properties; | ||||
|          for (const layer of layout.layers) { | ||||
|              if(layer.title === undefined){ | ||||
|                  continue; | ||||
|              } | ||||
|             if (layer.overpassTags.matchesProperties(properties)) { | ||||
| 
 | ||||
|         fullScreenMessage.addCallbackAndRun(selected => { | ||||
|             const title = Translations.WT(layoutToUse.data?.title)?.txt ?? "MapComplete" | ||||
|             if(selected?.titleText?.data === undefined){ | ||||
|                 document.title = title | ||||
|             }else{ | ||||
|                 selected.titleText.Update(); | ||||
|                 var d = document.createElement('div'); | ||||
|                 d.innerHTML = selected.titleText.InnerRender(); | ||||
|                 const poi = (d.textContent || d.innerText) | ||||
|                 document.title = title + " | " + poi; | ||||
|                 const title = new TagRenderingAnswer( | ||||
|                     this._allElementsStorage.getEventSourceFor(feature), | ||||
|                     layer.title | ||||
|                 ) | ||||
|                 return new Combine([defaultTitle," | ", title]).Render(); | ||||
|             } | ||||
|         }) | ||||
|         } | ||||
|         return defaultTitle; | ||||
|     } | ||||
|      | ||||
| } | ||||
| 
 | ||||
| export default class TitleHandler { | ||||
|     constructor(layoutToUse: UIEventSource<LayoutConfig>, | ||||
|                 selectedFeature: UIEventSource<any>, | ||||
|                 allElementsStorage : ElementStorage) { | ||||
| 
 | ||||
|         new TitleElement(layoutToUse, selectedFeature, allElementsStorage) | ||||
|             .addCallbackAndRun(contents => { | ||||
| 
 | ||||
|                 const d = document.createElement('div'); | ||||
|                 d.innerHTML = contents; | ||||
|                 // We pass everything into a div to strip out images etc...
 | ||||
|                 document.title = (d.textContent || d.innerText); | ||||
| 
 | ||||
|             }); | ||||
|          | ||||
|     } | ||||
| } | ||||
|  | @ -74,7 +74,7 @@ export default class MetaTagging { | |||
|                     const tagsSource = State.state.allElements.getEventSourceFor(feature); | ||||
|                     tagsSource.ping(); | ||||
|                 } catch (e) { | ||||
|                     console.error(e) | ||||
|                     console.warn(e) | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import {Utils} from "../../Utils"; | |||
| 
 | ||||
| export class OsmPreferences { | ||||
| 
 | ||||
|     public preferences = new UIEventSource<any>({}); | ||||
|     public preferences = new UIEventSource<any>({}, "all-osm-preferences"); | ||||
|     public preferenceSources: any = {} | ||||
|     private auth: any; | ||||
|     private userDetails: UIEventSource<UserDetails>; | ||||
|  | @ -29,7 +29,7 @@ export class OsmPreferences { | |||
|             return this.longPreferences[prefix + key]; | ||||
|         } | ||||
| 
 | ||||
|         const source = new UIEventSource<string>(undefined); | ||||
|         const source = new UIEventSource<string>(undefined, "long-osm-preference:"+prefix+key); | ||||
|         this.longPreferences[prefix + key] = source; | ||||
| 
 | ||||
|         const allStartWith = prefix + key + "-combined"; | ||||
|  | @ -106,7 +106,7 @@ export class OsmPreferences { | |||
|         if (this.userDetails.data.loggedIn && this.preferences.data[key] === undefined) { | ||||
|             this.UpdatePreferences(); | ||||
|         } | ||||
|         const pref = new UIEventSource<string>(this.preferences.data[key]); | ||||
|         const pref = new UIEventSource<string>(this.preferences.data[key],"osm-preference:"+key); | ||||
|         pref.addCallback((v) => { | ||||
|             this.SetPreference(key, v); | ||||
|         }); | ||||
|  |  | |||
|  | @ -1,14 +1,64 @@ | |||
| export class UIEventSource<T>{ | ||||
| export class UIEventSource<T> { | ||||
| 
 | ||||
|     public data: T; | ||||
|     private readonly tag: string; | ||||
|     private _callbacks = []; | ||||
|      | ||||
|     private static allSources : UIEventSource<any>[] = UIEventSource.PrepPerf(); | ||||
|      | ||||
|     static PrepPerf(){ | ||||
|         // @ts-ignore
 | ||||
|         window.mcperf = () => { | ||||
|             console.log(UIEventSource.allSources.length, "uieventsources created"); | ||||
|             const copy = [...UIEventSource.allSources]; | ||||
|             copy.sort((a,b) => b._callbacks.length - a._callbacks.length); | ||||
|             console.log("Topten is:") | ||||
|             for (let i = 0; i < 10; i++) { | ||||
|                 console.log(copy[i].tag, copy[i]); | ||||
|             } | ||||
|         } | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     constructor(data: T) { | ||||
|     constructor(data: T, tag: string = "") { | ||||
|         this.tag = tag; | ||||
|         this.data = data; | ||||
|         UIEventSource.allSources.push(this); | ||||
|     } | ||||
| 
 | ||||
|     public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources: UIEventSource<any>[]): UIEventSource<X> { | ||||
|         const sink = new UIEventSource<X>(source.data?.data); | ||||
| 
 | ||||
|         source.addCallback((latestData) => { | ||||
|             sink.setData(latestData?.data); | ||||
|         }); | ||||
| 
 | ||||
|         for (const possibleSource of possibleSources) { | ||||
|             possibleSource?.addCallback(() => { | ||||
|                 sink.setData(source.data?.data); | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         return sink; | ||||
|     } | ||||
| 
 | ||||
|     public static Chronic(millis: number, asLong: () => boolean = undefined): UIEventSource<Date> { | ||||
|         const source = new UIEventSource<Date>(undefined); | ||||
| 
 | ||||
|         function run() { | ||||
|             source.setData(new Date()); | ||||
|             if (asLong === undefined || asLong()) { | ||||
|                 window.setTimeout(run, millis); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         run(); | ||||
|         return source; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public addCallback(callback: ((latestData: T) => void)): UIEventSource<T> { | ||||
|         if(callback === console.log){ | ||||
|         if (callback === console.log) { | ||||
|             // This ^^^ actually works!
 | ||||
|             throw "Don't add console.log directly as a callback - you'll won't be able to find it afterwards. Wrap it in a lambda instead." | ||||
|         } | ||||
|  | @ -36,25 +86,9 @@ export class UIEventSource<T>{ | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static flatten<X>(source: UIEventSource<UIEventSource<X>>, possibleSources: UIEventSource<any>[]): UIEventSource<X> { | ||||
|         const sink = new UIEventSource<X>(source.data?.data); | ||||
| 
 | ||||
|         source.addCallback((latestData) => { | ||||
|            sink.setData(latestData?.data); | ||||
|         }); | ||||
| 
 | ||||
|         for (const possibleSource of possibleSources) { | ||||
|             possibleSource?.addCallback(() => { | ||||
|                 sink.setData(source.data?.data); | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         return sink; | ||||
|     } | ||||
| 
 | ||||
|     public map<J>(f: ((T) => J), | ||||
|                   extraSources: UIEventSource<any>[] = [], | ||||
|                   g: ((J) => T) = undefined ): UIEventSource<J> { | ||||
|                   g: ((J) => T) = undefined): UIEventSource<J> { | ||||
|         const self = this; | ||||
| 
 | ||||
|         const newSource = new UIEventSource<J>( | ||||
|  | @ -70,7 +104,7 @@ export class UIEventSource<T>{ | |||
|             extraSource?.addCallback(update); | ||||
|         } | ||||
| 
 | ||||
|         if(g !== undefined) { | ||||
|         if (g !== undefined) { | ||||
|             newSource.addCallback((latest) => { | ||||
|                 self.setData(g(latest)); | ||||
|             }) | ||||
|  | @ -79,7 +113,6 @@ export class UIEventSource<T>{ | |||
|         return newSource; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public syncWith(otherSource: UIEventSource<T>, reverseOverride = false): UIEventSource<T> { | ||||
|         this.addCallback((latest) => otherSource.setData(latest)); | ||||
|         const self = this; | ||||
|  | @ -94,7 +127,7 @@ export class UIEventSource<T>{ | |||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public stabilized(millisToStabilize) : UIEventSource<T>{ | ||||
|     public stabilized(millisToStabilize): UIEventSource<T> { | ||||
| 
 | ||||
|         const newSource = new UIEventSource<T>(this.data); | ||||
| 
 | ||||
|  | @ -103,7 +136,7 @@ export class UIEventSource<T>{ | |||
|             currentCallback++; | ||||
|             const thisCallback = currentCallback; | ||||
|             window.setTimeout(() => { | ||||
|                 if(thisCallback === currentCallback){ | ||||
|                 if (thisCallback === currentCallback) { | ||||
|                     newSource.setData(latestData); | ||||
|                 } | ||||
|             }, millisToStabilize) | ||||
|  | @ -112,19 +145,4 @@ export class UIEventSource<T>{ | |||
|         return newSource; | ||||
|     } | ||||
| 
 | ||||
|     public static Chronic(millis: number, asLong: () => boolean = undefined): UIEventSource<Date> { | ||||
|         const source = new UIEventSource<Date>(undefined); | ||||
| 
 | ||||
|         function run() { | ||||
|             source.setData(new Date()); | ||||
|             if (asLong === undefined || asLong()) { | ||||
|                 window.setTimeout(run, millis); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         run(); | ||||
|         return source; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -5,7 +5,7 @@ export class LocalStorageSource { | |||
|     static Get(key: string, defaultValue: string = undefined): UIEventSource<string> { | ||||
|         try { | ||||
|             const saved = localStorage.getItem(key); | ||||
|             const source = new UIEventSource<string>(saved ?? defaultValue); | ||||
|             const source = new UIEventSource<string>(saved ?? defaultValue, "localstorage:"+key); | ||||
| 
 | ||||
|             source.addCallback((data) => { | ||||
|                 localStorage.setItem(key, data); | ||||
|  |  | |||
							
								
								
									
										6
									
								
								State.ts
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								State.ts
									
										
									
									
									
								
							|  | @ -72,10 +72,6 @@ export default class State { | |||
|      *  The message that should be shown at the center of the screen | ||||
|      */ | ||||
|     public readonly centerMessage = new UIEventSource<string>(""); | ||||
|     /** | ||||
|      This message is shown full screen on mobile devices | ||||
|      */ | ||||
|     public readonly fullScreenMessage = new UIEventSource<{ content: UIElement, hashText: string, titleText?: UIElement }>(undefined) | ||||
| 
 | ||||
|     /** | ||||
|      The latest element that was selected - used to generate the right UI at the right place | ||||
|  | @ -244,7 +240,7 @@ export default class State { | |||
|             } | ||||
|         }).ping() | ||||
| 
 | ||||
|         new TitleHandler(this.layoutToUse, this.fullScreenMessage); | ||||
|         new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); | ||||
|          | ||||
| 
 | ||||
|         this.allElements = new ElementStorage(); | ||||
|  |  | |||
|  | @ -1,22 +1,25 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| 
 | ||||
| export default class LazyElement extends UIElement { | ||||
| export default class LazyElement<T extends UIElement> extends UIElement { | ||||
| 
 | ||||
| 
 | ||||
|     private _content: UIElement = undefined; | ||||
|     public Activate: (onElement?: (element: T) => void) => void; | ||||
|     private _content: T = undefined; | ||||
|     private readonly _loadingContent: string; | ||||
| 
 | ||||
|     public Activate: () => void; | ||||
|     private _loadingContent: string; | ||||
| 
 | ||||
|     constructor(content: (() => UIElement), loadingContent = "Rendering...") { | ||||
|     constructor(content: (() => T), loadingContent = "Rendering...") { | ||||
|         super(); | ||||
|         this._loadingContent = loadingContent; | ||||
|         this.dumbMode = false; | ||||
|         const self = this; | ||||
|         this.Activate = () => { | ||||
|         this.Activate = (onElement?: (element: T) => void) => { | ||||
|             console.log("ACTIVATED") | ||||
|             if (this._content === undefined) { | ||||
|                 self._content = content(); | ||||
|             } | ||||
|             if (onElement) { | ||||
|                 onElement(self._content) | ||||
|             } | ||||
|             self.Update(); | ||||
|         } | ||||
|     } | ||||
|  | @ -27,5 +30,7 @@ export default class LazyElement extends UIElement { | |||
|         } | ||||
|         return this._content.InnerRender(); | ||||
|     } | ||||
|      | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,6 +1,5 @@ | |||
| import {UIElement} from "../UIElement"; | ||||
| import Svg from "../../Svg"; | ||||
| import State from "../../State"; | ||||
| import Combine from "./Combine"; | ||||
| import Ornament from "./Ornament"; | ||||
| 
 | ||||
|  | @ -8,63 +7,150 @@ import Ornament from "./Ornament"; | |||
|  * Wraps some contents into a panel that scrolls the content _under_ the title | ||||
|  */ | ||||
| export default class ScrollableFullScreen extends UIElement { | ||||
|     private static _isInited = false; | ||||
|     private title: UIElement; | ||||
|     private content: UIElement; | ||||
|     private _component: UIElement; | ||||
|     private elementsToRestore: Set<HTMLElement> = new Set<HTMLElement>(); | ||||
| 
 | ||||
|     constructor(title: UIElement, content: UIElement, onClose: (() => void)) { | ||||
|         super(); | ||||
|         this.content = content; | ||||
|         this.title = title; | ||||
|         if (!ScrollableFullScreen._isInited) { | ||||
|             ScrollableFullScreen._isInited = ScrollableFullScreen.PreparePatchesForFullscreen(); | ||||
|         } | ||||
|         if (onClose === undefined) { | ||||
|             console.error("ScrollableFullScreen initialized without onClose!") | ||||
|         } | ||||
|         this.dumbMode = false; | ||||
|         const returnToTheMap = Svg.back_svg().onClick(() => { | ||||
|             console.log("Clicked back!"); | ||||
|             this.RestoreLeaflet(); | ||||
|             if (onClose() !== undefined) { | ||||
|                 console.error("WARNING: onClose is not defined") | ||||
|                 onClose(); | ||||
|             } | ||||
|         }).SetClass("block sm:hidden mb-2 bg-blue-50 rounded-full w-12 h-12 p-1.5") | ||||
|         const returnToTheMap = | ||||
|             new Combine([ | ||||
|                 Svg.back_svg().SetClass("block sm:hidden"), | ||||
|                 Svg.close_svg().SetClass("hidden sm:block") | ||||
|             ]) | ||||
|                 .onClick(() => { | ||||
|                     console.log("Clicked back!"); | ||||
|                     ScrollableFullScreen.RestoreLeaflet(); | ||||
|                     if (onClose !== undefined) { | ||||
|                         onClose(); | ||||
|                     } else { | ||||
|                         console.error("WARNING: onClose is not defined") | ||||
|                     } | ||||
|                 }).SetClass("mb-2 bg-blue-50 rounded-full w-12 h-12 p-1.5") | ||||
| 
 | ||||
|         title.SetClass("block w-full") | ||||
|         title.SetClass("block w-full text-2xl font-bold p-2 pl-4") | ||||
|         const ornament = new Combine([new Ornament().SetStyle("height:5em;")]) | ||||
|             .SetClass("block sm:hidden h-5") | ||||
| 
 | ||||
| 
 | ||||
|         this._component = | ||||
|             new Combine([ | ||||
|             new Combine([ | ||||
|             new Combine([returnToTheMap, title]) | ||||
|                 .AddClass("border-b-2 border-black shadow sm:shadow-none bg-white p-2 pb-0 sm:p-0 flex overflow-x-hidden flex-shrink-0 max-h-20vh"), | ||||
|             new Combine(["<span>", content, "</span>", ornament]) | ||||
|                 .SetClass("block p-2 sm:pt-4 w-full h-screen landscape:h-screen  sm:h-full sm:w-full overflow-y-auto overflow-x-hidden"), | ||||
|             // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide
 | ||||
|         ]).SetClass("block flex flex-col  relative bg-white") | ||||
|         ]).SetClass("fixed top-0 left-0 right-0 h-screen w-screen sm:max-h-65vh sm:w-auto"); | ||||
|                 new Combine([ | ||||
|                     new Combine([returnToTheMap, title]) | ||||
|                         .SetClass("border-b-2 border-black shadow sm:shadow-none bg-white p-2 pb-0 sm:p-0 flex overflow-x-hidden flex-shrink-0 max-h-20vh"), | ||||
|                     new Combine(["<span>", content, "</span>", ornament]) | ||||
|                         .SetClass("block p-2 sm:pt-4 w-full h-screen landscape:h-screen  sm:h-full sm:w-full overflow-y-auto overflow-x-hidden"), | ||||
|                     // We add an ornament which takes around 5em. This is in order to make sure the Web UI doesn't hide
 | ||||
|                 ]).SetClass("block flex flex-col  relative bg-white") | ||||
|             ]).SetClass("fixed top-0 left-0 right-0 h-screen w-screen sm:max-h-65vh sm:w-auto sm:relative"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static HideClutter(htmlElement: HTMLElement) { | ||||
|         const whiteList = new Set<Element>(); | ||||
|         do { | ||||
|             if(htmlElement === null){ | ||||
|                 break; | ||||
|             } | ||||
|             if (htmlElement.classList.contains("clutter")) { | ||||
|                 // Don't hide the parent element
 | ||||
|                 whiteList.add(htmlElement) | ||||
|             } | ||||
|             htmlElement = htmlElement.parentElement; | ||||
|         } while (htmlElement != null) | ||||
| 
 | ||||
|         const clutter = document.getElementsByClassName("clutter"); | ||||
|         for (let i = 0; i < clutter.length; ++i) { | ||||
|             if (whiteList.has(clutter[i])) { | ||||
|                 continue; | ||||
|             } | ||||
|             const classlist = clutter[i].classList; | ||||
|             if (classlist.contains("clutter-hidden")) { | ||||
|                 continue; | ||||
|             } | ||||
|             classlist.add("clutter-hidden"); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds the 'clutter' class (which merely acts as a tag) onto some elements, e.g. the leaflet attributions | ||||
|      * @constructor | ||||
|      */ | ||||
|     private static PreparePatchesForFullscreen(): boolean { | ||||
|         const toHide = document.getElementsByClassName("leaflet-control-container"); | ||||
|         for (let i = 0; i < toHide.length; ++i) { | ||||
|             toHide[i].classList.add("clutter"); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private static PatchLeaflet(htmlElement) { | ||||
|         if(htmlElement === null){ | ||||
|             return; | ||||
|         } | ||||
|         do { | ||||
|             // A leaflet workaround: in order for fullscreen to work, we need to get the parent element which does a transform3d and remove/read the transform
 | ||||
|             if (htmlElement.style.transform !== "") { | ||||
|                 if (!htmlElement.classList.contains("no-transform")) { | ||||
|                     htmlElement.classList.add("no-transform"); | ||||
|                     htmlElement.classList.add("scrollable-fullscreen-no-transform") | ||||
|                 } | ||||
|             } | ||||
|             htmlElement = htmlElement.parentElement; | ||||
|         } while (htmlElement != null) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private static RestoreLeaflet() { | ||||
|         console.log("Restoring") | ||||
|         const noTransf = document.getElementsByClassName("scrollable-fullscreen-no-transform"); | ||||
|         for (let i = 0; i < noTransf.length; ++i) { | ||||
|             noTransf[i].classList.remove("no-transform"); | ||||
|             noTransf[i].classList.remove("scrollable-fullscreen-no-transform"); | ||||
|         } | ||||
|         let clutter = document.getElementsByClassName("clutter-hidden"); | ||||
| 
 | ||||
|         do { | ||||
|             for (let i = 0; i < clutter.length; ++i) { | ||||
|                 clutter[i].classList.remove("clutter-hidden"); | ||||
|             } | ||||
|             clutter = document.getElementsByClassName("clutter-hidden"); | ||||
|         } while (clutter.length > 0) | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return this._component.Render(); | ||||
|     } | ||||
|      | ||||
|     protected InnerUpdate(htmlElement: HTMLElement) { | ||||
| 
 | ||||
|         do { | ||||
|             // A leaflet workaround: in order for fullscreen to work, we need to get the parent element which does a transform3d and remove/read the transform
 | ||||
|             if (htmlElement.style.transform !== "") { | ||||
|                 this.elementsToRestore.add(htmlElement); | ||||
|                 htmlElement.classList.add("no-transform") | ||||
|             } | ||||
|             htmlElement = htmlElement.parentElement; | ||||
|         } while (htmlElement != null) | ||||
| 
 | ||||
|         super.InnerUpdate(htmlElement); | ||||
|     Update() { | ||||
|         console.log("Updating  the scrollableFullScreen") | ||||
|         super.Update(); | ||||
|         this._component.Update(); | ||||
|     } | ||||
| 
 | ||||
|     private RestoreLeaflet() { | ||||
|         this.elementsToRestore.forEach( | ||||
|             el => el.classList.remove("no-transform") | ||||
|         ); | ||||
|     public PrepFullscreen(htmlElement = undefined) { | ||||
|         htmlElement = htmlElement ?? document.getElementById(this.id); | ||||
|         ScrollableFullScreen.PatchLeaflet(htmlElement); | ||||
|         ScrollableFullScreen.HideClutter(htmlElement); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     protected InnerUpdate(htmlElement: HTMLElement) { | ||||
|         this.PrepFullscreen(htmlElement) | ||||
|         super.InnerUpdate(htmlElement); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -24,9 +24,9 @@ export class SubtleButton extends UIElement{ | |||
|         } else { | ||||
|             img = imageUrl; | ||||
|         } | ||||
|         img.AddClass("block flex items-center justify-center h-11 w-11 flex-shrink0") | ||||
|         img.SetClass("block flex items-center justify-center h-11 w-11 flex-shrink0") | ||||
|         this.image = new Combine([img]) | ||||
|             .AddClass("flex-shrink-0"); | ||||
|             .SetClass("flex-shrink-0"); | ||||
|          | ||||
|         | ||||
|     } | ||||
|  | @ -53,7 +53,7 @@ export class SubtleButton extends UIElement{ | |||
|         return new Combine([ | ||||
|             this.image, | ||||
|             this.message, | ||||
|         ]).AddClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200") | ||||
|         ]).SetClass("block flex p-3 my-2 bg-blue-100 rounded-lg hover:shadow-xl hover:bg-blue-200") | ||||
|             .Render(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,22 +7,22 @@ export default class IndexText extends Combine { | |||
|     constructor() { | ||||
|         super([ | ||||
|             new FixedUiElement(`<img class="w-12 h-12 sm:h-24 sm:w-24" src="./assets/svg/logo.svg" alt="MapComplete Logo">`) | ||||
|                 .AddClass("flex-none m-3"), | ||||
|                 .SetClass("flex-none m-3"), | ||||
| 
 | ||||
|             new Combine([ | ||||
|                 Translations.t.index.title | ||||
|                     .AddClass("text-2xl tracking-tight font-extrabold text-gray-900 sm:text-5xl md:text-6xl block text-gray-800 xl:inline"), | ||||
|                     .SetClass("text-2xl tracking-tight font-extrabold text-gray-900 sm:text-5xl md:text-6xl block text-gray-800 xl:inline"), | ||||
| 
 | ||||
|                 Translations.t.index.intro.AddClass( | ||||
|                 Translations.t.index.intro.SetClass( | ||||
|                     "mt-3 text-base font-semibold text-gray-500 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0"), | ||||
| 
 | ||||
|                 Translations.t.index.pickTheme.AddClass("mt-3 text-base text-green-600 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0") | ||||
|                 Translations.t.index.pickTheme.SetClass("mt-3 text-base text-green-600 sm:mt-5 sm:text-lg sm:max-w-xl sm:mx-auto md:mt-5 md:text-xl lg:mx-0") | ||||
| 
 | ||||
|             ]).AddClass("flex flex-col sm:text-center lg:text-left m-1 mt-2 md:m-2 md:mt-4") | ||||
|             ]).SetClass("flex flex-col sm:text-center lg:text-left m-1 mt-2 md:m-2 md:mt-4") | ||||
|         ]); | ||||
| 
 | ||||
| 
 | ||||
|         this.AddClass("flex flex-row"); | ||||
|         this.SetClass("flex flex-row"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -22,7 +22,7 @@ export default class SearchAndGo extends UIElement { | |||
|     ); | ||||
| 
 | ||||
|     private _foundEntries = new UIEventSource([]); | ||||
|     private _goButton = Svg.search_ui().AddClass('w-8 h-8 full-rounded border-black float-right'); | ||||
|     private _goButton = Svg.search_ui().SetClass('w-8 h-8 full-rounded border-black float-right'); | ||||
| 
 | ||||
|     constructor() { | ||||
|         super(undefined); | ||||
|  |  | |||
|  | @ -1,32 +0,0 @@ | |||
| import {UIElement} from "./UIElement"; | ||||
| import State from "../State"; | ||||
| import Combine from "./Base/Combine"; | ||||
| 
 | ||||
| /** | ||||
|  * Handles the full screen popup on mobile | ||||
|  */ | ||||
| export default class FullScreenMessageBox extends UIElement { | ||||
| 
 | ||||
|     private _content: UIElement; | ||||
| 
 | ||||
|     constructor() { | ||||
|         super(State.state.fullScreenMessage); | ||||
|         this.HideOnEmpty(true); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         if (State.state.fullScreenMessage.data === undefined) { | ||||
|             return ""; | ||||
|         } | ||||
|         this._content = State.state.fullScreenMessage.data.content; | ||||
|         return new Combine([this._content]) | ||||
|             .SetClass("block max-h-screen h-screen overflow-x-hidden overflow-y-auto bg-white p-0").Render(); | ||||
|     } | ||||
| 
 | ||||
|     protected InnerUpdate(htmlElement: HTMLElement) { | ||||
|         super.InnerUpdate(htmlElement); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -9,29 +9,41 @@ import State from "../../State"; | |||
| import TagRenderingConfig from "../../Customizations/JSON/TagRenderingConfig"; | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||
| 
 | ||||
| export default class FeatureInfoBox extends UIElement { | ||||
|     private _component: UIElement; | ||||
| 
 | ||||
|     public title: UIElement ; | ||||
| export default class FeatureInfoBox extends ScrollableFullScreen { | ||||
| 
 | ||||
|     constructor( | ||||
|         tags: UIEventSource<any>, | ||||
|         layerConfig: LayerConfig, | ||||
|         onClose: () => {} | ||||
|         onClose: () => void | ||||
|     ) { | ||||
|         super(); | ||||
|         super( | ||||
|             FeatureInfoBox.GenerateTitleBar(tags, layerConfig), | ||||
|             FeatureInfoBox.GenerateContent(tags, layerConfig), | ||||
|             onClose | ||||
|         ); | ||||
|         if (layerConfig === undefined) { | ||||
|             throw "Undefined layerconfig" | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
|      | ||||
|     private static GenerateTitleBar( tags: UIEventSource<any>, | ||||
|                                      layerConfig: LayerConfig): UIElement{ | ||||
|         const title = new TagRenderingAnswer(tags, layerConfig.title ?? new TagRenderingConfig("POI", undefined)) | ||||
|             .AddClass("text-2xl break-words font-bold p-2"); | ||||
|         this.title = title; | ||||
|             .SetClass("text-2xl break-words font-bold p-2"); | ||||
|         const titleIcons = new Combine( | ||||
|             layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon) | ||||
|                 .AddClass("block w-8 h-8 align-baseline box-content p-0.5"))) | ||||
|             .AddClass("flex flex-row flex-wrap pt-1 items-center mr-2"); | ||||
|                 .SetClass("block w-8 h-8 align-baseline box-content p-0.5"))) | ||||
|             .SetClass("flex flex-row flex-wrap pt-1 items-center mr-2"); | ||||
| 
 | ||||
|       return new Combine([ | ||||
|             new Combine([title, titleIcons]).SetClass("flex flex-grow justify-between") | ||||
|         ]) | ||||
|     } | ||||
|      | ||||
|     private static GenerateContent(tags: UIEventSource<any>, | ||||
|                                    layerConfig: LayerConfig): UIElement{ | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         let questionBox: UIElement = undefined; | ||||
|         if (State.state.featureSwitchUserbadge.data) { | ||||
|  | @ -53,20 +65,12 @@ export default class FeatureInfoBox extends UIElement { | |||
|         } | ||||
|         const tail = new Combine([]).SetClass("only-on-mobile"); | ||||
| 
 | ||||
|         const content = new Combine([ | ||||
|         return new Combine([ | ||||
|                 ...renderings, | ||||
|                 tail.SetClass("featureinfobox-tail") | ||||
|             ] | ||||
|         ) | ||||
|         const titleBar = new Combine([ | ||||
|             new Combine([title, titleIcons]).SetClass("flex flex-grow justify-between") | ||||
|         ]) | ||||
| 
 | ||||
|         this._component = new ScrollableFullScreen(titleBar, content, onClose) | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|         return this._component.Render(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ export default class TagRenderingAnswer extends UIElement { | |||
|         if (configuration === undefined) { | ||||
|             throw "Trying to generate a tagRenderingAnswer without configuration..." | ||||
|         } | ||||
|         this.AddClass("flex items-center flex-row text-lg") | ||||
|         this.SetClass("flex items-center flex-row text-lg") | ||||
|     } | ||||
| 
 | ||||
|     InnerRender(): string { | ||||
|  |  | |||
|  | @ -88,7 +88,7 @@ export default class TagRenderingQuestion extends UIElement { | |||
|                     return tags.asHumanString(true, true); | ||||
|                 } | ||||
|             ) | ||||
|         ).AddClass("block") | ||||
|         ).SetClass("block") | ||||
|     } | ||||
| 
 | ||||
|     private GenerateInputElement(): InputElement<TagsFilter> { | ||||
|  |  | |||
|  | @ -44,10 +44,10 @@ export default class ShowDataLayer { | |||
| 
 | ||||
|             let geoLayer = self.CreateGeojsonLayer(feats) | ||||
|             if (layoutToUse.clustering.minNeededElements <= features.data.length) { | ||||
|                     const cl = window["L"]; // This is a dirty workaround, the clustering plugin binds to the L of the window, not of the namespace or something
 | ||||
|                     const cluster = cl.markerClusterGroup({ disableClusteringAtZoom: layoutToUse.clustering.maxZoom }); | ||||
|                     cluster.addLayer(geoLayer); | ||||
|                     geoLayer = cluster; | ||||
|                 const cl = window["L"]; // This is a dirty workaround, the clustering plugin binds to the L of the window, not of the namespace or something
 | ||||
|                 const cluster = cl.markerClusterGroup({disableClusteringAtZoom: layoutToUse.clustering.maxZoom}); | ||||
|                 cluster.addLayer(geoLayer); | ||||
|                 geoLayer = cluster; | ||||
|             } | ||||
| 
 | ||||
|             if (oldGeoLayer) { | ||||
|  | @ -60,22 +60,22 @@ export default class ShowDataLayer { | |||
|         features.addCallbackAndRun(() => update()); | ||||
|         leafletMap.addCallback(() => update()); | ||||
|         State.state.selectedElement.addCallback(feature => { | ||||
|             if(feature === undefined){ | ||||
|             if (feature === undefined) { | ||||
|                 return; | ||||
|             } | ||||
|             const id = feature.properties.id+feature.geometry.type+feature._matching_layer_id; | ||||
|             const id = feature.properties.id + feature.geometry.type + feature._matching_layer_id; | ||||
|             const action = self._onSelectedTrigger[id]; | ||||
|             if(action){ | ||||
|             if (action) { | ||||
|                 action(); | ||||
|             } | ||||
|         }); | ||||
|         Hash.hash.addCallbackAndRun(id => { | ||||
|             // This is a bit of an edge case: if the hash becomes an id to search, we have to show the corresponding popup
 | ||||
|             if(State.state.selectedElement !== undefined){ | ||||
|             if (State.state.selectedElement !== undefined) { | ||||
|                 return; // Something is already selected, we don't have to apply this fix
 | ||||
|             } | ||||
|             const action = self._onSelectedTrigger[id]; | ||||
|             if(action){ | ||||
|             if (action) { | ||||
|                 action(); | ||||
|             } | ||||
|         }) | ||||
|  | @ -126,41 +126,48 @@ export default class ShowDataLayer { | |||
| 
 | ||||
| 
 | ||||
|         const tags = State.state.allElements.getEventSourceFor(feature); | ||||
|         const uiElement: LazyElement = new LazyElement(() => new FeatureInfoBox(tags, layer, () => popup.closePopup()), | ||||
|         const uiElement: LazyElement<FeatureInfoBox> = new LazyElement(() => new FeatureInfoBox(tags, layer, () => { | ||||
|                 console.log("Closing the popup!") | ||||
|                 State.state.selectedElement.setData(undefined); | ||||
|                 popup.remove(); | ||||
| 
 | ||||
|             }), | ||||
|             "<div style='height: 90vh'>Rendering</div>"); | ||||
|         popup.setContent(uiElement.Render()); | ||||
|         popup.on('remove', () => { | ||||
|             if(!popup.isOpen()){ | ||||
|             if (!popup.isOpen()) { | ||||
|                 return; | ||||
|             } | ||||
|            State.state.selectedElement.setData(undefined);  | ||||
|             State.state.selectedElement.setData(undefined); | ||||
|         }); | ||||
|         leafletLayer.bindPopup(popup); | ||||
|         // We first render the UIelement (which'll still need an update later on...)
 | ||||
|         // But at least it'll be visible already
 | ||||
| 
 | ||||
| 
 | ||||
|         leafletLayer.on("click", (e) => { | ||||
|         leafletLayer.on("click", () => { | ||||
|             // We set the element as selected...
 | ||||
|             uiElement.Activate(); | ||||
|              | ||||
|             uiElement.Activate(e => e.PrepFullscreen()); | ||||
|             State.state.selectedElement.setData(feature); | ||||
|         }); | ||||
|          | ||||
|         const id = feature.properties.id+feature.geometry.type+feature._matching_layer_id; | ||||
| 
 | ||||
|         const id = feature.properties.id + feature.geometry.type + feature._matching_layer_id; | ||||
|         this._onSelectedTrigger[id] | ||||
|          = () => { | ||||
|             if(popup.isOpen()){ | ||||
|             = () => { | ||||
|             if (popup.isOpen()) { | ||||
|                 return; | ||||
|             } | ||||
|             leafletLayer.openPopup(); | ||||
|             uiElement.Activate(); | ||||
|             uiElement.Activate(e => e.PrepFullscreen()); | ||||
|             State.state.selectedElement.setData(feature); | ||||
|         } | ||||
|         this._onSelectedTrigger[feature.properties.id.replace("/","_")] = this._onSelectedTrigger[id]; | ||||
|         if (feature.properties.id.replace(/\//g, "_") === Hash.hash.data) { | ||||
|         this._onSelectedTrigger[feature.properties.id.replace("/", "_")] = this._onSelectedTrigger[id]; | ||||
|         if (feature.properties.id.replace(/\//g, "_") === Hash.hash.data && State.state.selectedElement.data === undefined) { | ||||
|             // This element is in the URL, so this is a share link
 | ||||
|             // We open the relevant popup straight away
 | ||||
|             uiElement.Activate(); | ||||
|             console.log("Opening the popup due to sharelink") | ||||
|             uiElement.Activate(e => e.PrepFullscreen()); | ||||
|             popup.setContent(uiElement.Render()); | ||||
| 
 | ||||
|             const center = GeoOperations.centerpoint(feature).geometry.coordinates; | ||||
|  |  | |||
|  | @ -149,16 +149,12 @@ export abstract class UIElement extends UIEventSource<string> { | |||
|         return this.InnerRender() === ""; | ||||
|     } | ||||
| 
 | ||||
|     public SetClass(clss: string): UIElement { | ||||
|         return this.AddClass(clss); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Adds all the relevant classes, space seperated | ||||
|      * @param clss | ||||
|      * @constructor | ||||
|      */ | ||||
|     public AddClass(clss: string) { | ||||
|     public SetClass(clss: string) { | ||||
|         this.dumbMode = false; | ||||
|         const all = clss.split(" "); | ||||
|         let recordedChange = false; | ||||
|  |  | |||
|  | @ -78,11 +78,4 @@ | |||
|     max-width: 100%; | ||||
|     max-height: 30vh; | ||||
|     border-radius: 1em; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .hidden { | ||||
|     /* This is used by the slideshow, to hide non-active slides*/ | ||||
|     display: none !important; | ||||
| 
 | ||||
| } | ||||
| } | ||||
							
								
								
									
										16
									
								
								index.css
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								index.css
									
										
									
									
									
								
							|  | @ -32,15 +32,21 @@ | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| @media only screen and (max-width: 640px), only screen and (max-height: 640px) { | ||||
| @media only screen and (max-width: 640px) { | ||||
|     .no-transform { | ||||
|         /*This is a workaround to let popup contents escape the popup on mobile*/ | ||||
|         /*This is a workaround to let popup contents escape the popup on mobile - see scrollableFullScreen.ts*/ | ||||
|         transform: none !important; | ||||
|     } | ||||
| 
 | ||||
|     .clutter-hidden { | ||||
|         /*This is a workaround to let popup contents escape the popup on mobile - see scrollableFullScreen.ts*/ | ||||
|         visibility: hidden !important; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| :root { | ||||
|     --subtle-detail-color: #e5f5ff; | ||||
|     --subtle-detail-color-contrast: black; | ||||
|  | @ -57,6 +63,11 @@ | |||
|     --return-to-the-map-height: 2em; | ||||
| } | ||||
| 
 | ||||
| .hide-when-fullscreen-is-shown { | ||||
|     /*Clutter is actually a class indicating that the element should be hidden when a scrollableFullScreen is opened | ||||
|     It doesn't actually define any rules*/ | ||||
| } | ||||
| 
 | ||||
| html, body { | ||||
|     height: 100%; | ||||
|     min-height: 100vh; | ||||
|  | @ -333,7 +344,6 @@ a { | |||
| 
 | ||||
| .welcomeMessage { | ||||
|     display: block; | ||||
|     margin-left: 4em; | ||||
|     max-width: calc(100vw - 5em); | ||||
|     width: 40em; | ||||
|     max-height: calc(100vh - 15em); | ||||
|  |  | |||
							
								
								
									
										10
									
								
								index.html
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								index.html
									
										
									
									
									
								
							|  | @ -44,11 +44,7 @@ | |||
|     <!-- DECORATION 0 END --> | ||||
| </div> | ||||
| 
 | ||||
| <div class="only-on-mobile"> | ||||
|     <div id="messagesboxmobile"></div> | ||||
| </div> | ||||
| 
 | ||||
| <div id="topleft-tools" class="z-index-above-map"> | ||||
| <div id="topleft-tools" class="z-index-above-map clutter"> | ||||
|     <div id="userbadge-and-search" class="p-3"> | ||||
|         <div id="userbadge" class="shadow rounded-3xl overflow-hidden"></div> | ||||
|         <div id="searchbox" class="shadow rounded-3xl overflow-hidden"></div> | ||||
|  | @ -57,13 +53,13 @@ | |||
|     <div id="help-button-mobile"></div> | ||||
| </div> | ||||
| 
 | ||||
| <div id="layer-selection" class="absolute bottom-3 left-3 rounded-3xl overflow-hidden"></div> | ||||
| <div id="layer-selection" class="absolute bottom-3 left-3 rounded-3xl overflow-hidden clutter"></div> | ||||
| 
 | ||||
| <div id="centermessage" class="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"> | ||||
|     Loading MapComplete, hang on... | ||||
| </div> | ||||
| 
 | ||||
| <div id="geolocate-button"></div> | ||||
| <div id="geolocate-button" class="clutter"></div> | ||||
| <div id="leafletDiv"></div> | ||||
| 
 | ||||
| <script src="./index.ts"></script> | ||||
|  |  | |||
							
								
								
									
										3
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -119,11 +119,10 @@ if (layoutFromBase64.startsWith("wiki:")) { | |||
| } else { | ||||
|     // We fall through: no theme loaded: just show a few buttons
 | ||||
|     State.state = new State(undefined); | ||||
|     document.getElementById("messagesboxmobile").remove(); | ||||
|     new Combine([new MoreScreen(true) | ||||
|         .SetStyle("pointer-events: all;"), | ||||
|         Translations.t.general.openStreetMapIntro | ||||
|     ]).AddClass("block m-5 lg:w-3/4 lg:ml-40") | ||||
|     ]).SetClass("block m-5 lg:w-3/4 lg:ml-40") | ||||
|         .AttachTo("topleft-tools"); | ||||
| } | ||||
| window.addEventListener('contextmenu', function (e) { // Not compatible with IE < 9
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue