forked from MapComplete/MapComplete
		
	A11y: reset zoom (chromium-browsers only) when getting back to the map or when opening a picture
This commit is contained in:
		
							parent
							
								
									41e7108b2e
								
							
						
					
					
						commit
						9655f8a092
					
				
					 10 changed files with 145 additions and 21 deletions
				
			
		|  | @ -891,6 +891,11 @@ video { | |||
|   margin-right: 3rem; | ||||
| } | ||||
| 
 | ||||
| .mx-4 { | ||||
|   margin-left: 1rem; | ||||
|   margin-right: 1rem; | ||||
| } | ||||
| 
 | ||||
| .mb-4 { | ||||
|   margin-bottom: 1rem; | ||||
| } | ||||
|  |  | |||
|  | @ -66,7 +66,7 @@ | |||
|     width: 3rem; | ||||
|     max-height: 3rem; | ||||
|     margin-right: 1rem; | ||||
|     margin-left: 1rem; | ||||
|     margin-left: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| .mapping-icon-large { | ||||
|  | @ -76,7 +76,7 @@ | |||
|     margin-top: 0.5rem; | ||||
|     margin-bottom: 0.5rem; | ||||
|     margin-right: 1.5rem; | ||||
|     margin-left: 1.5rem; | ||||
|     margin-left: 0.5rem; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -50,6 +50,11 @@ export default class ThemeViewStateHashActor { | |||
|             if (!!hash) { | ||||
|                 // There is still a hash
 | ||||
|                 // We _only_ have to (at most) close the overlays in this case
 | ||||
|                 if (state.previewedImage.data) { | ||||
|                     state.previewedImage.setData(undefined) | ||||
|                     return | ||||
|                 } | ||||
| 
 | ||||
|                 const parts = hash.split(";") | ||||
|                 if (parts.indexOf("background") < 0) { | ||||
|                     state.guistate.backgroundLayerSelectionIsOpened.setData(false) | ||||
|  | @ -176,6 +181,10 @@ export default class ThemeViewStateHashActor { | |||
| 
 | ||||
|     private back() { | ||||
|         const state = this._state | ||||
|         if (state.previewedImage.data) { | ||||
|             state.previewedImage.setData(undefined) | ||||
|             return | ||||
|         } | ||||
|         // history.pushState(null, null, window.location.pathname);
 | ||||
|         if (state.selectedElement.data) { | ||||
|             state.selectedElement.setData(undefined) | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ import { UIEventSource } from "../Logic/UIEventSource" | |||
| import UserRelatedState from "../Logic/State/UserRelatedState" | ||||
| import { Utils } from "../Utils" | ||||
| import { LocalStorageSource } from "../Logic/Web/LocalStorageSource" | ||||
| import Zoomcontrol from "../UI/Zoomcontrol" | ||||
| 
 | ||||
| export type ThemeViewTabStates = (typeof MenuState._themeviewTabs)[number] | ||||
| export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number] | ||||
|  | @ -114,7 +115,36 @@ export class MenuState { | |||
|                 name: "background", | ||||
|                 showOverOthers: true, | ||||
|             }, | ||||
|             { | ||||
|                 toggle: this.communityIndexPanelIsOpened, | ||||
|                 name: "community", | ||||
|                 showOverOthers: true, | ||||
|             }, | ||||
|             { | ||||
|                 toggle: this.privacyPanelIsOpened, | ||||
|                 name: "privacy", | ||||
|                 showOverOthers: true, | ||||
|             }, | ||||
|             { | ||||
|                 toggle: this.filtersPanelIsOpened, | ||||
|                 name: "filters", | ||||
|                 showOverOthers: true, | ||||
|             }, | ||||
|         ] | ||||
|         for (const toggle of this.allToggles) { | ||||
|             toggle.toggle.addCallback((isOpen) => { | ||||
|                 if (!isOpen) { | ||||
|                     this.resetZoomIfAllClosed() | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private resetZoomIfAllClosed() { | ||||
|         if (this.isSomethingOpen()) { | ||||
|             return | ||||
|         } | ||||
|         Zoomcontrol.resetzoom() | ||||
|     } | ||||
| 
 | ||||
|     public openFilterView(highlightLayer?: LayerConfig | string) { | ||||
|  | @ -146,27 +176,23 @@ export class MenuState { | |||
|         this.highlightedUserSetting.setData(highlightTagRendering) | ||||
|     } | ||||
| 
 | ||||
|     public isSomethingOpen(): boolean { | ||||
|         return this.allToggles.some((t) => t.toggle.data) | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close all floatOvers. | ||||
|      * Returns 'true' if at least one menu was opened | ||||
|      */ | ||||
|     public closeAll(): boolean { | ||||
|         const toggles = [ | ||||
|             this.communityIndexPanelIsOpened, | ||||
|             this.privacyPanelIsOpened, | ||||
|             this.backgroundLayerSelectionIsOpened, | ||||
|             this.filtersPanelIsOpened, | ||||
|             this.menuIsOpened, | ||||
|             this.themeIsOpened, | ||||
|         ] | ||||
|         let somethingIsOpen = false | ||||
|         for (const t of toggles) { | ||||
|             somethingIsOpen = t.data | ||||
|             t.setData(false) | ||||
|             if (somethingIsOpen) { | ||||
|         let somethingWasOpen = false | ||||
|         for (const t of this.allToggles) { | ||||
|             somethingWasOpen = t.toggle.data | ||||
|             t.toggle.setData(false) | ||||
|             if (somethingWasOpen) { | ||||
|                 break | ||||
|             } | ||||
|         } | ||||
|         return somethingIsOpen | ||||
|         return somethingWasOpen | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -61,6 +61,7 @@ import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSou | |||
| import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource" | ||||
| import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider" | ||||
| import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl" | ||||
| import Zoomcontrol from "../UI/Zoomcontrol" | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  | @ -481,6 +482,12 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|             this.lastClickObject.features.setData([]) | ||||
|         }) | ||||
| 
 | ||||
|         this.selectedElement.addCallback((selected) => { | ||||
|             if (selected === undefined) { | ||||
|                 Zoomcontrol.resetzoom() | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { | ||||
|             Utils.LoadCustomCss(this.layout.customCss) | ||||
|         } | ||||
|  | @ -524,7 +531,10 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|             } | ||||
|             this.selectedElement.setData(undefined) | ||||
|             this.guistate.closeAll() | ||||
|             this.focusOnMap() | ||||
|             if (!this.guistate.isSomethingOpen()) { | ||||
|                 Zoomcontrol.resetzoom() | ||||
|                 this.focusOnMap() | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|         Hotkeys.RegisterHotkey({ nomod: "f" }, docs.selectFavourites, () => { | ||||
|  |  | |||
|  | @ -5,12 +5,16 @@ | |||
|   import panzoom from "panzoom" | ||||
|   import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import Zoomcontrol from "../Zoomcontrol" | ||||
|   import { onDestroy, onMount } from "svelte" | ||||
| 
 | ||||
|   export let image: ProvidedImage | ||||
|   let panzoomInstance = undefined | ||||
|   let panzoomEl: HTMLElement | ||||
|   export let isLoaded: UIEventSource<boolean> = undefined | ||||
|    | ||||
|   onDestroy(Zoomcontrol.createLock()) | ||||
|    | ||||
|   $: { | ||||
|     if (panzoomEl) { | ||||
|       panzoomInstance = panzoom(panzoomEl, { | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ | |||
| </script> | ||||
| 
 | ||||
| {#if hasLayers} | ||||
|   <form class="flex h-full w-full flex-col"> | ||||
|   <form class="flex h-full w-full flex-col" on:submit|preventDefault={() => {}}> | ||||
|     <button | ||||
|       tabindex="-1" | ||||
|       on:click={() => apply()} | ||||
|  |  | |||
|  | @ -395,7 +395,7 @@ | |||
|   <!-- Floatover with the selected element, if applicable --> | ||||
|   <FloatOver | ||||
|     on:close={() => { | ||||
|       selectedElement.setData(undefined) | ||||
|       state.selectedElement.setData(undefined) | ||||
|     }} | ||||
|   > | ||||
|     <div class="flex h-full w-full"> | ||||
|  |  | |||
							
								
								
									
										70
									
								
								src/UI/Zoomcontrol.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/UI/Zoomcontrol.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| import { Stores, UIEventSource } from "../Logic/UIEventSource" | ||||
| 
 | ||||
| /** | ||||
|  * Utilities to (re)set user zoom (this is when the user enlarges HTML-elements by pinching out a non-map element). | ||||
|  * If the user zooms in and goes back to the map, it should reset to 1.0 | ||||
|  */ | ||||
| export default class Zoomcontrol { | ||||
|     private static readonly singleton = new Zoomcontrol() | ||||
|     private static readonly initialValue = | ||||
|         "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0, user-scalable=1" | ||||
|     private static readonly noZoom = | ||||
|         "width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=1" | ||||
| 
 | ||||
|     private readonly viewportElement: HTMLMetaElement | ||||
| 
 | ||||
|     private readonly _allowZooming: UIEventSource<boolean> | ||||
|     private readonly _lockTokens: Set<any> = new Set<any>() | ||||
| 
 | ||||
|     private constructor() { | ||||
|         const metaElems = document.getElementsByTagName("head")[0].getElementsByTagName("meta") | ||||
|         this.viewportElement = Array.from(metaElems).find( | ||||
|             (meta) => meta.getAttribute("name") === "viewport" | ||||
|         ) | ||||
|         this._allowZooming = new UIEventSource<boolean>(true) | ||||
|         this._allowZooming.addCallback((allowed) => { | ||||
|             this.apply(allowed ? Zoomcontrol.initialValue : Zoomcontrol.noZoom) | ||||
|         }) | ||||
|         Stores.Chronic(1000).addCallback((_) => | ||||
|             console.log(this.viewportElement.getAttribute("content")) | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     private _resetZoom() { | ||||
|         this.apply(Zoomcontrol.noZoom) | ||||
|         requestAnimationFrame(() => { | ||||
|             // Does not work on firefox, see https://bugzilla.mozilla.org/show_bug.cgi?id=1873934
 | ||||
|             this.allowZoomIfUnlocked() | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private apply(fullSpec: string) { | ||||
|         this.viewportElement.setAttribute("content", fullSpec) | ||||
|     } | ||||
| 
 | ||||
|     public static createLock(): () => void { | ||||
|         return Zoomcontrol.singleton._createLock() | ||||
|     } | ||||
| 
 | ||||
|     private allowZoomIfUnlocked() { | ||||
|         if (this._lockTokens.size > 0) { | ||||
|             return | ||||
|         } | ||||
|         this.apply(Zoomcontrol.initialValue) | ||||
|     } | ||||
| 
 | ||||
|     private _createLock(): () => void { | ||||
|         const token = {} | ||||
|         const lockTokens = this._lockTokens | ||||
|         lockTokens.add(token) | ||||
|         this._resetZoom() | ||||
|         return () => { | ||||
|             lockTokens.delete(token) | ||||
|             this.allowZoomIfUnlocked() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static resetzoom() { | ||||
|         this.singleton._resetZoom() | ||||
|     } | ||||
| } | ||||
|  | @ -4,7 +4,7 @@ | |||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <!-- We disable 'user-scalable'. As we are working with a map, the user might zoom in using a popup, close the popup and _not_ be able to zoom out again. --> | ||||
|     <meta content="width=device-width, initial-scale=1.0, user-scalable=yes" name="viewport"> | ||||
|     <meta content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0, user-scalable=yes" name="viewport" > | ||||
|     <!-- CSP --> | ||||
|     <link href="./css/mobile.css" rel="stylesheet"/> | ||||
|     <link href="./css/openinghourstable.css" rel="stylesheet"/> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue