diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 0618c5459..28c768a51 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -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", @@ -2215,7 +2227,7 @@ } } ] - }, + }, { "id": "internet-ssid", "labels": [ diff --git a/langs/en.json b/langs/en.json index aacb72dad..54d7581ab 100644 --- a/langs/en.json +++ b/langs/en.json @@ -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", diff --git a/langs/nl.json b/langs/nl.json index 178893e8d..ba84f8cc4 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -50,6 +50,12 @@ "panelIntro": "

Jouw persoonlijke thema

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": "

Plaats dit op je website

Voeg dit kaartje toe op je eigen website.
We moedigen dit zelfs aan - je hoeft geen toestemming te vragen.
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", diff --git a/package.json b/package.json index 0d6bd5f6b..f60304fc5 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Logic/Osm/Geocoding.ts b/src/Logic/Osm/Geocoding.ts index d3af5d6a5..0818c792f 100644 --- a/src/Logic/Osm/Geocoding.ts +++ b/src/Logic/Osm/Geocoding.ts @@ -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 { 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 { + // 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) } } diff --git a/src/Models/MapProperties.ts b/src/Models/MapProperties.ts index 8e905155a..73ec1b413 100644 --- a/src/Models/MapProperties.ts +++ b/src/Models/MapProperties.ts @@ -16,6 +16,7 @@ export interface MapProperties { readonly maxbounds: UIEventSource readonly allowMoving: UIEventSource readonly allowRotating: UIEventSource + readonly rotation: UIEventSource readonly lastClickLocation: Store<{ lon: number; lat: number }> readonly allowZooming: UIEventSource diff --git a/src/Models/MenuState.ts b/src/Models/MenuState.ts index a64c05a0a..58c2c356a 100644 --- a/src/Models/MenuState.ts +++ b/src/Models/MenuState.ts @@ -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 = new UIEventSource(false) + public readonly filtersPanelIsOpened: UIEventSource = new UIEventSource(false) + public readonly allToggles: { toggle: UIEventSource 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)) diff --git a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts index 85eb7c2c3..241d24c67 100644 --- a/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts +++ b/src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson.ts @@ -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 */ diff --git a/src/Models/ThemeConfig/TagRenderingConfig.ts b/src/Models/ThemeConfig/TagRenderingConfig.ts index 17c840489..a7c445706 100644 --- a/src/Models/ThemeConfig/TagRenderingConfig.ts +++ b/src/Models/ThemeConfig/TagRenderingConfig.ts @@ -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`) diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 24e399797..4d757889d 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -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 = new UIEventSource(undefined) - readonly imageUploadManager: ImageUploadManager readonly previewedImage = new UIEventSource(undefined) diff --git a/src/Sensors/Motion.ts b/src/Sensors/Motion.ts new file mode 100644 index 000000000..bedd2dbb4 --- /dev/null +++ b/src/Sensors/Motion.ts @@ -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(0) + + public lastShakeEvent = new UIEventSource(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)) + } +} diff --git a/src/Sensors/Orientation.ts b/src/Sensors/Orientation.ts index 149fdfeb8..df789a2c8 100644 --- a/src/Sensors/Orientation.ts +++ b/src/Sensors/Orientation.ts @@ -30,9 +30,13 @@ export class Orientation { */ public arrowDirection: UIEventSource = 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) diff --git a/src/UI/BigComponents/FilterPanel.svelte b/src/UI/BigComponents/FilterPanel.svelte new file mode 100644 index 000000000..5799d39cb --- /dev/null +++ b/src/UI/BigComponents/FilterPanel.svelte @@ -0,0 +1,39 @@ + + +
+ +

+ + +

+ + {#each layout.layers as layer} + + {/each} + {#each layout.tileLayerSources as tilesource} + + {/each} +
diff --git a/src/UI/BigComponents/Geosearch.svelte b/src/UI/BigComponents/Geosearch.svelte index b8fde0230..dd8f34b5c 100644 --- a/src/UI/BigComponents/Geosearch.svelte +++ b/src/UI/BigComponents/Geosearch.svelte @@ -1,8 +1,6 @@
-
+ {#if isRunning} {Translations.t.general.search.searching} - {:else if feedback !== undefined} -
(feedback = undefined)}> - {feedback} -
{:else} (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} + + + {/if} {/if}
-
diff --git a/src/UI/BigComponents/ReverseGeocoding.svelte b/src/UI/BigComponents/ReverseGeocoding.svelte new file mode 100644 index 000000000..50f9c3b72 --- /dev/null +++ b/src/UI/BigComponents/ReverseGeocoding.svelte @@ -0,0 +1,45 @@ + + +{#if currentLocation} + +{/if} diff --git a/src/UI/BigComponents/Summary.svelte b/src/UI/BigComponents/Summary.svelte index 37282330d..1e66f81b3 100644 --- a/src/UI/BigComponents/Summary.svelte +++ b/src/UI/BigComponents/Summary.svelte @@ -30,10 +30,12 @@ ) - + + {$bearingAndDist.dist}m {$bearingAndDist.bearing}° + + diff --git a/src/UI/BigComponents/VisualFeedbackPanel.svelte b/src/UI/BigComponents/VisualFeedbackPanel.svelte index 0d0242ca4..c718b599e 100644 --- a/src/UI/BigComponents/VisualFeedbackPanel.svelte +++ b/src/UI/BigComponents/VisualFeedbackPanel.svelte @@ -19,7 +19,7 @@ }) lastAction.stabilized(750).addCallbackAndRunD(_ => lastAction.setData(undefined)) - @@ -270,7 +273,7 @@ {#if $compassLoaded} {/if} @@ -360,55 +363,39 @@
- - -
- -
- {#each layout.layers as layer} - - {/each} - {#each layout.tileLayerSources as tilesource} - - {/each} -
- -
-
+
-
+
- new CopyrightPanel(state)} slot="content3" /> + new CopyrightPanel(state)} slot="content2" /> -
+
-
+
+ + state.guistate.filtersPanelIsOpened.setData(false)}> + + + + + 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)