diff --git a/langs/en.json b/langs/en.json index 2a7634cfd..aacb72dad 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 b6ed467b2..1c92f07e0 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 000000000..81b7dad7c --- /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 9fdf64618..087f43d48 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 64f8a575a..ec36f102e 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 8079d86a2..0ca17ed22 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 9b78d8690..37282330d 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 000000000..c935a3fae --- /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 ca21c21a7..835baddcc 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 154149b49..78cd48350 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 fa3559cff..69cb6784e 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 2af39417c..c529daca4 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 f15639cd0..aeaf2ffd0 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 ecda2e850..3631eee07 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 e8d98e71e..258eb621e 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 8d1c7b4d9..db8bcf84f 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 ac66110f9..029ae7ab3 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} + +