diff --git a/db/migrations/1713016468_created_settings.go b/db/migrations/1713016468_created_settings.go new file mode 100644 index 00000000..521bc58a --- /dev/null +++ b/db/migrations/1713016468_created_settings.go @@ -0,0 +1,130 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models" +) + +func init() { + m.Register(func(db dbx.Builder) error { + jsonData := `{ + "id": "uavt73rsqcn1n13", + "created": "2024-04-13 13:54:26.023Z", + "updated": "2024-04-13 13:54:26.023Z", + "name": "settings", + "type": "base", + "system": false, + "schema": [ + { + "system": false, + "id": "0sepzvkh", + "name": "language", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "en", + "de", + "fr", + "hu", + "nl", + "pl", + "pt", + "zh" + ] + } + }, + { + "system": false, + "id": "zwg1jl0d", + "name": "unit", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "metric", + "imperial" + ] + } + }, + { + "system": false, + "id": "jo1zcsbu", + "name": "mapFocus", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "trails", + "location" + ] + } + }, + { + "system": false, + "id": "ufhepjxo", + "name": "location", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + }, + { + "system": false, + "id": "5uip7a4p", + "name": "user", + "type": "relation", + "required": false, + "presentable": false, + "unique": false, + "options": { + "collectionId": "_pb_users_auth_", + "cascadeDelete": true, + "minSelect": null, + "maxSelect": 1, + "displayFields": null + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": {} + }` + + collection := &models.Collection{} + if err := json.Unmarshal([]byte(jsonData), &collection); err != nil { + return err + } + + return daos.New(db).SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + return dao.DeleteCollection(collection) + }) +} diff --git a/db/migrations/1713018113_updated_users.go b/db/migrations/1713018113_updated_users.go new file mode 100644 index 00000000..8faf0ef0 --- /dev/null +++ b/db/migrations/1713018113_updated_users.go @@ -0,0 +1,162 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models" + "github.com/pocketbase/pocketbase/models/schema" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("_pb_users_auth_") + if err != nil { + return err + } + + // remove + collection.Schema.RemoveField("wjofulpg") + + // remove + collection.Schema.RemoveField("t1wlsqyp") + + // remove + collection.Schema.RemoveField("fhxhln9g") + + // remove + collection.Schema.RemoveField("wosrk4ue") + + query := dao.RecordQuery("_pb_users_auth_") + + users := []*models.Record{} + if err := query.All(&users); err != nil { + return err + } + settingsCollection, err := dao.FindCollectionByNameOrId("settings") + + if err != nil { + return err + } + + for _, user := range users { + settings := models.NewRecord(settingsCollection) + language := user.Get("language") + unit := user.Get("unit") + location := user.Get("location") + settings.Set("language", language) + settings.Set("unit", unit) + settings.Set("location", location) + settings.Set("user", user.Id) + settings.Set("mapFocus", "trails") + if err := dao.SaveRecord(settings); err != nil { + return err + } + } + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db) + + collection, err := dao.FindCollectionByNameOrId("_pb_users_auth_") + if err != nil { + return err + } + + // add + del_unit := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "wjofulpg", + "name": "unit", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "metric", + "imperial" + ] + } + }`), del_unit); err != nil { + return err + } + collection.Schema.AddField(del_unit) + + // add + del_language := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "t1wlsqyp", + "name": "language", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "en", + "de", + "fr", + "hu", + "nl", + "pl", + "pt", + "zh" + ] + } + }`), del_language); err != nil { + return err + } + collection.Schema.AddField(del_language) + + // add + del_mapView := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "fhxhln9g", + "name": "mapView", + "type": "select", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSelect": 1, + "values": [ + "location", + "trails" + ] + } + }`), del_mapView); err != nil { + return err + } + collection.Schema.AddField(del_mapView) + + // add + del_location := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "wosrk4ue", + "name": "location", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 2000000 + } + }`), del_location); err != nil { + return err + } + collection.Schema.AddField(del_location) + + return dao.SaveCollection(collection) + }) +} diff --git a/db/migrations/1713023959_updated_settings.go b/db/migrations/1713023959_updated_settings.go new file mode 100644 index 00000000..aa2b3895 --- /dev/null +++ b/db/migrations/1713023959_updated_settings.go @@ -0,0 +1,46 @@ +package migrations + +import ( + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/tools/types" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + collection.ListRule = types.Pointer("user = @request.auth.id") + + collection.ViewRule = types.Pointer("user = @request.auth.id") + + collection.CreateRule = types.Pointer("") + + collection.UpdateRule = types.Pointer("user = @request.auth.id") + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("uavt73rsqcn1n13") + if err != nil { + return err + } + + collection.ListRule = nil + + collection.ViewRule = nil + + collection.CreateRule = nil + + collection.UpdateRule = nil + + return dao.SaveCollection(collection) + }) +} diff --git a/db/migrations/1713027641_created_trails_bounding_box.go b/db/migrations/1713027641_created_trails_bounding_box.go new file mode 100644 index 00000000..261f4d1b --- /dev/null +++ b/db/migrations/1713027641_created_trails_bounding_box.go @@ -0,0 +1,98 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models" +) + +func init() { + m.Register(func(db dbx.Builder) error { + jsonData := `{ + "id": "urytyc428mwlbqq", + "created": "2024-04-13 17:00:41.541Z", + "updated": "2024-04-13 17:00:41.541Z", + "name": "trails_bounding_box", + "type": "view", + "system": false, + "schema": [ + { + "system": false, + "id": "iyhsoisl", + "name": "max_lat", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "kx2qfztr", + "name": "max_lon", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "z4qsnjeb", + "name": "min_lat", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }, + { + "system": false, + "id": "p66xomdb", + "name": "min_lon", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + } + ], + "indexes": [], + "listRule": null, + "viewRule": null, + "createRule": null, + "updateRule": null, + "deleteRule": null, + "options": { + "query": "SELECT users.id, COALESCE(MAX(trails.lat), 0) AS max_lat, COALESCE(MAX(trails.lon), 0) AS max_lon, COALESCE(MIN(trails.lat), 0) AS min_lat, COALESCE(MIN(trails.lon), 0) AS min_lon FROM users LEFT JOIN trails ON users.id = trails.author GROUP BY users.id;" + } + }` + + collection := &models.Collection{} + if err := json.Unmarshal([]byte(jsonData), &collection); err != nil { + return err + } + + return daos.New(db).SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("urytyc428mwlbqq") + if err != nil { + return err + } + + return dao.DeleteCollection(collection) + }) +} diff --git a/db/migrations/1713028155_updated_trails_bounding_box.go b/db/migrations/1713028155_updated_trails_bounding_box.go new file mode 100644 index 00000000..bda06439 --- /dev/null +++ b/db/migrations/1713028155_updated_trails_bounding_box.go @@ -0,0 +1,205 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/daos" + m "github.com/pocketbase/pocketbase/migrations" + "github.com/pocketbase/pocketbase/models/schema" + "github.com/pocketbase/pocketbase/tools/types" +) + +func init() { + m.Register(func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("urytyc428mwlbqq") + if err != nil { + return err + } + + collection.ViewRule = types.Pointer("@request.auth.id = id") + + // remove + collection.Schema.RemoveField("iyhsoisl") + + // remove + collection.Schema.RemoveField("kx2qfztr") + + // remove + collection.Schema.RemoveField("z4qsnjeb") + + // remove + collection.Schema.RemoveField("p66xomdb") + + // add + new_max_lat := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "osfey1yx", + "name": "max_lat", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_max_lat); err != nil { + return err + } + collection.Schema.AddField(new_max_lat) + + // add + new_max_lon := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "eohslzky", + "name": "max_lon", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_max_lon); err != nil { + return err + } + collection.Schema.AddField(new_max_lon) + + // add + new_min_lat := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "kigvankd", + "name": "min_lat", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_min_lat); err != nil { + return err + } + collection.Schema.AddField(new_min_lat) + + // add + new_min_lon := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "ifnks9mg", + "name": "min_lon", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), new_min_lon); err != nil { + return err + } + collection.Schema.AddField(new_min_lon) + + return dao.SaveCollection(collection) + }, func(db dbx.Builder) error { + dao := daos.New(db); + + collection, err := dao.FindCollectionByNameOrId("urytyc428mwlbqq") + if err != nil { + return err + } + + collection.ViewRule = nil + + // add + del_max_lat := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "iyhsoisl", + "name": "max_lat", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_max_lat); err != nil { + return err + } + collection.Schema.AddField(del_max_lat) + + // add + del_max_lon := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "kx2qfztr", + "name": "max_lon", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_max_lon); err != nil { + return err + } + collection.Schema.AddField(del_max_lon) + + // add + del_min_lat := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "z4qsnjeb", + "name": "min_lat", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_min_lat); err != nil { + return err + } + collection.Schema.AddField(del_min_lat) + + // add + del_min_lon := &schema.SchemaField{} + if err := json.Unmarshal([]byte(`{ + "system": false, + "id": "p66xomdb", + "name": "min_lon", + "type": "json", + "required": false, + "presentable": false, + "unique": false, + "options": { + "maxSize": 1 + } + }`), del_min_lon); err != nil { + return err + } + collection.Schema.AddField(del_min_lon) + + // remove + collection.Schema.RemoveField("osfey1yx") + + // remove + collection.Schema.RemoveField("eohslzky") + + // remove + collection.Schema.RemoveField("kigvankd") + + // remove + collection.Schema.RemoveField("ifnks9mg") + + return dao.SaveCollection(collection) + }) +} diff --git a/web/package-lock.json b/web/package-lock.json index c3df9507..a2ea5cd8 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -26,7 +26,7 @@ "pdfkit": "^0.15.0", "photoswipe": "^5.4.3", "pocketbase": "^0.21.0", - "qrcode-with-logos": "^1.0.5", + "qrcode": "^1.4.4", "svelte-i18n": "^4.0.0", "three": "^0.161.0", "yup": "^1.3.3" @@ -4356,14 +4356,6 @@ "node": ">=10.13.0" } }, - "node_modules/qrcode-with-logos": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/qrcode-with-logos/-/qrcode-with-logos-1.0.5.tgz", - "integrity": "sha512-ZHynBWdidxv57T5Lw8KBGpM6c/bwrL9buITPOrtX0PaoV0AgKkqHifdO+rKV7jMSiR/EcQWpvHI9ZarZonDq6g==", - "dependencies": { - "qrcode": "^1.4.4" - } - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", diff --git a/web/package.json b/web/package.json index dd945b32..28405b56 100644 --- a/web/package.json +++ b/web/package.json @@ -51,7 +51,7 @@ "pdfkit": "^0.15.0", "photoswipe": "^5.4.3", "pocketbase": "^0.21.0", - "qrcode-with-logos": "^1.0.5", + "qrcode": "^1.4.4", "svelte-i18n": "^4.0.0", "three": "^0.161.0", "yup": "^1.3.3" diff --git a/web/src/app.d.ts b/web/src/app.d.ts index 620fa86b..6c9ff5f9 100644 --- a/web/src/app.d.ts +++ b/web/src/app.d.ts @@ -14,7 +14,9 @@ declare global { interface Locals { pb: PocketBase ms: MeiliSearch - user: AuthModel | null + user: AuthModel | null, + settings: Settings | null + } } } diff --git a/web/src/hooks.client.ts b/web/src/hooks.client.ts index 19ea1fe5..764697a3 100644 --- a/web/src/hooks.client.ts +++ b/web/src/hooks.client.ts @@ -1,8 +1,11 @@ +import type { User } from '$lib/models/user' import { pb } from '$lib/pocketbase' -import { currentUser, type User } from '$lib/stores/user_store' +import { settings_show } from '$lib/stores/settings_store'; +import { currentUser } from '$lib/stores/user_store' +import { get } from 'svelte/store'; pb.authStore.loadFromCookie(document.cookie) pb.authStore.onChange(() => { currentUser.set(pb.authStore.model as User) document.cookie = pb.authStore.exportToCookie({ httpOnly: false }) -}, true) +}, true) \ No newline at end of file diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts index 3b85b48a..ff726593 100644 --- a/web/src/hooks.server.ts +++ b/web/src/hooks.server.ts @@ -1,11 +1,13 @@ import { env } from '$env/dynamic/private' import { env as envPub } from '$env/dynamic/public' +import type { Settings } from '$lib/models/settings' import { pb } from '$lib/pocketbase' import { isRouteProtected } from '$lib/util/authorization_util' import { redirect, type Handle } from '@sveltejs/kit' import { MeiliSearch } from 'meilisearch' import { locale } from 'svelte-i18n' +import { get } from 'svelte/store' export const handle: Handle = async ({ event, resolve }) => { // load the store data from the request cookie string @@ -13,7 +15,7 @@ export const handle: Handle = async ({ event, resolve }) => { const url = new URL(event.request.url); - + // validate the user existence and if the path is acceesible if (!pb.authStore.model && isRouteProtected(url.pathname)) { throw redirect(302, '/login?r=' + url.pathname); @@ -34,8 +36,10 @@ export const handle: Handle = async ({ event, resolve }) => { } let meiliApiKey: string = ""; + let settings: Settings | undefined; if (pb.authStore.model) { meiliApiKey = pb.authStore.model.token + settings = await pb.collection('settings').getFirstListItem(`user="${pb.authStore.model.id}"`) } else { const r = await event.fetch(pb.buildUrl("/public/search/token")); const response = await r.json(); @@ -46,11 +50,15 @@ export const handle: Handle = async ({ event, resolve }) => { event.locals.ms = ms event.locals.pb = pb event.locals.user = pb.authStore.model + event.locals.settings = settings - const lang = pb.authStore.model?.language ?? event.request.headers.get('accept-language')?.split(',')[0] + const lang = settings?.language ?? event.request.headers.get('accept-language')?.split(',')[0] if (lang) { locale.set(lang) + if (pb.authStore.model) { + pb.authStore.model!.language = lang; + } } const response = await resolve(event) diff --git a/web/src/lib/components/trail/map_with_elevation.svelte b/web/src/lib/components/trail/map_with_elevation.svelte index f184b85e..39aaeb7d 100644 --- a/web/src/lib/components/trail/map_with_elevation.svelte +++ b/web/src/lib/components/trail/map_with_elevation.svelte @@ -1,6 +1,6 @@ diff --git a/web/src/routes/api/v1/settings/+server.ts b/web/src/routes/api/v1/settings/+server.ts new file mode 100644 index 00000000..aac4690c --- /dev/null +++ b/web/src/routes/api/v1/settings/+server.ts @@ -0,0 +1,14 @@ +import type { Settings } from '$lib/models/settings'; +import { pb } from '$lib/pocketbase'; +import { error, json, type RequestEvent } from '@sveltejs/kit'; + +export async function PUT(event: RequestEvent) { + const data = await event.request.json(); + + try { + const r = await pb.collection('settings').create(data) + return json(r); + } catch (e: any) { + throw error(e.status, e) + } +} diff --git a/web/src/routes/api/v1/settings/[id]/+server.ts b/web/src/routes/api/v1/settings/[id]/+server.ts new file mode 100644 index 00000000..89a7f989 --- /dev/null +++ b/web/src/routes/api/v1/settings/[id]/+server.ts @@ -0,0 +1,32 @@ +import type { Settings } from "$lib/models/settings"; +import { pb } from "$lib/pocketbase"; +import { error, json, type RequestEvent } from "@sveltejs/kit"; + + +export async function GET(event: RequestEvent) { + try { + const r = await pb.collection('settings').getFirstListItem(`user="${event.params.id}"`) + return json(r) + } catch (e: any) { + throw error(e.status, e); + } +} + +export async function POST(event: RequestEvent) { + const data = await event.request.json() + try { + const r = await await pb.collection("settings").update(event.params.id as string, data); + return json(r); + } catch (e: any) { + throw error(e.status, e) + } +} + +export async function DELETE(event: RequestEvent) { + try { + const r = await pb.collection('settings').delete(event.params.id as string) + return json({ 'acknowledged': r }); + } catch (e: any) { + throw error(e.status, e) + } +} \ No newline at end of file diff --git a/web/src/routes/api/v1/trail/bounding-box/+server.ts b/web/src/routes/api/v1/trail/bounding-box/+server.ts new file mode 100644 index 00000000..7661a405 --- /dev/null +++ b/web/src/routes/api/v1/trail/bounding-box/+server.ts @@ -0,0 +1,20 @@ +import { type TrailBoundingBox, type TrailFilterValues } from '$lib/models/trail'; +import { pb } from '$lib/pocketbase'; +import { error, json, type RequestEvent } from '@sveltejs/kit'; + +export async function GET(event: RequestEvent) { + if (!pb.authStore.model) { + return json({ + max_lat: 0, + min_lat: 0, + max_lon: 0, + min_lon: 0 + }); + } + try { + const r = await pb.collection('trails_bounding_box').getOne(pb.authStore.model!.id) + return json(r) + } catch (e: any) { + throw error(e.status || 500, e); + } +} diff --git a/web/src/routes/map/+page.svelte b/web/src/routes/map/+page.svelte index ef115c93..771a4385 100644 --- a/web/src/routes/map/+page.svelte +++ b/web/src/routes/map/+page.svelte @@ -8,7 +8,12 @@ import EmptyStateSearch from "$lib/components/empty_states/empty_state_search.svelte"; import TrailCard from "$lib/components/trail/trail_card.svelte"; import TrailFilterPanel from "$lib/components/trail/trail_filter_panel.svelte"; - import type { Trail, TrailFilter } from "$lib/models/trail"; + import type { Settings } from "$lib/models/settings"; + import type { + Trail, + TrailBoundingBox, + TrailFilter, + } from "$lib/models/trail"; import { categories } from "$lib/stores/category_store"; import { trails, @@ -48,6 +53,8 @@ let showMap: boolean = true; const filter: TrailFilter = $page.data.filter; + const maxBoundingBox: TrailBoundingBox = $page.data.boundingBox; + const settings: Settings = $page.data.settings; onMount(async () => { L = (await import("leaflet")).default; @@ -90,11 +97,18 @@ ]; map.fitBounds(boundingBox); - } else if ($currentUser && $currentUser.location) { - map.setView( - [$currentUser.location.lat, $currentUser.location.lon], - 12, - ); + } else if (settings && settings.mapFocus == "trails") { + const boundingBox: LatLngBoundsExpression = [ + [maxBoundingBox.max_lat, maxBoundingBox.min_lon], + [maxBoundingBox.min_lat, maxBoundingBox.max_lon], + ]; + map.fitBounds(boundingBox); + } else if ( + settings && + settings.mapFocus == "location" && + settings.location + ) { + map.setView([settings.location.lat, settings.location.lon], 12); } else { navigator.geolocation.getCurrentPosition( (position) => { diff --git a/web/src/routes/map/+page.ts b/web/src/routes/map/+page.ts index 0ad17d18..5c8d81dc 100644 --- a/web/src/routes/map/+page.ts +++ b/web/src/routes/map/+page.ts @@ -1,9 +1,10 @@ import type { TrailFilter } from "$lib/models/trail"; import { categories_index } from "$lib/stores/category_store"; -import { trails, trails_get_filter_values } from "$lib/stores/trail_store"; +import { trails, trails_get_bounding_box, trails_get_filter_values } from "$lib/stores/trail_store"; import type { ServerLoad } from "@sveltejs/kit"; export const load: ServerLoad = async ({ params, locals, fetch }) => { + const boundingBox = await trails_get_bounding_box(fetch); const filterValues = await trails_get_filter_values(fetch); const filter: TrailFilter = { @@ -27,5 +28,5 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => { trails.set([]) - return { filter: filter } + return { filter: filter, boundingBox: boundingBox } }; \ No newline at end of file diff --git a/web/src/routes/map/trail/[id]/print/+page.svelte b/web/src/routes/map/trail/[id]/print/+page.svelte index ccfa7bbf..af0844cf 100644 --- a/web/src/routes/map/trail/[id]/print/+page.svelte +++ b/web/src/routes/map/trail/[id]/print/+page.svelte @@ -4,13 +4,12 @@ import "$lib/assets/fonts/IBMPlexSans-SemiBold-bold"; import "$lib/assets/fonts/fa-solid-900-normal"; import Button from "$lib/components/base/button.svelte"; - import Select, { - type SelectItem, - } from "$lib/components/base/select.svelte"; + import Select from "$lib/components/base/select.svelte"; import LogoText from "$lib/components/logo/logo_text.svelte"; import MapWithElevation from "$lib/components/trail/map_with_elevation.svelte"; + import type { Settings } from "$lib/models/settings"; + import { show_toast } from "$lib/stores/toast_store"; import { trail } from "$lib/stores/trail_store"; - import { currentUser } from "$lib/stores/user_store"; import { formatDistance, formatElevation, @@ -22,21 +21,22 @@ } from "$lib/util/leaflet_util"; import { createRect, createText } from "$lib/util/svg_util"; import "$lib/vendor/leaflet-elevation/src/index.css"; + import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule"; import leafletImage from "$lib/vendor/leaflet-image/leaflet-image.js"; + import { Canvg } from "canvg"; import { jsPDF } from "jspdf"; import type { Map } from "leaflet"; import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css"; import "leaflet/dist/leaflet.css"; - import QrCodeWithLogo from "qrcode-with-logos"; + import QrCodeWithLogo from "$lib/vendor/qr-code-with-logos/index"; import { onMount, tick } from "svelte"; import { _ } from "svelte-i18n"; - import { Canvg } from "canvg"; - import type AutoGraticule from "$lib/vendor/leaflet-graticule/leaflet-auto-graticule"; - import { show_toast } from "$lib/stores/toast_store"; let map: Map; let graticule: AutoGraticule; + const settings: Settings = $page.data.settings; + const paperSizes: { text: string; value: keyof typeof paperDimensions }[] = [ { text: "A4", value: "a4" }, @@ -375,7 +375,7 @@ let oneUnitInPixels = calculatePixelPerMeter( map, - $currentUser && $currentUser.unit == "imperial" ? 1609.34 : 1000, + settings && settings.unit == "imperial" ? 1609.34 : 1000, ); let unitsInRuler = width / oneUnitInPixels; @@ -430,7 +430,7 @@ svg.appendChild( createText( - $currentUser && $currentUser.unit == "imperial" ? "MI" : "KM", + settings && settings.unit == "imperial" ? "MI" : "KM", segmentCount * multiplier * oneUnitInPixels - 20, height / 2 - 7, ), @@ -535,7 +535,9 @@ > {formatDistance($trail.distance)}{formatDistance( + $trail.distance, + )} import { goto } from "$app/navigation"; + import { page } from "$app/stores"; import RadioGroup, { type RadioItem, } from "$lib/components/base/radio_group.svelte"; @@ -10,6 +11,7 @@ type SelectItem, } from "$lib/components/base/select.svelte"; import ConfirmModal from "$lib/components/confirm_modal.svelte"; + import { settings_update } from "$lib/stores/settings_store"; import { currentUser, logout, @@ -21,6 +23,8 @@ import { onMount } from "svelte"; import { _, locale } from "svelte-i18n"; + const settings = $page.data.settings; + const languages: SelectItem[] = [ { text: $_("chinese"), value: "zh" }, { text: $_("german"), value: "de" }, @@ -32,12 +36,18 @@ { text: $_("portuguese"), value: "pt" }, ]; + const mapFocus: SelectItem[] = [ + { text: $_("trail", { values: { n: 2 } }), value: "trails" }, + { text: $_("location"), value: "location" }, + ]; + const units: RadioItem[] = [ { text: $_("metric"), value: "metric" }, { text: $_("imperial"), value: "imperial" }, ]; let selectedLanguage = "en"; + let selectedMapFocus = "trails"; let searchDropdownItems: SearchItem[] = []; let citySearchQuery: string = ""; @@ -45,8 +55,9 @@ let openConfirmModal: () => void; onMount(() => { - citySearchQuery = $currentUser?.location?.name ?? ""; - selectedLanguage = $currentUser?.language || "en"; + citySearchQuery = settings?.location?.name ?? ""; + selectedLanguage = settings?.language || "en"; + selectedMapFocus = settings?.mapFocus ?? "trails"; }); async function searchCities(q: string) { @@ -67,8 +78,8 @@ async function handleSearchClick(item: SearchItem) { citySearchQuery = item.text; - await users_update({ - id: $currentUser!.id, + await settings_update({ + id: settings?.id!, location: { name: item.value.name, lat: item.value.lat, @@ -90,8 +101,8 @@ } async function handleUnitSelection(e: RadioItem) { - await users_update({ - id: $currentUser!.id, + await settings_update({ + id: settings!.id, unit: e.value as "imperial" | "metric", }); } @@ -100,12 +111,19 @@ value: "en" | "de" | "fr" | "hu" | "nl" | "pl" | "pt", ) { locale.set(value); - await users_update({ - id: $currentUser!.id, + await settings_update({ + id: settings!.id, language: value, }); } + async function handleMapFocusSelection(value: "trails" | "location") { + await settings_update({ + id: settings!.id, + mapFocus: value, + }); + } + async function deleteAccount() { await users_delete($currentUser!); logout(); @@ -156,17 +174,6 @@

{$_("settings")}

- -
-
{$_("default-location")}
- searchCities(e.detail)} - on:click={(e) => handleSearchClick(e.detail)} - > -
{$_("language")}
handleMapFocusSelection(e.detail)} + > + {#if selectedMapFocus == "location"} +
+ searchCities(e.detail)} + on:click={(e) => handleSearchClick(e.detail)} + > +
+ {/if} +

{$_("danger-zone")}

diff --git a/web/src/routes/trail/edit/[id]/+page.svelte b/web/src/routes/trail/edit/[id]/+page.svelte index 0e46e9c4..3f6167d3 100644 --- a/web/src/routes/trail/edit/[id]/+page.svelte +++ b/web/src/routes/trail/edit/[id]/+page.svelte @@ -1,4 +1,5 @@