A11y: various improvements

This commit is contained in:
Pieter Vander Vennet 2023-12-19 22:21:34 +01:00
parent 0d4f2c9c36
commit 5fa2ddd9c1
23 changed files with 327 additions and 98 deletions

View file

@ -0,0 +1,39 @@
<script lang="ts">
/**
* THe panel containing all filter- and layerselection options
*/
import OverlayToggle from "./OverlayToggle.svelte"
import Filterview from "./Filterview.svelte"
import ThemeViewState from "../../Models/ThemeViewState"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
import Filter from "../../assets/svg/Filter.svelte"
export let state: ThemeViewState
let layout = state.layout
</script>
<div class="m-2 flex flex-col">
<h2 class="flex items-center">
<Filter class="h-6 w-6 pr-2" />
<Tr t={Translations.t.general.menu.filter} />
</h2>
{#each layout.layers as layer}
<Filterview
zoomlevel={state.mapProperties.zoom}
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
/>
{/each}
{#each layout.tileLayerSources as tilesource}
<OverlayToggle
layerproperties={tilesource}
state={state.overlayLayerStates.get(tilesource.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
zoomlevel={state.mapProperties.zoom}
/>
{/each}
</div>

View file

@ -1,8 +1,6 @@
<script lang="ts">
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource"
import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg.js"
import Translations from "../i18n/Translations"
import Loading from "../Base/Loading.svelte"
import Hotkeys from "../Base/Hotkeys"
@ -23,7 +21,7 @@
onDestroy(
triggerSearch.addCallback((_) => {
performSearch()
})
}),
)
let isRunning: boolean = false
@ -31,15 +29,17 @@
let inputElement: HTMLInputElement
let feedback: string = undefined
function focusOnSearch() {
requestAnimationFrame(() => {
inputElement?.focus()
inputElement?.select()
})
}
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
feedback = undefined
requestAnimationFrame(() => {
inputElement?.focus()
inputElement?.select()
})
focusOnSearch()
})
const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>()
@ -62,6 +62,7 @@
const result = await Geocoding.Search(searchContents, bounds.data)
if (result.length == 0) {
feedback = Translations.t.general.search.nothing.txt
focusOnSearch()
return
}
const poi = result[0]
@ -70,7 +71,7 @@
new BBox([
[lon0, lat0],
[lon1, lat1],
]).pad(0.01)
]).pad(0.01),
)
if (perLayer !== undefined) {
const id = poi.osm_type + "/" + poi.osm_id
@ -78,11 +79,11 @@
for (const layer of layers) {
const found = layer.features.data.find((f) => f.properties.id === id)
if (found === undefined) {
continue;
continue
}
selectedElement?.setData(found);
console.log("Found an element that probably matches:", selectedElement?.data);
break;
selectedElement?.setData(found)
console.log("Found an element that probably matches:", selectedElement?.data)
break
}
}
if (clearAfterView) {
@ -93,6 +94,7 @@
} catch (e) {
console.error(e)
feedback = Translations.t.general.search.error.txt
focusOnSearch()
} finally {
isRunning = false
}
@ -100,23 +102,25 @@
</script>
<div class="normal-background flex justify-between rounded-full pl-2">
<form class="w-full">
<form class="w-full flex flex-wrap">
{#if isRunning}
<Loading>{Translations.t.general.search.searching}</Loading>
{:else if feedback !== undefined}
<div class="alert" on:click={() => (feedback = undefined)}>
{feedback}
</div>
{:else}
<input
type="search"
class="w-full"
bind:this={inputElement}
on:keypress={(keypr) => (keypr.key === "Enter" ? performSearch() : undefined)}
on:keypress={(keypr) =>{ feedback = undefined; return (keypr.key === "Enter" ? performSearch() : undefined); }}
bind:value={searchContents}
use:placeholder={Translations.t.general.search.search}
/>
{#if feedback !== undefined}
<!-- The feedback is _always_ shown for screenreaders and to make sure that the searchfield can still be selected by tabbing-->
<div class="alert " role="alert" aria-live="assertive">
{feedback}
</div>
{/if}
{/if}
</form>
<SearchIcon class="h-6 w-6 self-end" aria-hidden="true" on:click={performSearch}/>
<SearchIcon aria-hidden="true" class="h-6 w-6 self-end" on:click={performSearch} />
</div>

View file

@ -0,0 +1,45 @@
<script lang="ts">/**
* Shows the current address when shaken
**/
import Motion from "../../Sensors/Motion"
import { Geocoding } from "../../Logic/Osm/Geocoding"
import type { MapProperties } from "../../Models/MapProperties"
export let mapProperties: MapProperties
let lastDisplayed: Date = undefined
let currentLocation: string = undefined
async function displayLocation() {
lastDisplayed = new Date()
let result = await Geocoding.reverse(
mapProperties.location.data,
mapProperties.zoom.data,
)
console.log("Got result", result)
let properties = result.features[0].properties
currentLocation = properties.display_name
window.setTimeout(() => {
currentLocation = undefined
}, 5000)
}
Motion.singleton.lastShakeEvent.addCallbackD(shaken => {
console.log("Got a shaken event")
if (shaken.getTime() - lastDisplayed.getTime() < 1000) {
console.log("To soon:",shaken.getTime() - lastDisplayed.getTime())
// return
}
displayLocation()
})
Motion.singleton.startListening()
mapProperties.location.stabilized(500).addCallbackAndRun(loc => {
displayLocation()
})
</script>
{#if currentLocation}
<div role="alert" aria-live="assertive" class="normal-background">
{currentLocation}
</div>
{/if}

View file

@ -30,10 +30,12 @@
)
</script>
<button class="cursor-pointer small flex" on:click={() => select()}>
<div class="cursor-pointer small flex" on:click={() => select()}>
<span class="flex">
{#if i !== undefined}
<span class="font-bold">{i + 1}.</span>
{/if}
<TagRenderingAnswer config={layer.title} {layer} selectedElement={feature} {state} {tags} />
<span class="flex">{$bearingAndDist.dist}m {$bearingAndDist.bearing}°</span>
</button>
<TagRenderingAnswer config={layer.title} {layer} selectedElement={feature} {state} {tags} />
{$bearingAndDist.dist}m {$bearingAndDist.bearing}°
</span>
</div>

View file

@ -19,7 +19,7 @@
})
lastAction.stabilized(750).addCallbackAndRunD(_ => lastAction.setData(undefined))
</script>
<div aria-live="assertive" class=" interactive p-1" role="alert">
<div aria-live="assertive" class="p-1" role="alert">
{#if $lastAction !== undefined}
<Tr t={Translations.t.general.visualFeedback[$lastAction.key]} />

View file

@ -9,7 +9,6 @@ import SvelteUIElement from "../Base/SvelteUIElement"
import MaplibreMap from "./MaplibreMap.svelte"
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
import * as htmltoimage from "html-to-image"
import { ALL } from "node:dns"
/**
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
@ -41,6 +40,8 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
readonly lastClickLocation: Store<undefined | { lon: number; lat: number }>
readonly minzoom: UIEventSource<number>
readonly maxzoom: UIEventSource<number>
readonly rotation: UIEventSource<number>
readonly animationRunning = new UIEventSource(false)
/**
* Functions that are called when one of those actions has happened
@ -81,6 +82,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
this.allowRotating = state?.allowRotating ?? new UIEventSource<boolean>(true)
this.allowZooming = state?.allowZooming ?? new UIEventSource(true)
this.bounds = state?.bounds ?? new UIEventSource(undefined)
this.rotation = state?.rotation ?? new UIEventSource<number>(0)
this.rasterLayer =
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
@ -121,6 +123,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setMinzoom(self.minzoom.data)
self.setMaxzoom(self.maxzoom.data)
self.setBounds(self.bounds.data)
self.SetRotation(self.rotation.data)
self.setBackground()
this.updateStores(true)
map.on("moveend", () => this.updateStores())
@ -133,6 +136,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.on("dblclick", (e) => {
handleClick(e)
})
map.on("rotateend", (e) => {
this.updateStores()
})
map.getContainer().addEventListener("keydown", (event) => {
let locked: "islocked" = undefined
if (!this.allowMoving.data) {
@ -169,12 +175,12 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
console.error("Could not set background")
})
)
this.location.addCallbackAndRunD((loc) => {
self.MoveMapToCurrentLoc(loc)
})
this.zoom.addCallbackAndRunD((z) => self.SetZoom(z))
this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox))
this.rotation.addCallbackAndRunD((bearing) => self.SetRotation(bearing))
this.allowMoving.addCallbackAndRun((allowMoving) => {
self.setAllowMoving(allowMoving)
self.pingKeycodeEvent(allowMoving ? "unlocked" : "locked")
@ -459,6 +465,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (this.bounds.data === undefined || !isSetup) {
this.bounds.setData(bbox)
}
this.rotation.setData(map.getBearing())
}
private SetZoom(z: number): void {
@ -471,6 +478,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
}
}
private SetRotation(bearing: number): void {
const map = this._maplibreMap.data
if (!map || bearing === undefined) {
return
}
map.rotateTo(bearing, { duration: 0 })
}
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }): void {
const map = this._maplibreMap.data
if (!map || loc === undefined) {

View file

@ -955,7 +955,6 @@ export class ToTextualDescription {
]
for (let i = 0; i < weekdays.length; i++) {
const day = weekdays[i]
console.log(day, "-->", ranges[i])
if (ranges[i]?.length > 0) {
result.push(
t[day].Subs({ ranges: ToTextualDescription.createRangesFor(ranges[i]) })

View file

@ -38,7 +38,7 @@
let htmlElem: HTMLDivElement
$: {
if (editMode && htmlElem !== undefined && config.IsKnown(tags)) {
if (editMode && htmlElem !== undefined && config.IsKnown($tags)) {
// EditMode switched to true yet the answer is already known, so the person wants to make a change
// Make sure that the question is in the scrollview!
@ -108,7 +108,8 @@
editMode = true
}}
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1"
aria-labelledby={answerId}
aria-labelledby={config.editButtonAriaLabel === undefined ? answerId : undefined}
use:ariaLabel={config.editButtonAriaLabel}
>
<PencilAltIcon />
</button>

View file

@ -1,10 +1,16 @@
<script lang="ts">
// Testing grounds
import { UIEventSource } from "../Logic/UIEventSource"
import SlopeInput from "./InputElement/Helpers/SlopeInput.svelte"
import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte"
let value: UIEventSource<string> = new UIEventSource(undefined)
import Motion from "../Sensors/Motion"
import { Store, Stores } from "../Logic/UIEventSource"
let maxAcc = Motion.singleton.maxAcc
let shaken =Motion.singleton.lastShakeEvent
let recentlyShaken = Stores.Chronic(250).mapD(now => now.getTime() - 3000 < shaken.data?.getTime())
</script>
<OrientationDebugPanel/>
<SlopeInput />
Acc: {$maxAcc}
{#if $recentlyShaken}
<div class="text-red-500 text-5xl">
SHAKEN
</div>
{/if}

View file

@ -66,6 +66,8 @@
import { Orientation } from "../Sensors/Orientation"
import GeolocationControl from "./BigComponents/GeolocationControl.svelte"
import Compass_arrow from "../assets/svg/Compass_arrow.svelte"
import ReverseGeocoding from "./BigComponents/ReverseGeocoding.svelte"
import FilterPanel from "./BigComponents/FilterPanel.svelte"
export let state: ThemeViewState
let layout = state.layout
@ -183,6 +185,7 @@
<!-- Flex and w-full are needed for the positioning -->
<!-- Centermessage -->
<StateIndicator {state} />
<ReverseGeocoding mapProperties={mapproperties}/>
</div>
</div>
@ -270,7 +273,7 @@
</MapControlButton>
{#if $compassLoaded}
<div class="absolute top-0 left-0 w-0 h-0 m-0.5 sm:m-1">
<Compass_arrow class="compass_arrow" style={`rotate: calc(${-$compass}deg + 225deg); transform-origin: 50% 50%;`} />
<Compass_arrow class="compass_arrow" style={`rotate: calc(${-$compass}deg + 45deg); transform-origin: 50% 50%;`} />
</div>
{/if}
</div>
@ -360,55 +363,39 @@
</div>
<div class="flex" slot="title1">
<Filter class="h-4 w-4" />
<Tr t={Translations.t.general.menu.filter} />
</div>
<div class="m-2 flex flex-col" slot="content1">
{#each layout.layers as layer}
<Filterview
zoomlevel={state.mapProperties.zoom}
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
/>
{/each}
{#each layout.tileLayerSources as tilesource}
<OverlayToggle
layerproperties={tilesource}
state={state.overlayLayerStates.get(tilesource.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}
zoomlevel={state.mapProperties.zoom}
/>
{/each}
</div>
<div class="flex" slot="title2">
<If condition={state.featureSwitches.featureSwitchEnableExport}>
<Download class="h-4 w-4" />
<Tr t={Translations.t.general.download.title} />
</If>
</div>
<div class="m-4" slot="content2">
<div class="m-4" slot="content1">
<DownloadPanel {state} />
</div>
<div slot="title3">
<div slot="title2">
<Tr t={Translations.t.general.attribution.title} />
</div>
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content2" />
<div class="flex" slot="title4">
<div class="flex" slot="title3">
<Share class="h-4 w-4" />
<Tr t={Translations.t.general.sharescreen.title} />
</div>
<div class="m-2" slot="content4">
<div class="m-2" slot="content3">
<ShareScreen {state} />
</div>
</TabbedGroup>
</FloatOver>
</If>
<If condition={state.guistate.filtersPanelIsOpened}>
<FloatOver on:close={() => state.guistate.filtersPanelIsOpened.setData(false)}>
<FilterPanel {state}/>
</FloatOver>
</If>
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
<!-- background layer selector -->
<FloatOver