From bd293e67394cb546899f0e65ec5fd64156d916dc Mon Sep 17 00:00:00 2001 From: slothful-vassal <89943360+slothful-vassal@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:15:43 +0100 Subject: [PATCH] new setting: begin drawing a new trail from current location (#592) * new setting: begin drawing a new trail from current location * apply only when drawing * fix application when editing an existing route * documentation * ignore pointer events on location circles * fix i18n --------- Co-authored-by: Flomp Co-authored-by: Christian Beutel <> --- db/migrations/1758392924_updated_settings.go | 41 ++++++++++++++++++ docs/src/content/docs/use/customize-map.md | 9 ++++ .../trail/map_with_elevation_maplibre.svelte | 30 +++++++++++-- web/src/lib/i18n/locales/de.json | 4 +- web/src/lib/i18n/locales/en.json | 4 +- web/src/lib/i18n/locales/es.json | 3 +- web/src/lib/i18n/locales/eu.json | 3 +- web/src/lib/i18n/locales/fr.json | 3 +- web/src/lib/i18n/locales/hu.json | 3 +- web/src/lib/i18n/locales/it.json | 3 +- web/src/lib/i18n/locales/nl.json | 3 +- web/src/lib/i18n/locales/pl.json | 3 +- web/src/lib/i18n/locales/pt.json | 3 +- web/src/lib/i18n/locales/ru.json | 3 +- web/src/lib/i18n/locales/zh.json | 3 +- web/src/lib/models/api/settings_schema.ts | 4 +- web/src/lib/models/settings.ts | 5 +++ web/src/routes/settings/map/+page.svelte | 42 ++++++++++++++++++- web/src/routes/trail/edit/[id]/+page.svelte | 1 + 19 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 db/migrations/1758392924_updated_settings.go diff --git a/db/migrations/1758392924_updated_settings.go b/db/migrations/1758392924_updated_settings.go new file mode 100644 index 00000000..a55a6a74 --- /dev/null +++ b/db/migrations/1758392924_updated_settings.go @@ -0,0 +1,41 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + // add field + if err := collection.Fields.AddMarshaledJSONAt(12, []byte(`{ + "hidden": false, + "id": "json1001103536", + "maxSize": 0, + "name": "behavior", + "presentable": false, + "required": false, + "system": false, + "type": "json" + }`)); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + // remove field + collection.Fields.RemoveById("json1001103536") + + return app.Save(collection) + }) +} diff --git a/docs/src/content/docs/use/customize-map.md b/docs/src/content/docs/use/customize-map.md index 2d184d2f..84fc5474 100644 --- a/docs/src/content/docs/use/customize-map.md +++ b/docs/src/content/docs/use/customize-map.md @@ -38,3 +38,12 @@ To add the respective URLs navigate to `Settings -> Display` and add them in the 2. Locate the compass control in the top-right corner of the map. 3. Click and drag the compass control to tilt the map into 3D mode. 4. Adjust the tilt and rotation as desired to view the terrain in 3D. + +## Route drawing behavior + +You can configure how new route drawing starts in `Settings -> Display`. + +- Enable `Begin drawing a new trail from your current location` to automatically center route drawing on your current GPS location. +- Disable it to start drawing at the current map view instead. + +This option only affects creating a **new** trail in the route editor. diff --git a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte index ce5a4003..7c862265 100644 --- a/web/src/lib/components/trail/map_with_elevation_maplibre.svelte +++ b/web/src/lib/components/trail/map_with_elevation_maplibre.svelte @@ -5,7 +5,6 @@ import type { Trail } from "$lib/models/trail"; import type { Waypoint } from "$lib/models/waypoint"; import { theme } from "$lib/stores/theme_store"; - import { fetchGPX } from "$lib/stores/trail_store"; import { findStartAndEndPoints } from "$lib/util/geojson_util"; import { createMarkerFromWaypoint, @@ -69,6 +68,7 @@ trail: Trail, ) => void; oninit?: (map: M.Map) => void; + autoGeolocateOnDrawing?: boolean; } let { @@ -99,6 +99,7 @@ onclick, onUnclusteredClick, oninit, + autoGeolocateOnDrawing = false, }: Props = $props(); let mapContainer: HTMLDivElement; @@ -592,6 +593,10 @@ if (trails[activeTrail]) { removeStartEndMarkers(trails[activeTrail].id); } + + if (autoGeolocateOnDrawing) { + geolocate(); + } } function stopDrawing() { @@ -731,6 +736,8 @@ } } + let geolocateControl : M.GeolocateControl; + onMount(async () => { const initialState = { lng: 0, @@ -800,8 +807,7 @@ "top-left", ); - map.addControl( - new M.GeolocateControl({ + geolocateControl = new M.GeolocateControl({ positionOptions: { enableHighAccuracy: true, }, @@ -809,7 +815,8 @@ animate: fitBounds == "animate", }, trackUserLocation: true, - })); + }); + map.addControl(geolocateControl); if (showStyleSwitcher) { map.addControl(switcherControl); @@ -901,6 +908,17 @@ showWaypoints(); }); + function geolocate() { + if (!page.data.settings?.behavior) return; + + if (page.data.settings.behavior.allowAutoGeolocate === true) { + if (geolocateControl._watchState === 'OFF') { + geolocateControl.options.trackUserLocation = true; + geolocateControl.trigger(); + } + } + } + onDestroy(() => { map?.remove(); }); @@ -982,4 +1000,8 @@ padding-bottom: 2.5px; @apply bg-menu-item-background-focus w-3 aspect-square rounded-full; } + + :global(.maplibregl-user-location-accuracy-circle, .maplibregl-user-location-dot) { + pointer-events: none; + } diff --git a/web/src/lib/i18n/locales/de.json b/web/src/lib/i18n/locales/de.json index 3c116469..4af76fe3 100644 --- a/web/src/lib/i18n/locales/de.json +++ b/web/src/lib/i18n/locales/de.json @@ -17,6 +17,7 @@ "added-trails-to": "Routen hinzugefügt zu", "after": "Nach", "all-activities": "Alle Aktivitäten", + "allow-auto-geolocate": "Beginne das Zeichnen einer neuen Route am aktuellen Standort", "alphabetical": "Alphabetisch", "already-account": "Du hast bereits ein Konto?", "altitude": "Höhe", @@ -35,6 +36,7 @@ "basic-info": "Basisinformation", "basque": "Baskisch", "before": "Vor", + "behavior": "Verhalten", "bicycle-parking": "Fahrrad-Parkplatz", "bicycle-rental": "Fahrradverleih", "bicycle-shop": "Fahrrad-Reparatur", @@ -215,8 +217,8 @@ "invalid-username": "Ungültiger Nutzername", "italian": "Italienisch", "joined": "Beigetreten", - "keep-private": "Ohne Veröffentlichung fortfahren", "keep-original": "", + "keep-private": "Ohne Veröffentlichung fortfahren", "language": "Sprache", "latitude": "Breitengrad", "layer": "{n, plural, =1 {Ebene} other {Ebenen}}", diff --git a/web/src/lib/i18n/locales/en.json b/web/src/lib/i18n/locales/en.json index 6e9bcca3..4e31c592 100644 --- a/web/src/lib/i18n/locales/en.json +++ b/web/src/lib/i18n/locales/en.json @@ -17,6 +17,7 @@ "added-trails-to": "Added trails to", "after": "After", "all-activities": "All activities", + "allow-auto-geolocate": "Begin drawing a new trail from your current location", "alphabetical": "Alphabetical", "already-account": "Already have an account?", "altitude": "Altitude", @@ -35,6 +36,7 @@ "basic-info": "Basic Info", "basque": "Basque", "before": "Before", + "behavior": "Behavior", "bicycle-parking": "Bicycle Parking", "bicycle-rental": "Bicycle Rental", "bicycle-shop": "Bicycle Shop", @@ -215,8 +217,8 @@ "invalid-username": "Invalid username", "italian": "Italian", "joined": "Joined", - "keep-private": "Keep private", "keep-original": "Keep original", + "keep-private": "Keep private", "language": "Language", "latitude": "Latitude", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/es.json b/web/src/lib/i18n/locales/es.json index 03d2be96..6768bb88 100644 --- a/web/src/lib/i18n/locales/es.json +++ b/web/src/lib/i18n/locales/es.json @@ -17,6 +17,7 @@ "added-trails-to": "Rutas añadidas a", "after": "Después", "all-activities": "Todas las actividades", + "allow-auto-geolocate": "", "alphabetical": "Alfabético", "already-account": "¿Ya tienes una cuenta?", "altitude": "Altitud", @@ -213,9 +214,9 @@ "invalid-date": "Fecha no válida", "invalid-username": "Usuario no válido", "italian": "Italiano", - "keep-private": "Keep private", "joined": "Afiliado", "keep-original": "", + "keep-private": "Keep private", "language": "Idioma", "latitude": "Latitud", "layer": "{n, plural, one {}=1 {Lista} other {Listas}}", diff --git a/web/src/lib/i18n/locales/eu.json b/web/src/lib/i18n/locales/eu.json index 9ea42dd3..a2616b24 100644 --- a/web/src/lib/i18n/locales/eu.json +++ b/web/src/lib/i18n/locales/eu.json @@ -17,6 +17,7 @@ "added-trails-to": "Ibilbideak hona gehitu dira", "after": "Ondoren", "all-activities": "Ekintza guztiak", + "allow-auto-geolocate": "", "alphabetical": "Alfabetikoa", "already-account": "Baduzu kontua lehendik?", "altitude": "Altuera", @@ -215,8 +216,8 @@ "invalid-username": "Erabiltzailea ez da zuzena", "italian": "Italiera", "joined": "Sartu da", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Hizkuntza", "latitude": "Latitudea", "layer": "{n, plural, one {}=1 {geruza} other {geruza}}", diff --git a/web/src/lib/i18n/locales/fr.json b/web/src/lib/i18n/locales/fr.json index ae1d11b3..de69ca38 100644 --- a/web/src/lib/i18n/locales/fr.json +++ b/web/src/lib/i18n/locales/fr.json @@ -17,6 +17,7 @@ "added-trails-to": "Ajouter les itinéraires à", "after": "Après", "all-activities": "Toutes les activités", + "allow-auto-geolocate": "", "alphabetical": "Alphabétique", "already-account": "Déjà un compte ?", "altitude": "Altitude", @@ -215,8 +216,8 @@ "invalid-username": "Nom d'utilisateur invalide", "italian": "Italien", "joined": "Rejoint", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Langue", "latitude": "Latitude", "layer": "{n, plural, =1 {Calque} other {Calques}}", diff --git a/web/src/lib/i18n/locales/hu.json b/web/src/lib/i18n/locales/hu.json index 653bd1ae..181ea8fe 100644 --- a/web/src/lib/i18n/locales/hu.json +++ b/web/src/lib/i18n/locales/hu.json @@ -17,6 +17,7 @@ "added-trails-to": "Hozzáadott nyomvonalak a", "after": "After", "all-activities": "All activities", + "allow-auto-geolocate": "", "alphabetical": "Betűrendben", "already-account": "Már rendelkezik fiókkal?", "altitude": "Magasság", @@ -215,8 +216,8 @@ "invalid-username": "Érvénytelen felhasználó", "italian": "Olasz", "joined": "Joined", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Nyelf", "latitude": "Szélesség", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/it.json b/web/src/lib/i18n/locales/it.json index 55ea9d2f..a329b14a 100644 --- a/web/src/lib/i18n/locales/it.json +++ b/web/src/lib/i18n/locales/it.json @@ -17,6 +17,7 @@ "added-trails-to": "Percorsi aggiunto a", "after": "Dopo", "all-activities": "Tutte le Attività", + "allow-auto-geolocate": "", "alphabetical": "Alfabetico", "already-account": "Hai già un account?", "altitude": "Altitudine", @@ -215,8 +216,8 @@ "invalid-username": "Nome utente non valido", "italian": "Italiano", "joined": "Aggiunto", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Lingua", "latitude": "Latitudine", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/nl.json b/web/src/lib/i18n/locales/nl.json index a3b6a024..4a7d874b 100644 --- a/web/src/lib/i18n/locales/nl.json +++ b/web/src/lib/i18n/locales/nl.json @@ -17,6 +17,7 @@ "added-trails-to": "Route toegevoegd aan", "after": "Na", "all-activities": "Alle activiteiten", + "allow-auto-geolocate": "", "alphabetical": "Alfabetisch", "already-account": "Heb je al een account?", "altitude": "Hoogte", @@ -216,8 +217,8 @@ "invalid-username": "Ongeldige gebruikersnaam", "italian": "Italiaans", "joined": "Aangesloten", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Taal", "latitude": "Breedtegraad", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/pl.json b/web/src/lib/i18n/locales/pl.json index 10fdff14..0bb10044 100644 --- a/web/src/lib/i18n/locales/pl.json +++ b/web/src/lib/i18n/locales/pl.json @@ -17,6 +17,7 @@ "added-trails-to": "Dodaj szlaki do", "after": "Po", "all-activities": "Wszystkie aktywności", + "allow-auto-geolocate": "", "alphabetical": "Alfabetyczne", "already-account": "Czy masz już konto?", "altitude": "Wysokość", @@ -215,8 +216,8 @@ "invalid-username": "Błędna nazwa użytkownika", "italian": "Włoski", "joined": "Dołączono", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Język", "latitude": "Szerokość", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/pt.json b/web/src/lib/i18n/locales/pt.json index 1ce09643..dc3ffd7e 100644 --- a/web/src/lib/i18n/locales/pt.json +++ b/web/src/lib/i18n/locales/pt.json @@ -17,6 +17,7 @@ "added-trails-to": "trilhas adicionada para", "after": "Depois", "all-activities": "Todas as atividades", + "allow-auto-geolocate": "", "alphabetical": "Alfabético", "already-account": "Já tem uma conta?", "altitude": "Altitude", @@ -215,8 +216,8 @@ "invalid-username": "Nome de usuário inválido", "italian": "Italiano", "joined": "Joined", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Língua", "latitude": "Latitude", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/ru.json b/web/src/lib/i18n/locales/ru.json index e28bd4c2..dd4c44fb 100644 --- a/web/src/lib/i18n/locales/ru.json +++ b/web/src/lib/i18n/locales/ru.json @@ -17,6 +17,7 @@ "added-trails-to": "Added trails to", "after": "После", "all-activities": "Все активности", + "allow-auto-geolocate": "", "alphabetical": "По алфавиту", "already-account": "Уже есть аккаунт?", "altitude": "Высота", @@ -215,8 +216,8 @@ "invalid-username": "Некорректное имя пользователя", "italian": "Итальянский", "joined": "Зарегистрирован", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "Язык", "latitude": "Широта", "layer": "{n, plural, =1 {Layer} other {Layers}}", diff --git a/web/src/lib/i18n/locales/zh.json b/web/src/lib/i18n/locales/zh.json index 6a4801d5..f9bf13c7 100644 --- a/web/src/lib/i18n/locales/zh.json +++ b/web/src/lib/i18n/locales/zh.json @@ -17,6 +17,7 @@ "added-trails-to": "添加路线到", "after": "之后", "all-activities": "所有活动", + "allow-auto-geolocate": "", "alphabetical": "字母", "already-account": "已注册账户?", "altitude": "海拔", @@ -215,8 +216,8 @@ "invalid-username": "无效用户名", "italian": "意大利语", "joined": "已加入", - "keep-private": "Keep private", "keep-original": "", + "keep-private": "Keep private", "language": "语言", "latitude": "纬度", "layer": "{n, plural, =1 {层} other {层}}", diff --git a/web/src/lib/models/api/settings_schema.ts b/web/src/lib/models/api/settings_schema.ts index 62be0f75..a426adb2 100644 --- a/web/src/lib/models/api/settings_schema.ts +++ b/web/src/lib/models/api/settings_schema.ts @@ -21,8 +21,8 @@ const SettingsCreateSchema = z.object({ trails: z.enum(["public", "private"]), lists: z.enum(["public", "private"]) }).optional().nullable(), - notifications: z.record(z.enum(Object.values(NotificationType) as [string, ...string[]]), z.object({ web: z.boolean(), email: z.boolean() })).optional().nullable() - + notifications: z.record(z.enum(Object.values(NotificationType) as [string, ...string[]]), z.object({ web: z.boolean(), email: z.boolean() })).optional().nullable(), + behavior: z.object({ allowAutoGeolocate: z.boolean() }).optional().nullable(), }) satisfies ZodType ZodType> diff --git a/web/src/lib/models/settings.ts b/web/src/lib/models/settings.ts index 6b18739a..f895288f 100644 --- a/web/src/lib/models/settings.ts +++ b/web/src/lib/models/settings.ts @@ -30,6 +30,7 @@ class Settings { user?: string; privacy?: { account: "public" | "private", trails: "public" | "private", lists: "public" | "private" } | null notifications?: Record | null + behavior?: Behavior | null; constructor( unit: "metric" | "imperial", @@ -54,5 +55,9 @@ class Settings { } } +export type Behavior = { + allowAutoGeolocate: boolean; +} + export { Settings }; diff --git a/web/src/routes/settings/map/+page.svelte b/web/src/routes/settings/map/+page.svelte index b185d11a..79e52da2 100644 --- a/web/src/routes/settings/map/+page.svelte +++ b/web/src/routes/settings/map/+page.svelte @@ -14,13 +14,41 @@ } from "$lib/stores/search_store"; import { settings_update } from "$lib/stores/settings_store"; import { currentUser } from "$lib/stores/user_store"; - import { country_codes } from "$lib/util/country_code_util"; import { getIconForLocation } from "$lib/util/icon_util"; import { onMount } from "svelte"; import { _ } from "svelte-i18n"; + import Toggle from "$lib/components/base/toggle.svelte"; + import { show_toast } from "$lib/stores/toast_store.svelte.js"; let settings = $derived(page.data.settings); + let allowAutoGeolocate = $state( + page.data.settings.behavior?.allowAutoGeolocate ?? false, + ); + + async function handleAllowAutoGeolocateChange() { + if (!settings) { + return; + } + + try { + if (!settings.behavior) { + settings.behavior = { allowAutoGeolocate: allowAutoGeolocate }; + } else { + settings.behavior.allowAutoGeolocate = allowAutoGeolocate; + } + + await settings_update(settings); + } catch (e) { + show_toast({ + type: "error", + icon: "close", + text: "Error updating behavior settings", + }); + console.error(e); + } + } + const mapFocus: SelectItem[] = [ { text: $_("trail", { values: { n: 2 } }), value: "trails" }, { text: $_("location"), value: "location" }, @@ -138,6 +166,18 @@ > {/if} +
+

{$_("allow-auto-geolocate")}

+
+ +
+

{$_("tilesets")}

diff --git a/web/src/routes/trail/edit/[id]/+page.svelte b/web/src/routes/trail/edit/[id]/+page.svelte index 8405da96..c0385068 100644 --- a/web/src/routes/trail/edit/[id]/+page.svelte +++ b/web/src/routes/trail/edit/[id]/+page.svelte @@ -1504,6 +1504,7 @@ waypoints={$formData.expand?.waypoints_via_trail} drawing={drawingActive} showTerrain={true} + autoGeolocateOnDrawing={page.params.id === "new"} onmarkerdragend={moveMarker} activeTrail={0} bind:map