A11y: various improvements
This commit is contained in:
parent
0d4f2c9c36
commit
5fa2ddd9c1
23 changed files with 327 additions and 98 deletions
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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`)
|
||||
|
|
|
@ -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
33
src/Sensors/Motion.ts
Normal 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))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
39
src/UI/BigComponents/FilterPanel.svelte
Normal file
39
src/UI/BigComponents/FilterPanel.svelte
Normal 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>
|
|
@ -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>
|
||||
|
|
45
src/UI/BigComponents/ReverseGeocoding.svelte
Normal file
45
src/UI/BigComponents/ReverseGeocoding.svelte
Normal 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}
|
|
@ -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>
|
||||
|
|
|
@ -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]} />
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]) })
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue