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

@ -189,6 +189,10 @@
"addExtraTags": [
"contact:phone="
]
},
"editButtonAriaLabel": {
"en": "Edit phone number",
"nl": "Pas telefoonnummer aan"
}
},
{
@ -268,6 +272,10 @@
"addExtraTags": [
"contact:email="
]
},
"editButtonAriaLabel": {
"en": "Edit email address",
"nl": "Pas emailadres aan"
}
},
{
@ -318,7 +326,11 @@
"hideInAnswer": true,
"icon": "./assets/layers/icons/website.svg"
}
]
],
"editButtonAriaLabel": {
"en": "Edit website",
"nl": "Pas website aan"
}
},
{
"id": "wheelchair-access",

View file

@ -306,7 +306,7 @@
"sunday": "On sunday {ranges}",
"thursday": "On thursday {ranges}",
"tuesday": "On tuesday {ranges}",
"unknown": "The opening hours are unkown",
"unknown": "The opening hours are unknown",
"wednesday": "On wednesday {ranges}"
},
"osmLinkTooltip": "Browse this object on OpenStreetMap for history and more editing options",
@ -398,12 +398,12 @@
"useSearch": "Use the search above to see presets",
"useSearchForMore": "Use the search function to search within {total} more values…",
"visualFeedback": {
"closestFeaturesAre": "Closest features are:",
"closestFeaturesAre": "{n} features within view",
"east": "Moving east",
"in": "Zooming in",
"islocked": "View locked to your GPS-location, moving disabled. Press the geolocation button to unlock.",
"locked": "View is now locked to your GPS-location, moving disabled.",
"navigation": "Use arrow keys to move the map, press space to select the closest feature",
"navigation": "Use arrow keys to move the map, press space to select the closest feature. Press a number to select locations further away.",
"noCloseFeatures": "No features in view",
"north": "Moving north",
"out": "Zooming out",

View file

@ -50,6 +50,12 @@
"panelIntro": "<h3>Jouw persoonlijke thema</h3>Activeer je favorite lagen van alle andere themas",
"reload": "Herlaad de data"
},
"favouritePoi": {
"button": {
"isMarkedShort": "Als favoriet gemarkeerd",
"isNotMarkedShort": "Niet als favoriet gemarkeerd"
}
},
"flyer": {
"aerial": "Deze kaart gebruikt luchtfoto's van het Agentschap Informatie Vlaanderen als achtergrond.\nOok het GRB is beschikbaar als achtergrondlaag.",
"callToAction": "Probeer het uit op mapcomplete.org",
@ -162,6 +168,7 @@
},
"back": "Vorige",
"backToIndex": "Keer terug naar het overzicht met alle thematische kaarten",
"backToMap": "Ga terug naar de kaart",
"backgroundMap": "Selecteer een achtergrondlaag",
"backgroundSwitch": "Verander achtergrond",
"cancel": "Annuleren",
@ -200,6 +207,14 @@
"histogram": {
"error_loading": "Kan het histogram niet laden"
},
"labels": {
"background": "Kies achtergrondlaag",
"filter": "Filter data",
"jumpToLocation": "Ga naar jouw locatie",
"menu": "Menu",
"zoomIn": "Zoom in",
"zoomOut": "Zoom uit"
},
"layerSelection": {
"title": "Selecteer lagen",
"zoomInToSeeThisLayer": "Vergroot de kaart om deze laag te zien"
@ -245,11 +260,17 @@
"openTheMap": "Raadpleeg de kaart",
"openTheMapAtGeolocation": "Ga naar jouw locatie",
"opening_hours": {
"all_days_from": "Elke dag geopend {ranges}",
"closed_permanently": "Gesloten voor onbepaalde tijd",
"closed_until": "Gesloten - open op {date}",
"error": "Kan de openingsuren niet inlezen",
"error_loading": "Sorry, deze openingsuren kunnen niet getoond worden",
"friday": "Op vrijdag {ranges}",
"loadingCountry": "Het land wordt nog bepaald…",
"monday": "Op maandag {ranges}",
"not_all_rules_parsed": "De openingsuren zijn ingewikkeld. De volgende regels worden niet getoond bij het ingeven:",
"on_weekdays": "Op weekdagen {ranges}",
"on_weekends": "In het weekend {ranges}",
"openTill": "tot",
"open_24_7": "Dag en nacht open",
"open_during_ph": "Op een feestdag is dit",
@ -257,7 +278,15 @@
"ph_closed": "gesloten",
"ph_not_known": " ",
"ph_open": "open",
"ph_open_as_usual": "geopend zoals gewoonlijk"
"ph_open_as_usual": "geopend zoals gewoonlijk",
"ranges": "van {starttime} tot {endtime}",
"rangescombined": "{range0} en {range1}",
"saturday": "Op zaterdag {ranges}",
"sunday": "Op zondag {ranges}",
"thursday": "Op donderdag {ranges}",
"tuesday": "Op dinsdag {ranges}",
"unknown": "De openingsuren zijn niet gekend",
"wednesday": "Op woensdag {ranges}"
},
"osmLinkTooltip": "Bekijk dit object op OpenStreetMap om de geschiedenis te zien en meer te kunnen aanpassen",
"pdf": {
@ -300,6 +329,7 @@
"searchShort": "Zoek…",
"searching": "Aan het zoeken…"
},
"share": "Deel deze locatie",
"sharescreen": {
"copiedToClipboard": "Link gekopieerd naar klembord",
"embedIntro": "<h3>Plaats dit op je website</h3>Voeg dit kaartje toe op je eigen website.<br/>We moedigen dit zelfs aan - je hoeft geen toestemming te vragen.<br/> Het is gratis en zal dat altijd blijven. Hoe meer het gebruikt wordt, hoe waardevoller",
@ -340,6 +370,20 @@
},
"useSearch": "Gebruik de zoekfunctie hierboven om meer opties te zien",
"useSearchForMore": "Gebruik de zoekfunctie om {total} meer waarden te vinden…",
"visualFeedback": {
"closestFeaturesAre": "{n} object in in beeld",
"east": "Naar het oosten",
"in": "Aan het inzoomen",
"islocked": "Bewegen vergrendeld rond je huidige locatie. Duw op de geolocatie-knop om te ontgrendelen.",
"locked": "Bewegen vergrendeld rond jouw huidige locatie.",
"navigation": "Gebruik de pijltjestoetsen om te bewegen. Druk op spatie om het meest centrale punt te selecteren. Druk op een cijfertoets om andere items te selecteren.",
"noCloseFeatures": "Niet in beeld",
"north": "Naar het noorden",
"out": "Aan het uitzoomen",
"south": "Naar het zuiden",
"unlocked": "Bewegen ontgrendeld",
"west": "Naar het westen"
},
"weekdays": {
"abbreviations": {
"friday": "Vrij",

View file

@ -32,7 +32,7 @@
"https://overpass.openstreetmap.ru/cgi/interpreter"
],
"country_coder_host": "https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/latlon2country",
"nominatimEndpoint": "https://nominatim.openstreetmap.org/search?"
"nominatimEndpoint": "https://nominatim.openstreetmap.org/"
},
"scripts": {
"start": "npm run generate:layeroverview && npm run strt",

View file

@ -1,6 +1,7 @@
import { Utils } from "../../Utils"
import { BBox } from "../BBox"
import Constants from "../../Models/Constants"
import { FeatureCollection } from "geojson"
export interface GeoCodeResult {
display_name: string
@ -20,12 +21,21 @@ export class Geocoding {
static async Search(query: string, bbox: BBox): Promise<GeoCodeResult[]> {
const b = bbox ?? BBox.global
const url =
Geocoding.host +
"format=json&limit=1&viewbox=" +
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}` +
"&accept-language=nl&q=" +
query
const url = `${
Geocoding.host
}search?format=json&limit=1&viewbox=${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}&accept-language=nl&q=${query}`
return Utils.downloadJson(url)
}
static async reverse(
coordinate: { lon: number; lat: number },
zoom: number = 18
): Promise<FeatureCollection> {
// https://nominatim.org/release-docs/develop/api/Reverse/
// IF the zoom is low, it'll only return a country instead of an address
const url = `${Geocoding.host}reverse?format=geojson&lat=${coordinate.lat}&lon=${
coordinate.lon
}&zoom=${Math.round(zoom)}`
return Utils.downloadJson(url)
}
}

View file

@ -16,6 +16,7 @@ export interface MapProperties {
readonly maxbounds: UIEventSource<undefined | BBox>
readonly allowMoving: UIEventSource<true | boolean>
readonly allowRotating: UIEventSource<true | boolean>
readonly rotation: UIEventSource<number>
readonly lastClickLocation: Store<{ lon: number; lat: number }>
readonly allowZooming: UIEventSource<true | boolean>

View file

@ -14,13 +14,7 @@ export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number]
* Some convenience methods are provided for this as well
*/
export class MenuState {
public static readonly _themeviewTabs = [
"intro",
"filters",
"download",
"copyright",
"share",
] as const
public static readonly _themeviewTabs = ["intro", "download", "copyright", "share"] as const
public static readonly _menuviewTabs = [
"about",
"settings",
@ -39,6 +33,8 @@ export class MenuState {
public readonly backgroundLayerSelectionIsOpened: UIEventSource<boolean> =
new UIEventSource<boolean>(false)
public readonly filtersPanelIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public readonly allToggles: {
toggle: UIEventSource<boolean>
name: string
@ -84,8 +80,8 @@ export class MenuState {
this.highlightedUserSetting.setData(undefined)
}
})
this.themeViewTab.addCallbackAndRun((tab) => {
if (tab !== "filters") {
this.filtersPanelIsOpened.addCallbackAndRun((isOpen) => {
if (!isOpen) {
this.highlightedLayerInFilters.setData(undefined)
}
})
@ -121,8 +117,7 @@ export class MenuState {
}
public openFilterView(highlightLayer?: LayerConfig | string) {
this.themeIsOpened.setData(true)
this.themeViewTab.setData("filters")
this.filtersPanelIsOpened.setData(true)
if (highlightLayer) {
if (typeof highlightLayer !== "string") {
highlightLayer = highlightLayer.id
@ -159,6 +154,7 @@ export class MenuState {
this.menuIsOpened,
this.themeIsOpened,
this.backgroundLayerSelectionIsOpened,
this.filtersPanelIsOpened,
]
const somethingIsOpen = toggles.some((t) => t.data)
toggles.forEach((t) => t.setData(false))

View file

@ -279,6 +279,13 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
*/
questionHint?: Translatable
/**
* When using a screenreader and selecting the 'edit' button, the current rendered value is read aloud in normal circumstances.
* In some rare cases, this is not desirable. For example, if the rendered value is a link to a website, this link can be selected (and will be read aloud).
* If the user presses _tab_ again, they'll select the button and have the link read aloud a second time.
*/
editButtonAriaLabel?: Translatable
/**
* A list of labels. These are strings that are used for various purposes, e.g. to only include a subset of the tagRenderings when reusing a layer
*/

View file

@ -76,6 +76,7 @@ export default class TagRenderingConfig {
public readonly multiAnswer: boolean
public readonly mappings?: Mapping[]
public readonly editButtonAriaLabel?: Translation
public readonly labels: string[]
public readonly classes: string[]
@ -134,6 +135,11 @@ export default class TagRenderingConfig {
this.question = Translations.T(json.question, translationKey + ".question")
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
this.description = Translations.T(json.description, translationKey + ".description")
this.editButtonAriaLabel = Translations.T(
json.editButtonAriaLabel,
translationKey + ".editButtonAriaLabel"
)
this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`)
this.invalidValues = json["invalidValues"]
? TagUtils.Tag(json["invalidValues"], `${context}.invalidValues`)

View file

@ -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 { Orientation } from "../Sensors/Orientation"
/**
*
@ -115,8 +116,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
readonly geolocation: GeoLocationHandler
readonly geolocationControl: GeolocationControlState
readonly lastGeolocationRequestMoment: UIEventSource<Date> = new UIEventSource<Date>(undefined)
readonly imageUploadManager: ImageUploadManager
readonly previewedImage = new UIEventSource<ProvidedImage>(undefined)

33
src/Sensors/Motion.ts Normal file
View file

@ -0,0 +1,33 @@
import { UIEventSource } from "../Logic/UIEventSource"
export default class Motion {
public static singleton = new Motion()
/**
* In m/s²
*/
public maxAcc = new UIEventSource<number>(0)
public lastShakeEvent = new UIEventSource<Date>(undefined)
private isListening = false
private constructor() {
this.startListening()
}
private onUpdate(eventData: DeviceMotionEvent) {
const acc = eventData.acceleration
this.maxAcc.setData(Math.max(acc.x, acc.y, acc.z))
if (this.maxAcc.data > 22) {
this.lastShakeEvent.setData(new Date())
}
}
startListening() {
if (this.isListening) {
return
}
this.isListening = true
console.log("Listening to motion events", this)
window.addEventListener("devicemotion", (e) => this.onUpdate(e))
}
}

View file

@ -30,9 +30,13 @@ export class Orientation {
*/
public arrowDirection: UIEventSource<number> = new UIEventSource(undefined)
private _measurementsStarted = false
private _animateFakeMeasurements = false
constructor() {}
constructor() {
// this.fakeMeasurements(true)
}
// noinspection JSUnusedGlobalSymbols
public fakeMeasurements(rotateAlpha: boolean = true) {
console.log("Starting fake measurements of orientation sensors", {
alpha: this.alpha,
@ -41,10 +45,15 @@ export class Orientation {
absolute: this.absolute,
})
this.alpha.setData(45)
if (rotateAlpha) {
Stores.Chronic(25).addCallback((date) =>
this.alpha.setData(-(date.getTime() / 10) % 360)
)
this._animateFakeMeasurements = true
Stores.Chronic(25).addCallback((date) => {
this.alpha.setData((date.getTime() / 100) % 360)
if (!this._animateFakeMeasurements) {
return true
}
})
}
this.beta.setData(20)
this.gamma.setData(30)

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
@ -32,14 +30,16 @@
let feedback: string = undefined
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
feedback = undefined
function focusOnSearch() {
requestAnimationFrame(() => {
inputElement?.focus()
inputElement?.select()
})
}
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
feedback = undefined
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>
{$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

View file

@ -1,4 +1,5 @@
import { Translation } from "../UI/i18n/Translation"
import Locale from "../UI/i18n/Locale"
export function ariaLabel(htmlElement: Element, t: Translation) {
if (!t) {
@ -6,6 +7,19 @@ export function ariaLabel(htmlElement: Element, t: Translation) {
}
let destroy: () => void = undefined
Locale.language.map((language) => {
if (!t.translations[language]) {
console.log(
"No aria label in",
language,
"for",
t.context,
"; en is",
t.translations["en"]
)
}
})
t.current.map(
(label) => {
htmlElement.setAttribute("aria-label", label)