From 7a3cb9fbdd4663a50e1b29b01caa8f8fc78f5972 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 16 Dec 2023 01:29:42 +0100 Subject: [PATCH] Improve 'slopes' input, add compass indicator --- langs/en.json | 16 +- src/Logic/Osm/OsmConnection.ts | 3 + src/Logic/Web/Orientation.ts | 80 ++++++++++ src/Models/ThemeViewState.ts | 7 + src/UI/Base/MapControlButton.svelte | 4 +- src/UI/BigComponents/GeolocationControl.ts | 4 +- src/UI/BigComponents/Summary.svelte | 15 +- src/UI/Debug/OrientationDebugPanel.svelte | 22 +++ src/UI/InputElement/Helpers/SlopeInput.svelte | 149 ++++++++---------- src/UI/InputElement/InputHelper.svelte | 8 +- .../InputElement/Validators/SlopeValidator.ts | 9 +- src/UI/Map/MaplibreMap.svelte | 1 + .../Popup/TagRendering/FreeformInput.svelte | 3 + .../TagRendering/TagRenderingQuestion.svelte | 3 + src/UI/SpecialVisualizations.ts | 12 +- src/UI/Test.svelte | 7 +- src/UI/ThemeViewGUI.svelte | 33 +++- 17 files changed, 268 insertions(+), 108 deletions(-) create mode 100644 src/Logic/Web/Orientation.ts create mode 100644 src/UI/Debug/OrientationDebugPanel.svelte diff --git a/langs/en.json b/langs/en.json index 2a7634cfdf..aacb72dad1 100644 --- a/langs/en.json +++ b/langs/en.json @@ -281,11 +281,17 @@ "openTheMap": "Open the map", "openTheMapAtGeolocation": "Zoom to your location", "opening_hours": { + "all_days_from": "Opened every day {ranges}", "closed_permanently": "Closed for an unknown duration", "closed_until": "Closed until {date}", + "error": "Could not parse the opening hours", "error_loading": "Error: could not visualize these opening hours.", + "friday": "On friday {ranges}", "loadingCountry": "Determining country…", + "monday": "On monday {ranges}", "not_all_rules_parsed": "These opening hours are complicated. The following rules are ignored in the input element:", + "on_weekdays": "Opened on weekdays {ranges}", + "on_weekends": "Opened on weekends {ranges}", "openTill": "till", "open_24_7": "Open around the clock", "open_during_ph": "During a public holiday, this is", @@ -293,7 +299,15 @@ "ph_closed": "closed", "ph_not_known": " ", "ph_open": "open", - "ph_open_as_usual": "open, as usual" + "ph_open_as_usual": "open, as usual", + "ranges": "from {starttime} till {endtime}", + "rangescombined": "{range0} and {range1}", + "saturday": "On saturday {ranges}", + "sunday": "On sunday {ranges}", + "thursday": "On thursday {ranges}", + "tuesday": "On tuesday {ranges}", + "unknown": "The opening hours are unkown", + "wednesday": "On wednesday {ranges}" }, "osmLinkTooltip": "Browse this object on OpenStreetMap for history and more editing options", "pdf": { diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index b6ed467b22..1c92f07e06 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -551,6 +551,9 @@ export class OsmConnection { private UpdateCapabilities(): void { const self = this + if (this.fakeUser) { + return + } this.FetchCapabilities().then(({ api, gpx }) => { self.apiIsOnline.setData(api) self.gpxServiceIsOnline.setData(gpx) diff --git a/src/Logic/Web/Orientation.ts b/src/Logic/Web/Orientation.ts new file mode 100644 index 0000000000..81b7dad7c8 --- /dev/null +++ b/src/Logic/Web/Orientation.ts @@ -0,0 +1,80 @@ +import { UIEventSource } from "../UIEventSource" + +/** + * Exports the device orientation as UIEventSources and detects 'shakes' + */ +export class Orientation { + public static singleton = new Orientation() + + public gotMeasurement: UIEventSource = new UIEventSource(false) + /** + * The direction wrt to the magnetic north, with clockwise = positive. + * 0 degrees is pointing towards the north + * 90° is east, + * 180° is south + * 270° is west + * + * Note that this is the opposite of what the DeviceOrientationEvent uses! + * */ + public alpha: UIEventSource = new UIEventSource(undefined) + public beta: UIEventSource = new UIEventSource(undefined) + public gamma: UIEventSource = new UIEventSource(undefined) + /** + * Indicates if 'alpha' is with the actual magnetic field or just mimicks that + */ + public absolute: UIEventSource = new UIEventSource(undefined) + /** + * A derivate of beta and gamma + * An arrow pointing up, rotated with this amount should more or less point towards the sky + * Used in the slope input + */ + public arrowDirection: UIEventSource = new UIEventSource(undefined) + private _measurementsStarted = false + + constructor() { + this.fakeMeasurements() + } + + public fakeMeasurements() { + this.alpha.setData(45) + this.beta.setData(20) + this.gamma.setData(30) + this.absolute.setData(true) + this.gotMeasurement.setData(true) + } + + public startMeasurements() { + if (this._measurementsStarted) { + return + } + this._measurementsStarted = true + console.log("Starting device orientation listener") + try { + window.addEventListener("deviceorientationabsolute", (e: DeviceOrientationEvent) => + this.update(e) + ) + } catch (e) { + console.log("Could not init device orientation api due to", e) + } + } + + private update(event: DeviceOrientationEvent) { + this.gotMeasurement.setData(true) + // IF the phone is lying flat, then: + // alpha is the compass direction (but not absolute) + // beta is tilt if you would lift the phone towards you + // gamma is rotation if you rotate the phone along the long axis + + // Note: the event uses _counterclockwise_ = positive for alpha + // However, we use _clockwise_ = positive throughout the application, so we use '-' here! + this.alpha.setData(Math.floor(360 - event.alpha)) + this.beta.setData(Math.floor(event.beta)) + this.gamma.setData(Math.floor(event.gamma)) + this.absolute.setData(event.absolute) + if (this.beta.data < 0) { + this.arrowDirection.setData(this.gamma.data + 180) + } else { + this.arrowDirection.setData(-this.gamma.data) + } + } +} diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 9fdf646188..087f43d489 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -518,6 +518,13 @@ export default class ThemeViewState implements SpecialVisualizationState { if (this.selectedElement.data !== undefined) { return false } + if ( + this.guistate.menuIsOpened.data || + this.guistate.themeIsOpened.data || + this.previewedImage.data !== undefined + ) { + return + } this.selectClosestAtCenter(0) } ) diff --git a/src/UI/Base/MapControlButton.svelte b/src/UI/Base/MapControlButton.svelte index 64f8a575aa..ec36f102e2 100644 --- a/src/UI/Base/MapControlButton.svelte +++ b/src/UI/Base/MapControlButton.svelte @@ -8,7 +8,7 @@ * A round button with an icon and possible a small text, which hovers above the map */ const dispatch = createEventDispatcher() - export let cls = "" + export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1" export let arialabel: Translation = undefined @@ -16,7 +16,7 @@ on:click={(e) => dispatch("click", e)} on:keydown use:ariaLabel={arialabel} - class={twJoin("pointer-events-auto m-0.5 h-fit w-fit rounded-full p-0.5 sm:p-1 md:m-1", cls)} + class={twJoin("pointer-events-auto h-fit w-fit rounded-full", cls)} > diff --git a/src/UI/BigComponents/GeolocationControl.ts b/src/UI/BigComponents/GeolocationControl.ts index 8079d86a2a..0ca17ed224 100644 --- a/src/UI/BigComponents/GeolocationControl.ts +++ b/src/UI/BigComponents/GeolocationControl.ts @@ -92,9 +92,9 @@ export class GeolocationControl extends VariableUiElement { this._lastClickWithinThreeSecs = lastClickWithinThreeSecs this.onClick(() => this.handleClick()) - Hotkeys.RegisterHotkey({ nomod: "L" }, Translations.t.hotkeyDocumentation.geolocate, () => + Hotkeys.RegisterHotkey({ nomod: "L" }, Translations.t.hotkeyDocumentation.geolocate, () => { this.handleClick() - ) + }) lastClick.addCallbackAndRunD((_) => { window.setTimeout(() => { diff --git a/src/UI/BigComponents/Summary.svelte b/src/UI/BigComponents/Summary.svelte index 9b78d8690f..37282330d7 100644 --- a/src/UI/BigComponents/Summary.svelte +++ b/src/UI/BigComponents/Summary.svelte @@ -3,6 +3,8 @@ import type { SpecialVisualizationState } from "../SpecialVisualization" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte" + import { GeoOperations } from "../../Logic/GeoOperations" + import { Store } from "../../Logic/UIEventSource" export let state: SpecialVisualizationState export let feature: Feature @@ -16,11 +18,22 @@ state.selectedLayer.setData(layer) state.selectedElement.setData(feature) } + + let bearingAndDist: Store<{ bearing: number, dist: number }> = state.mapProperties.location.map(l => { + let fcenter = GeoOperations.centerpointCoordinates(feature) + let mapCenter = [l.lon, l.lat] + + let bearing = Math.round(GeoOperations.bearing(fcenter, mapCenter)) + let dist = Math.round(GeoOperations.distanceBetween(fcenter, mapCenter)) + return { bearing, dist } + }, + ) - diff --git a/src/UI/Debug/OrientationDebugPanel.svelte b/src/UI/Debug/OrientationDebugPanel.svelte new file mode 100644 index 0000000000..c935a3fae9 --- /dev/null +++ b/src/UI/Debug/OrientationDebugPanel.svelte @@ -0,0 +1,22 @@ + +{#if !$gotMeasurement} + No device orientation data available +{:else} + Device orientation data: +
    +
  1. Alpha: {$alpha}
  2. +
  3. Beta: {$beta}
  4. +
  5. Gamma: {$gamma}
  6. +
  7. Absolute?: {$absolute}
  8. +
+{/if} diff --git a/src/UI/InputElement/Helpers/SlopeInput.svelte b/src/UI/InputElement/Helpers/SlopeInput.svelte index ca21c21a70..835baddccd 100644 --- a/src/UI/InputElement/Helpers/SlopeInput.svelte +++ b/src/UI/InputElement/Helpers/SlopeInput.svelte @@ -1,112 +1,92 @@ {#if $gotMeasurement}
-
-
-
-
-
- +
+
+ {$previewDegrees} +
+
+ {$previewPercentage}
-
- -
- {#if $value} -
{previewMode = oppMode(previewMode)}}> - {$preview} -
- {:else} - - {/if}
@@ -114,5 +94,16 @@
+ + + + Way: {featureBearing}°, compass: {$alpha}°, diff: {(featureBearing - $alpha)} + {#if $valuesign === 1} + Forward + {:else} + Backward + {/if} + +
{/if} diff --git a/src/UI/InputElement/InputHelper.svelte b/src/UI/InputElement/InputHelper.svelte index 154149b492..78cd48350f 100644 --- a/src/UI/InputElement/InputHelper.svelte +++ b/src/UI/InputElement/InputHelper.svelte @@ -9,7 +9,6 @@ import InputHelpers from "./InputHelpers" import ToSvelte from "../Base/ToSvelte.svelte" import type { Feature } from "geojson" - import { createEventDispatcher } from "svelte" import ImageHelper from "./Helpers/ImageHelper.svelte" import TranslationInput from "./Helpers/TranslationInput.svelte" import TagInput from "./Helpers/TagInput.svelte" @@ -19,17 +18,16 @@ import ColorInput from "./Helpers/ColorInput.svelte" import OpeningHoursInput from "./Helpers/OpeningHoursInput.svelte" import SlopeInput from "./Helpers/SlopeInput.svelte" + import type { SpecialVisualizationState } from "../SpecialVisualization" export let type: ValidatorType export let value: UIEventSource export let feature: Feature export let args: (string | number | boolean)[] = undefined + export let state: SpecialVisualizationState let properties = { feature, args: args ?? [] } - let dispatch = createEventDispatcher<{ - selected - }>() {#if type === "translation"} @@ -49,7 +47,7 @@ {:else if type === "opening_hours"} {:else if type === "slope"} - + {:else if type === "wikidata"} InputHelpers.constructWikidataHelper(value, properties)} /> {/if} diff --git a/src/UI/InputElement/Validators/SlopeValidator.ts b/src/UI/InputElement/Validators/SlopeValidator.ts index fa3559cff4..69cb6784e7 100644 --- a/src/UI/InputElement/Validators/SlopeValidator.ts +++ b/src/UI/InputElement/Validators/SlopeValidator.ts @@ -1,8 +1,13 @@ import NatValidator from "./NatValidator" +import FloatValidator from "./FloatValidator" -export default class SlopeValidator extends NatValidator { +export default class SlopeValidator extends FloatValidator { constructor() { - super("slope", "Validates that the slope is a valid number") + super( + "slope", + "Validates that the slope is a valid number." + + "The accompanying input element uses the gyroscope and the compass to determine the correct incline. The sign of the incline will be set automatically. The bearing of the way is compared to the bearing of the compass, as such, the device knows if it is measuring in the forward or backward direction." + ) } isValid(str: string): boolean { if (str.endsWith("%") || str.endsWith("°")) { diff --git a/src/UI/Map/MaplibreMap.svelte b/src/UI/Map/MaplibreMap.svelte index 2af39417c0..c529daca49 100644 --- a/src/UI/Map/MaplibreMap.svelte +++ b/src/UI/Map/MaplibreMap.svelte @@ -53,6 +53,7 @@ const canvas = _map.getCanvas() ariaLabel(canvas, Translations.t.general.visualFeedback.navigation) canvas.role="application" + canvas.tabIndex = 0 }) map.set(_map) }) diff --git a/src/UI/Popup/TagRendering/FreeformInput.svelte b/src/UI/Popup/TagRendering/FreeformInput.svelte index f15639cd0a..aeaf2ffd07 100644 --- a/src/UI/Popup/TagRendering/FreeformInput.svelte +++ b/src/UI/Popup/TagRendering/FreeformInput.svelte @@ -9,12 +9,14 @@ import type { Feature } from "geojson" import { Unit } from "../../../Models/Unit" import InputHelpers from "../../InputElement/InputHelpers" + import type { SpecialVisualizationState } from "../../SpecialVisualization" export let value: UIEventSource export let config: TagRenderingConfig export let tags: UIEventSource> export let feature: Feature = undefined + export let state: SpecialVisualizationState export let unit: Unit | undefined let placeholder = config.freeform?.placeholder @@ -70,6 +72,7 @@ {feature} type={config.freeform.type} {value} + {state} on:submit />
diff --git a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte index ecda2e8508..3631eee074 100644 --- a/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte +++ b/src/UI/Popup/TagRendering/TagRenderingQuestion.svelte @@ -254,6 +254,7 @@ {tags} {feedback} {unit} + {state} feature={selectedElement} value={freeformInput} on:submit={onSave} @@ -296,6 +297,7 @@ {tags} {feedback} {unit} + {state} feature={selectedElement} value={freeformInput} on:selected={() => (selectedMapping = config.mappings?.length)} @@ -338,6 +340,7 @@ {tags} {feedback} {unit} + {state} feature={selectedElement} value={freeformInput} on:submit={onSave} diff --git a/src/UI/SpecialVisualizations.ts b/src/UI/SpecialVisualizations.ts index e8d98e71eb..258eb621ed 100644 --- a/src/UI/SpecialVisualizations.ts +++ b/src/UI/SpecialVisualizations.ts @@ -83,6 +83,7 @@ import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte" import MoveWizard from "./Popup/MoveWizard.svelte" import { Unit } from "../Models/Unit" import Link from "./Base/Link.svelte" +import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte" class NearbyImageVis implements SpecialVisualization { // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests @@ -1536,10 +1537,13 @@ export default class SpecialVisualizations { )).geolocation.currentUserLocation.features.map( (features) => features[0]?.properties ) - return new SvelteUIElement(AllTagsPanel, { - state, - tags, - }) + return new Combine([ + new SvelteUIElement(OrientationDebugPanel, {}), + new SvelteUIElement(AllTagsPanel, { + state, + tags, + }), + ]) }, }, { diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 8d1c7b4d94..db8bcf84f3 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -2,10 +2,9 @@ // Testing grounds import { UIEventSource } from "../Logic/UIEventSource" import SlopeInput from "./InputElement/Helpers/SlopeInput.svelte" + import OrientationDebugPanel from "./Debug/OrientationDebugPanel.svelte" let value: UIEventSource = new UIEventSource(undefined) -
-
Value: {$value}
-
- + + diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index ac66110f9b..029ae7ab3a 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -64,12 +64,15 @@ import Favourites from "./Favourites/Favourites.svelte" import ImageOperations from "./Image/ImageOperations.svelte" import VisualFeedbackPanel from "./BigComponents/VisualFeedbackPanel.svelte" + import { Orientation } from "../Logic/Web/Orientation" export let state: ThemeViewState let layout = state.layout let maplibremap: UIEventSource = state.map let selectedElement: UIEventSource = new UIEventSource(undefined) + let compass = Orientation.singleton.alpha + let compassLoaded = Orientation.singleton.gotMeasurement state.selectedElement.addCallback(selected => { if (!selected) { selectedElement.setData(selected) @@ -258,15 +261,29 @@ - geolocationControl.handleClick()} - on:keydown={forwardEventToMap} - > - - +
+ {#if $compassLoaded} + +