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;
|
margin-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx-4 {
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
max-height: 3rem;
|
max-height: 3rem;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
margin-left: 1rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mapping-icon-large {
|
.mapping-icon-large {
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
margin-right: 1.5rem;
|
margin-right: 1.5rem;
|
||||||
margin-left: 1.5rem;
|
margin-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,11 @@ export default class ThemeViewStateHashActor {
|
||||||
if (!!hash) {
|
if (!!hash) {
|
||||||
// There is still a hash
|
// There is still a hash
|
||||||
// We _only_ have to (at most) close the overlays in this case
|
// 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(";")
|
const parts = hash.split(";")
|
||||||
if (parts.indexOf("background") < 0) {
|
if (parts.indexOf("background") < 0) {
|
||||||
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
|
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
|
||||||
|
@ -176,6 +181,10 @@ export default class ThemeViewStateHashActor {
|
||||||
|
|
||||||
private back() {
|
private back() {
|
||||||
const state = this._state
|
const state = this._state
|
||||||
|
if (state.previewedImage.data) {
|
||||||
|
state.previewedImage.setData(undefined)
|
||||||
|
return
|
||||||
|
}
|
||||||
// history.pushState(null, null, window.location.pathname);
|
// history.pushState(null, null, window.location.pathname);
|
||||||
if (state.selectedElement.data) {
|
if (state.selectedElement.data) {
|
||||||
state.selectedElement.setData(undefined)
|
state.selectedElement.setData(undefined)
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { UIEventSource } from "../Logic/UIEventSource"
|
||||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||||
import { Utils } from "../Utils"
|
import { Utils } from "../Utils"
|
||||||
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
||||||
|
import Zoomcontrol from "../UI/Zoomcontrol"
|
||||||
|
|
||||||
export type ThemeViewTabStates = (typeof MenuState._themeviewTabs)[number]
|
export type ThemeViewTabStates = (typeof MenuState._themeviewTabs)[number]
|
||||||
export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number]
|
export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number]
|
||||||
|
@ -114,7 +115,36 @@ export class MenuState {
|
||||||
name: "background",
|
name: "background",
|
||||||
showOverOthers: true,
|
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) {
|
public openFilterView(highlightLayer?: LayerConfig | string) {
|
||||||
|
@ -146,27 +176,23 @@ export class MenuState {
|
||||||
this.highlightedUserSetting.setData(highlightTagRendering)
|
this.highlightedUserSetting.setData(highlightTagRendering)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isSomethingOpen(): boolean {
|
||||||
|
return this.allToggles.some((t) => t.toggle.data)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close all floatOvers.
|
* Close all floatOvers.
|
||||||
* Returns 'true' if at least one menu was opened
|
* Returns 'true' if at least one menu was opened
|
||||||
*/
|
*/
|
||||||
public closeAll(): boolean {
|
public closeAll(): boolean {
|
||||||
const toggles = [
|
let somethingWasOpen = false
|
||||||
this.communityIndexPanelIsOpened,
|
for (const t of this.allToggles) {
|
||||||
this.privacyPanelIsOpened,
|
somethingWasOpen = t.toggle.data
|
||||||
this.backgroundLayerSelectionIsOpened,
|
t.toggle.setData(false)
|
||||||
this.filtersPanelIsOpened,
|
if (somethingWasOpen) {
|
||||||
this.menuIsOpened,
|
|
||||||
this.themeIsOpened,
|
|
||||||
]
|
|
||||||
let somethingIsOpen = false
|
|
||||||
for (const t of toggles) {
|
|
||||||
somethingIsOpen = t.data
|
|
||||||
t.setData(false)
|
|
||||||
if (somethingIsOpen) {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return somethingIsOpen
|
return somethingWasOpen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ import NearbyFeatureSource from "../Logic/FeatureSource/Sources/NearbyFeatureSou
|
||||||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
||||||
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||||
import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
|
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.lastClickObject.features.setData([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.selectedElement.addCallback((selected) => {
|
||||||
|
if (selected === undefined) {
|
||||||
|
Zoomcontrol.resetzoom()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
|
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
|
||||||
Utils.LoadCustomCss(this.layout.customCss)
|
Utils.LoadCustomCss(this.layout.customCss)
|
||||||
}
|
}
|
||||||
|
@ -524,7 +531,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
}
|
}
|
||||||
this.selectedElement.setData(undefined)
|
this.selectedElement.setData(undefined)
|
||||||
this.guistate.closeAll()
|
this.guistate.closeAll()
|
||||||
|
if (!this.guistate.isSomethingOpen()) {
|
||||||
|
Zoomcontrol.resetzoom()
|
||||||
this.focusOnMap()
|
this.focusOnMap()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
Hotkeys.RegisterHotkey({ nomod: "f" }, docs.selectFavourites, () => {
|
Hotkeys.RegisterHotkey({ nomod: "f" }, docs.selectFavourites, () => {
|
||||||
|
|
|
@ -5,12 +5,16 @@
|
||||||
import panzoom from "panzoom"
|
import panzoom from "panzoom"
|
||||||
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import Zoomcontrol from "../Zoomcontrol"
|
||||||
|
import { onDestroy, onMount } from "svelte"
|
||||||
|
|
||||||
export let image: ProvidedImage
|
export let image: ProvidedImage
|
||||||
let panzoomInstance = undefined
|
let panzoomInstance = undefined
|
||||||
let panzoomEl: HTMLElement
|
let panzoomEl: HTMLElement
|
||||||
export let isLoaded: UIEventSource<boolean> = undefined
|
export let isLoaded: UIEventSource<boolean> = undefined
|
||||||
|
|
||||||
|
onDestroy(Zoomcontrol.createLock())
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (panzoomEl) {
|
if (panzoomEl) {
|
||||||
panzoomInstance = panzoom(panzoomEl, {
|
panzoomInstance = panzoom(panzoomEl, {
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasLayers}
|
{#if hasLayers}
|
||||||
<form class="flex h-full w-full flex-col">
|
<form class="flex h-full w-full flex-col" on:submit|preventDefault={() => {}}>
|
||||||
<button
|
<button
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
on:click={() => apply()}
|
on:click={() => apply()}
|
||||||
|
|
|
@ -395,7 +395,7 @@
|
||||||
<!-- Floatover with the selected element, if applicable -->
|
<!-- Floatover with the selected element, if applicable -->
|
||||||
<FloatOver
|
<FloatOver
|
||||||
on:close={() => {
|
on:close={() => {
|
||||||
selectedElement.setData(undefined)
|
state.selectedElement.setData(undefined)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex h-full w-full">
|
<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>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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. -->
|
<!-- 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 -->
|
<!-- CSP -->
|
||||||
<link href="./css/mobile.css" rel="stylesheet"/>
|
<link href="./css/mobile.css" rel="stylesheet"/>
|
||||||
<link href="./css/openinghourstable.css" rel="stylesheet"/>
|
<link href="./css/openinghourstable.css" rel="stylesheet"/>
|
||||||
|
|
Loading…
Reference in a new issue