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": [ "addExtraTags": [
"contact:phone=" "contact:phone="
] ]
},
"editButtonAriaLabel": {
"en": "Edit phone number",
"nl": "Pas telefoonnummer aan"
} }
}, },
{ {
@ -268,6 +272,10 @@
"addExtraTags": [ "addExtraTags": [
"contact:email=" "contact:email="
] ]
},
"editButtonAriaLabel": {
"en": "Edit email address",
"nl": "Pas emailadres aan"
} }
}, },
{ {
@ -318,7 +326,11 @@
"hideInAnswer": true, "hideInAnswer": true,
"icon": "./assets/layers/icons/website.svg" "icon": "./assets/layers/icons/website.svg"
} }
] ],
"editButtonAriaLabel": {
"en": "Edit website",
"nl": "Pas website aan"
}
}, },
{ {
"id": "wheelchair-access", "id": "wheelchair-access",
@ -2215,7 +2227,7 @@
} }
} }
] ]
}, },
{ {
"id": "internet-ssid", "id": "internet-ssid",
"labels": [ "labels": [

View file

@ -306,7 +306,7 @@
"sunday": "On sunday {ranges}", "sunday": "On sunday {ranges}",
"thursday": "On thursday {ranges}", "thursday": "On thursday {ranges}",
"tuesday": "On tuesday {ranges}", "tuesday": "On tuesday {ranges}",
"unknown": "The opening hours are unkown", "unknown": "The opening hours are unknown",
"wednesday": "On wednesday {ranges}" "wednesday": "On wednesday {ranges}"
}, },
"osmLinkTooltip": "Browse this object on OpenStreetMap for history and more editing options", "osmLinkTooltip": "Browse this object on OpenStreetMap for history and more editing options",
@ -398,12 +398,12 @@
"useSearch": "Use the search above to see presets", "useSearch": "Use the search above to see presets",
"useSearchForMore": "Use the search function to search within {total} more values…", "useSearchForMore": "Use the search function to search within {total} more values…",
"visualFeedback": { "visualFeedback": {
"closestFeaturesAre": "Closest features are:", "closestFeaturesAre": "{n} features within view",
"east": "Moving east", "east": "Moving east",
"in": "Zooming in", "in": "Zooming in",
"islocked": "View locked to your GPS-location, moving disabled. Press the geolocation button to unlock.", "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.", "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", "noCloseFeatures": "No features in view",
"north": "Moving north", "north": "Moving north",
"out": "Zooming out", "out": "Zooming out",

View file

@ -50,6 +50,12 @@
"panelIntro": "<h3>Jouw persoonlijke thema</h3>Activeer je favorite lagen van alle andere themas", "panelIntro": "<h3>Jouw persoonlijke thema</h3>Activeer je favorite lagen van alle andere themas",
"reload": "Herlaad de data" "reload": "Herlaad de data"
}, },
"favouritePoi": {
"button": {
"isMarkedShort": "Als favoriet gemarkeerd",
"isNotMarkedShort": "Niet als favoriet gemarkeerd"
}
},
"flyer": { "flyer": {
"aerial": "Deze kaart gebruikt luchtfoto's van het Agentschap Informatie Vlaanderen als achtergrond.\nOok het GRB is beschikbaar als achtergrondlaag.", "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", "callToAction": "Probeer het uit op mapcomplete.org",
@ -162,6 +168,7 @@
}, },
"back": "Vorige", "back": "Vorige",
"backToIndex": "Keer terug naar het overzicht met alle thematische kaarten", "backToIndex": "Keer terug naar het overzicht met alle thematische kaarten",
"backToMap": "Ga terug naar de kaart",
"backgroundMap": "Selecteer een achtergrondlaag", "backgroundMap": "Selecteer een achtergrondlaag",
"backgroundSwitch": "Verander achtergrond", "backgroundSwitch": "Verander achtergrond",
"cancel": "Annuleren", "cancel": "Annuleren",
@ -200,6 +207,14 @@
"histogram": { "histogram": {
"error_loading": "Kan het histogram niet laden" "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": { "layerSelection": {
"title": "Selecteer lagen", "title": "Selecteer lagen",
"zoomInToSeeThisLayer": "Vergroot de kaart om deze laag te zien" "zoomInToSeeThisLayer": "Vergroot de kaart om deze laag te zien"
@ -245,11 +260,17 @@
"openTheMap": "Raadpleeg de kaart", "openTheMap": "Raadpleeg de kaart",
"openTheMapAtGeolocation": "Ga naar jouw locatie", "openTheMapAtGeolocation": "Ga naar jouw locatie",
"opening_hours": { "opening_hours": {
"all_days_from": "Elke dag geopend {ranges}",
"closed_permanently": "Gesloten voor onbepaalde tijd", "closed_permanently": "Gesloten voor onbepaalde tijd",
"closed_until": "Gesloten - open op {date}", "closed_until": "Gesloten - open op {date}",
"error": "Kan de openingsuren niet inlezen",
"error_loading": "Sorry, deze openingsuren kunnen niet getoond worden", "error_loading": "Sorry, deze openingsuren kunnen niet getoond worden",
"friday": "Op vrijdag {ranges}",
"loadingCountry": "Het land wordt nog bepaald…", "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:", "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", "openTill": "tot",
"open_24_7": "Dag en nacht open", "open_24_7": "Dag en nacht open",
"open_during_ph": "Op een feestdag is dit", "open_during_ph": "Op een feestdag is dit",
@ -257,7 +278,15 @@
"ph_closed": "gesloten", "ph_closed": "gesloten",
"ph_not_known": " ", "ph_not_known": " ",
"ph_open": "open", "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", "osmLinkTooltip": "Bekijk dit object op OpenStreetMap om de geschiedenis te zien en meer te kunnen aanpassen",
"pdf": { "pdf": {
@ -300,6 +329,7 @@
"searchShort": "Zoek…", "searchShort": "Zoek…",
"searching": "Aan het zoeken…" "searching": "Aan het zoeken…"
}, },
"share": "Deel deze locatie",
"sharescreen": { "sharescreen": {
"copiedToClipboard": "Link gekopieerd naar klembord", "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", "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", "useSearch": "Gebruik de zoekfunctie hierboven om meer opties te zien",
"useSearchForMore": "Gebruik de zoekfunctie om {total} meer waarden te vinden…", "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": { "weekdays": {
"abbreviations": { "abbreviations": {
"friday": "Vrij", "friday": "Vrij",

View file

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

View file

@ -1,6 +1,7 @@
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import { BBox } from "../BBox" import { BBox } from "../BBox"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { FeatureCollection } from "geojson"
export interface GeoCodeResult { export interface GeoCodeResult {
display_name: string display_name: string
@ -20,12 +21,21 @@ export class Geocoding {
static async Search(query: string, bbox: BBox): Promise<GeoCodeResult[]> { static async Search(query: string, bbox: BBox): Promise<GeoCodeResult[]> {
const b = bbox ?? BBox.global const b = bbox ?? BBox.global
const url = const url = `${
Geocoding.host + Geocoding.host
"format=json&limit=1&viewbox=" + }search?format=json&limit=1&viewbox=${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}&accept-language=nl&q=${query}`
`${b.getEast()},${b.getNorth()},${b.getWest()},${b.getSouth()}` + return Utils.downloadJson(url)
"&accept-language=nl&q=" + }
query
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) return Utils.downloadJson(url)
} }
} }

View file

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

View file

@ -279,6 +279,13 @@ export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJs
*/ */
questionHint?: Translatable 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 * 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 multiAnswer: boolean
public readonly mappings?: Mapping[] public readonly mappings?: Mapping[]
public readonly editButtonAriaLabel?: Translation
public readonly labels: string[] public readonly labels: string[]
public readonly classes: string[] public readonly classes: string[]
@ -134,6 +135,11 @@ export default class TagRenderingConfig {
this.question = Translations.T(json.question, translationKey + ".question") this.question = Translations.T(json.question, translationKey + ".question")
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint") this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
this.description = Translations.T(json.description, translationKey + ".description") 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.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`)
this.invalidValues = json["invalidValues"] this.invalidValues = json["invalidValues"]
? TagUtils.Tag(json["invalidValues"], `${context}.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 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 { Orientation } from "../Sensors/Orientation"
/** /**
* *
@ -115,8 +116,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
readonly geolocation: GeoLocationHandler readonly geolocation: GeoLocationHandler
readonly geolocationControl: GeolocationControlState readonly geolocationControl: GeolocationControlState
readonly lastGeolocationRequestMoment: UIEventSource<Date> = new UIEventSource<Date>(undefined)
readonly imageUploadManager: ImageUploadManager readonly imageUploadManager: ImageUploadManager
readonly previewedImage = new UIEventSource<ProvidedImage>(undefined) 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) public arrowDirection: UIEventSource<number> = new UIEventSource(undefined)
private _measurementsStarted = false private _measurementsStarted = false
private _animateFakeMeasurements = false
constructor() {} constructor() {
// this.fakeMeasurements(true)
}
// noinspection JSUnusedGlobalSymbols
public fakeMeasurements(rotateAlpha: boolean = true) { public fakeMeasurements(rotateAlpha: boolean = true) {
console.log("Starting fake measurements of orientation sensors", { console.log("Starting fake measurements of orientation sensors", {
alpha: this.alpha, alpha: this.alpha,
@ -41,10 +45,15 @@ export class Orientation {
absolute: this.absolute, absolute: this.absolute,
}) })
this.alpha.setData(45) this.alpha.setData(45)
if (rotateAlpha) { if (rotateAlpha) {
Stores.Chronic(25).addCallback((date) => this._animateFakeMeasurements = true
this.alpha.setData(-(date.getTime() / 10) % 360) Stores.Chronic(25).addCallback((date) => {
) this.alpha.setData((date.getTime() / 100) % 360)
if (!this._animateFakeMeasurements) {
return true
}
})
} }
this.beta.setData(20) this.beta.setData(20)
this.gamma.setData(30) 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"> <script lang="ts">
import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import type { Feature } from "geojson" import type { Feature } from "geojson"
import ToSvelte from "../Base/ToSvelte.svelte"
import Svg from "../../Svg.js"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Loading from "../Base/Loading.svelte" import Loading from "../Base/Loading.svelte"
import Hotkeys from "../Base/Hotkeys" import Hotkeys from "../Base/Hotkeys"
@ -23,7 +21,7 @@
onDestroy( onDestroy(
triggerSearch.addCallback((_) => { triggerSearch.addCallback((_) => {
performSearch() performSearch()
}) }),
) )
let isRunning: boolean = false let isRunning: boolean = false
@ -31,15 +29,17 @@
let inputElement: HTMLInputElement let inputElement: HTMLInputElement
let feedback: string = undefined let feedback: string = undefined
function focusOnSearch() {
requestAnimationFrame(() => {
inputElement?.focus()
inputElement?.select()
})
}
Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => { Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => {
feedback = undefined feedback = undefined
requestAnimationFrame(() => { focusOnSearch()
inputElement?.focus()
inputElement?.select()
})
}) })
const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>() const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>()
@ -62,6 +62,7 @@
const result = await Geocoding.Search(searchContents, bounds.data) const result = await Geocoding.Search(searchContents, bounds.data)
if (result.length == 0) { if (result.length == 0) {
feedback = Translations.t.general.search.nothing.txt feedback = Translations.t.general.search.nothing.txt
focusOnSearch()
return return
} }
const poi = result[0] const poi = result[0]
@ -70,7 +71,7 @@
new BBox([ new BBox([
[lon0, lat0], [lon0, lat0],
[lon1, lat1], [lon1, lat1],
]).pad(0.01) ]).pad(0.01),
) )
if (perLayer !== undefined) { if (perLayer !== undefined) {
const id = poi.osm_type + "/" + poi.osm_id const id = poi.osm_type + "/" + poi.osm_id
@ -78,11 +79,11 @@
for (const layer of layers) { for (const layer of layers) {
const found = layer.features.data.find((f) => f.properties.id === id) const found = layer.features.data.find((f) => f.properties.id === id)
if (found === undefined) { if (found === undefined) {
continue; continue
} }
selectedElement?.setData(found); selectedElement?.setData(found)
console.log("Found an element that probably matches:", selectedElement?.data); console.log("Found an element that probably matches:", selectedElement?.data)
break; break
} }
} }
if (clearAfterView) { if (clearAfterView) {
@ -93,6 +94,7 @@
} catch (e) { } catch (e) {
console.error(e) console.error(e)
feedback = Translations.t.general.search.error.txt feedback = Translations.t.general.search.error.txt
focusOnSearch()
} finally { } finally {
isRunning = false isRunning = false
} }
@ -100,23 +102,25 @@
</script> </script>
<div class="normal-background flex justify-between rounded-full pl-2"> <div class="normal-background flex justify-between rounded-full pl-2">
<form class="w-full"> <form class="w-full flex flex-wrap">
{#if isRunning} {#if isRunning}
<Loading>{Translations.t.general.search.searching}</Loading> <Loading>{Translations.t.general.search.searching}</Loading>
{:else if feedback !== undefined}
<div class="alert" on:click={() => (feedback = undefined)}>
{feedback}
</div>
{:else} {:else}
<input <input
type="search" type="search"
class="w-full" class="w-full"
bind:this={inputElement} 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} bind:value={searchContents}
use:placeholder={Translations.t.general.search.search} 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} {/if}
</form> </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> </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> </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} {#if i !== undefined}
<span class="font-bold">{i + 1}.</span> <span class="font-bold">{i + 1}.</span>
{/if} {/if}
<TagRenderingAnswer config={layer.title} {layer} selectedElement={feature} {state} {tags} /> <TagRenderingAnswer config={layer.title} {layer} selectedElement={feature} {state} {tags} />
<span class="flex">{$bearingAndDist.dist}m {$bearingAndDist.bearing}°</span> {$bearingAndDist.dist}m {$bearingAndDist.bearing}°
</button> </span>
</div>

View file

@ -19,7 +19,7 @@
}) })
lastAction.stabilized(750).addCallbackAndRunD(_ => lastAction.setData(undefined)) lastAction.stabilized(750).addCallbackAndRunD(_ => lastAction.setData(undefined))
</script> </script>
<div aria-live="assertive" class=" interactive p-1" role="alert"> <div aria-live="assertive" class="p-1" role="alert">
{#if $lastAction !== undefined} {#if $lastAction !== undefined}
<Tr t={Translations.t.general.visualFeedback[$lastAction.key]} /> <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 MaplibreMap from "./MaplibreMap.svelte"
import { RasterLayerProperties } from "../../Models/RasterLayerProperties" import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
import * as htmltoimage from "html-to-image" import * as htmltoimage from "html-to-image"
import { ALL } from "node:dns"
/** /**
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` * 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 lastClickLocation: Store<undefined | { lon: number; lat: number }>
readonly minzoom: UIEventSource<number> readonly minzoom: UIEventSource<number>
readonly maxzoom: 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 * 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.allowRotating = state?.allowRotating ?? new UIEventSource<boolean>(true)
this.allowZooming = state?.allowZooming ?? new UIEventSource(true) this.allowZooming = state?.allowZooming ?? new UIEventSource(true)
this.bounds = state?.bounds ?? new UIEventSource(undefined) this.bounds = state?.bounds ?? new UIEventSource(undefined)
this.rotation = state?.rotation ?? new UIEventSource<number>(0)
this.rasterLayer = this.rasterLayer =
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined) state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
@ -121,6 +123,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setMinzoom(self.minzoom.data) self.setMinzoom(self.minzoom.data)
self.setMaxzoom(self.maxzoom.data) self.setMaxzoom(self.maxzoom.data)
self.setBounds(self.bounds.data) self.setBounds(self.bounds.data)
self.SetRotation(self.rotation.data)
self.setBackground() self.setBackground()
this.updateStores(true) this.updateStores(true)
map.on("moveend", () => this.updateStores()) map.on("moveend", () => this.updateStores())
@ -133,6 +136,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.on("dblclick", (e) => { map.on("dblclick", (e) => {
handleClick(e) handleClick(e)
}) })
map.on("rotateend", (e) => {
this.updateStores()
})
map.getContainer().addEventListener("keydown", (event) => { map.getContainer().addEventListener("keydown", (event) => {
let locked: "islocked" = undefined let locked: "islocked" = undefined
if (!this.allowMoving.data) { if (!this.allowMoving.data) {
@ -169,12 +175,12 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
console.error("Could not set background") console.error("Could not set background")
}) })
) )
this.location.addCallbackAndRunD((loc) => { this.location.addCallbackAndRunD((loc) => {
self.MoveMapToCurrentLoc(loc) self.MoveMapToCurrentLoc(loc)
}) })
this.zoom.addCallbackAndRunD((z) => self.SetZoom(z)) this.zoom.addCallbackAndRunD((z) => self.SetZoom(z))
this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox)) this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox))
this.rotation.addCallbackAndRunD((bearing) => self.SetRotation(bearing))
this.allowMoving.addCallbackAndRun((allowMoving) => { this.allowMoving.addCallbackAndRun((allowMoving) => {
self.setAllowMoving(allowMoving) self.setAllowMoving(allowMoving)
self.pingKeycodeEvent(allowMoving ? "unlocked" : "locked") self.pingKeycodeEvent(allowMoving ? "unlocked" : "locked")
@ -459,6 +465,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (this.bounds.data === undefined || !isSetup) { if (this.bounds.data === undefined || !isSetup) {
this.bounds.setData(bbox) this.bounds.setData(bbox)
} }
this.rotation.setData(map.getBearing())
} }
private SetZoom(z: number): void { 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 { private MoveMapToCurrentLoc(loc: { lat: number; lon: number }): void {
const map = this._maplibreMap.data const map = this._maplibreMap.data
if (!map || loc === undefined) { if (!map || loc === undefined) {

View file

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

View file

@ -38,7 +38,7 @@
let htmlElem: HTMLDivElement 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 // 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! // Make sure that the question is in the scrollview!
@ -108,7 +108,8 @@
editMode = true editMode = true
}} }}
class="secondary h-8 w-8 shrink-0 self-start rounded-full p-1" 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 /> <PencilAltIcon />
</button> </button>

View file

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

View file

@ -66,6 +66,8 @@
import { Orientation } from "../Sensors/Orientation" import { Orientation } from "../Sensors/Orientation"
import GeolocationControl from "./BigComponents/GeolocationControl.svelte" import GeolocationControl from "./BigComponents/GeolocationControl.svelte"
import Compass_arrow from "../assets/svg/Compass_arrow.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 export let state: ThemeViewState
let layout = state.layout let layout = state.layout
@ -183,6 +185,7 @@
<!-- Flex and w-full are needed for the positioning --> <!-- Flex and w-full are needed for the positioning -->
<!-- Centermessage --> <!-- Centermessage -->
<StateIndicator {state} /> <StateIndicator {state} />
<ReverseGeocoding mapProperties={mapproperties}/>
</div> </div>
</div> </div>
@ -270,7 +273,7 @@
</MapControlButton> </MapControlButton>
{#if $compassLoaded} {#if $compassLoaded}
<div class="absolute top-0 left-0 w-0 h-0 m-0.5 sm:m-1"> <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> </div>
{/if} {/if}
</div> </div>
@ -360,55 +363,39 @@
</div> </div>
<div class="flex" slot="title1"> <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}> <If condition={state.featureSwitches.featureSwitchEnableExport}>
<Download class="h-4 w-4" /> <Download class="h-4 w-4" />
<Tr t={Translations.t.general.download.title} /> <Tr t={Translations.t.general.download.title} />
</If> </If>
</div> </div>
<div class="m-4" slot="content2"> <div class="m-4" slot="content1">
<DownloadPanel {state} /> <DownloadPanel {state} />
</div> </div>
<div slot="title3"> <div slot="title2">
<Tr t={Translations.t.general.attribution.title} /> <Tr t={Translations.t.general.attribution.title} />
</div> </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" /> <Share class="h-4 w-4" />
<Tr t={Translations.t.general.sharescreen.title} /> <Tr t={Translations.t.general.sharescreen.title} />
</div> </div>
<div class="m-2" slot="content4"> <div class="m-2" slot="content3">
<ShareScreen {state} /> <ShareScreen {state} />
</div> </div>
</TabbedGroup> </TabbedGroup>
</FloatOver> </FloatOver>
</If> </If>
<If condition={state.guistate.filtersPanelIsOpened}>
<FloatOver on:close={() => state.guistate.filtersPanelIsOpened.setData(false)}>
<FilterPanel {state}/>
</FloatOver>
</If>
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}> <IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
<!-- background layer selector --> <!-- background layer selector -->
<FloatOver <FloatOver

View file

@ -1,4 +1,5 @@
import { Translation } from "../UI/i18n/Translation" import { Translation } from "../UI/i18n/Translation"
import Locale from "../UI/i18n/Locale"
export function ariaLabel(htmlElement: Element, t: Translation) { export function ariaLabel(htmlElement: Element, t: Translation) {
if (!t) { if (!t) {
@ -6,6 +7,19 @@ export function ariaLabel(htmlElement: Element, t: Translation) {
} }
let destroy: () => void = undefined 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( t.current.map(
(label) => { (label) => {
htmlElement.setAttribute("aria-label", label) htmlElement.setAttribute("aria-label", label)