adds settings collection
This commit is contained in:
7
web/src/routes/+layout.server.ts
Normal file
7
web/src/routes/+layout.server.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// +layout.ts
|
||||
import '$lib/i18n';
|
||||
import type { LayoutServerLoad } from './$types';
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals }) => {
|
||||
return { settings: locals.settings }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { beforeNavigate, goto } from "$app/navigation";
|
||||
import Toast from "$lib/components/base/toast.svelte";
|
||||
import Footer from "$lib/components/footer.svelte";
|
||||
@@ -13,7 +13,7 @@
|
||||
beforeNavigate((n) => {
|
||||
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
||||
n.cancel();
|
||||
goto("/login?r="+n.to?.url?.pathname);
|
||||
goto("/login?r=" + n.to?.url?.pathname);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
14
web/src/routes/api/v1/settings/+server.ts
Normal file
14
web/src/routes/api/v1/settings/+server.ts
Normal file
@@ -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<Settings>(data)
|
||||
return json(r);
|
||||
} catch (e: any) {
|
||||
throw error(e.status, e)
|
||||
}
|
||||
}
|
||||
32
web/src/routes/api/v1/settings/[id]/+server.ts
Normal file
32
web/src/routes/api/v1/settings/[id]/+server.ts
Normal file
@@ -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<Settings>(`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<Settings>(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)
|
||||
}
|
||||
}
|
||||
20
web/src/routes/api/v1/trail/bounding-box/+server.ts
Normal file
20
web/src/routes/api/v1/trail/bounding-box/+server.ts
Normal file
@@ -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<TrailBoundingBox>(pb.authStore.model!.id)
|
||||
return json(r)
|
||||
} catch (e: any) {
|
||||
throw error(e.status || 500, e);
|
||||
}
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 }
|
||||
};
|
||||
@@ -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 @@
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-left-right mr-2"
|
||||
></i>{formatDistance($trail.distance)}</span
|
||||
></i>{formatDistance(
|
||||
$trail.distance,
|
||||
)}</span
|
||||
>
|
||||
<span
|
||||
><i class="fa fa-up-down mr-2"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
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 @@
|
||||
</div>
|
||||
<div class="space-y-6 rounded-xl p-4">
|
||||
<h3 class="text-2xl font-semibold">{$_("settings")}</h3>
|
||||
|
||||
<div>
|
||||
<h5 class="font-medium mb-1">{$_("default-location")}</h5>
|
||||
<Search
|
||||
items={searchDropdownItems}
|
||||
placeholder="{$_('search-cities')}..."
|
||||
bind:value={citySearchQuery}
|
||||
on:update={(e) => searchCities(e.detail)}
|
||||
on:click={(e) => handleSearchClick(e.detail)}
|
||||
></Search>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="font-medium mb-1">{$_("language")}</h5>
|
||||
<Select
|
||||
@@ -180,10 +187,30 @@
|
||||
<RadioGroup
|
||||
name="unit"
|
||||
items={units}
|
||||
selected={$currentUser.unit == "metric" ? 0 : 1}
|
||||
selected={settings?.unit == "metric" ? 0 : 1}
|
||||
on:change={(e) => handleUnitSelection(e.detail)}
|
||||
></RadioGroup>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="font-medium mb-1">Focus map on</h5>
|
||||
|
||||
<Select
|
||||
items={mapFocus}
|
||||
bind:value={selectedMapFocus}
|
||||
on:change={(e) => handleMapFocusSelection(e.detail)}
|
||||
></Select>
|
||||
{#if selectedMapFocus == "location"}
|
||||
<div class="mt-3">
|
||||
<Search
|
||||
items={searchDropdownItems}
|
||||
placeholder="{$_('search-cities')}..."
|
||||
bind:value={citySearchQuery}
|
||||
on:update={(e) => searchCities(e.detail)}
|
||||
on:click={(e) => handleSearchClick(e.detail)}
|
||||
></Search>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<hr class="border-input-border" />
|
||||
<div class="space-y-4">
|
||||
<h4 class="text-xl text-red-400">{$_("danger-zone")}</h4>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import Button from "$lib/components/base/button.svelte";
|
||||
import Datepicker from "$lib/components/base/datepicker.svelte";
|
||||
import Select from "$lib/components/base/select.svelte";
|
||||
|
||||
Reference in New Issue
Block a user