adds settings collection

This commit is contained in:
Christian Beutel
2024-04-13 20:51:05 +02:00
parent f9f8998617
commit feb2214b1b
33 changed files with 1321 additions and 81 deletions

View 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 }
}

View File

@@ -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>

View 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)
}
}

View 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)
}
}

View 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);
}
}

View File

@@ -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) => {

View File

@@ -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 }
};

View File

@@ -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"

View File

@@ -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>

View File

@@ -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";