adds lists search and filter

This commit is contained in:
Christian Beutel
2024-12-13 18:05:57 +01:00
parent 673eb78dd0
commit c00cf85fed
20 changed files with 417 additions and 104 deletions

View File

@@ -1,5 +1,10 @@
<script lang="ts">
export let fullWidth: boolean = false;
</script>
<div
class="skeleton-card relative rounded-2xl sm:w-72 animate-pulse flex flex-col w-80 p-4 bg-menu-background border border-input-border"
class="skeleton-card relative rounded-2xl animate-pulse flex flex-col {fullWidth ? "w-full" : "sm:w-72 w-80"} p-4 bg-menu-background border border-input-border"
aria-hidden="true"
>
<!-- Image placeholder -->

View File

@@ -25,7 +25,7 @@
<button class="btn-secondary" on:click={closeModal}
>{$_("cancel")}</button
>
<button class={action === "delete" ? "btn-delete" : "btn-primary"} type="button" on:click={confirm} name="delete"
<button class={action === "delete" ? "btn-danger" : "btn-primary"} type="button" on:click={confirm} name="delete"
>{$_(action)}</button
>
</div></Modal

View File

@@ -51,18 +51,15 @@
<i class="fa fa-table-list text-5xl"></i>
</div>
{/if}
<div class="self-start min-w-0 w-full transition-transform">
<div class="self-start min-w-0 basis-full transition-transform">
<div class="flex items-center gap-3">
<h5
class="text-xl font-semibold overflow-hidden overflow-ellipsis basis-full"
class="text-xl font-semibold overflow-hidden overflow-ellipsis"
>
{list.name}
</h5>
{#if list.public}
<span
class="tooltip"
data-title={$_("public")}
>
<span class="tooltip" data-title={$_("public")}>
<i class="fa fa-globe"></i>
</span>
{/if}
@@ -70,6 +67,21 @@
<ShareInfo type="list" subject={list}></ShareInfo>
{/if}
</div>
{#if list.expand?.author}
<p class="text-xs text-gray-500 my-2">
{$_("by")}
<img
class="rounded-full w-5 aspect-square mx-1 inline"
src={getFileURL(
list.expand.author,
list.expand.author.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${list.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
{list.expand?.author.username}
</p>
{/if}
<div
class="grid grid-cols-2 mt-1 mb-2 gap-x-4 gap-y-1 text-sm text-gray-500 whitespace-nowrap flex-wrap"
>

View File

@@ -42,8 +42,12 @@
list.author == $currentUser?.id ||
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
$: allowShare =
list.author == $currentUser?.id &&
!list.expand?.trails?.some((t) => t.author !== $currentUser?.id);
$: dropdownItems = [
...(list.author == $currentUser?.id
...(allowShare
? [{ text: $_("share"), value: "share", icon: "share" }]
: []),
...(allowEdit
@@ -122,7 +126,23 @@
<div class="p-4 md:p-6">
<h4 class="text-2xl font-semibold mb-4">{list.name}</h4>
{#if list.expand?.author}
<p class="my-3 text-gray-500 text-sm">
{$_("by")}
<img
class="rounded-full w-8 aspect-square mx-1 inline"
src={getFileURL(
list.expand.author,
list.expand.author.avatar,
) ||
`https://api.dicebear.com/7.x/initials/svg?seed=${list.expand.author.username}&backgroundType=gradientLinear`}
alt="avatar"
/>
<a class="underline" href="/profile/{list.expand.author.id}"
>{list.expand.author.username}</a
>
</p>
{/if}
<hr />
<div
class="grid grid-cols-2 my-4 gap-4 font-semibold whitespace-nowrap flex-wrap justify-around"

View File

@@ -2,11 +2,12 @@
import { createEventDispatcher } from "svelte";
import type { List } from "$lib/models/list";
import { lists } from "$lib/stores/list_store";
import { trail } from "$lib/stores/trail_store";
import { getFileURL } from "$lib/util/file_util";
import { _ } from "svelte-i18n";
import Modal from "../base/modal.svelte";
import { getFileURL } from "$lib/util/file_util";
export let lists: List[];
export let openModal: (() => void) | undefined = undefined;
export let closeModal: (() => void) | undefined = undefined;
@@ -34,7 +35,7 @@
<slot {openModal} />
<ul slot="content">
{#each $lists as list}
{#each lists as list}
<li
class="flex gap-4 items-center p-4 hover:bg-menu-item-background-hover rounded-xl transition-colors cursor-pointer"
on:click={() => handleSelect(list)}

View File

@@ -3,7 +3,6 @@
import type { List } from "$lib/models/list";
import { ListShare } from "$lib/models/list_share";
import { TrailShare } from "$lib/models/trail_share";
import type { User } from "$lib/models/user";
import {
list_share_create,
list_share_delete,
@@ -11,19 +10,15 @@
list_share_update,
shares,
} from "$lib/stores/list_share_store";
import { lists } from "$lib/stores/list_store";
import { show_toast } from "$lib/stores/toast_store";
import {
trail_share_create,
trail_share_delete,
trail_share_index,
} from "$lib/stores/trail_share_store";
import { users_search } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import { createEventDispatcher } from "svelte";
import { _ } from "svelte-i18n";
import Button from "../base/button.svelte";
import Search, { type SearchItem } from "../base/search.svelte";
import type { SelectItem } from "../base/select.svelte";
import Select from "../base/select.svelte";
import UserSearch from "../user_search.svelte";
@@ -116,8 +111,8 @@
trails: list.expand?.trails ?? [],
list_share_via_list: fetchedShares,
};
lists.set($lists);
sharesLoading = false;
sharesLoading = false;
dispatch("update", list)
}
</script>

View File

@@ -52,7 +52,7 @@
></i>
{#if showInfo}
<ul
class="menu absolute z-10 top-8 bg-menu-background border border-input-border rounded-xl shadow-md overflow-hidden p-3 space-y-3 text-content"
class="menu absolute z-10 top-8 bg-menu-background border border-input-border rounded-xl shadow-md overflow-hidden p-3 space-y-3 text-content -translate-x-3/4"
in:fly={{y: -10, duration: 150}} out:fly={{y: -10, duration: 150}}
>
{#if loading}

View File

@@ -30,6 +30,8 @@
let openExportModal: () => void;
let openShareModal: () => void;
let lists: List[] = [];
const allowEdit =
trail.author == $currentUser?.id ||
trail.expand.trail_share_via_trail?.some((s) => s.permission == "edit");
@@ -76,6 +78,13 @@
: `/trail/view/${trail.id!}`,
);
} else if (item.value == "list") {
lists = (
await lists_index(
{ q: "", author: $currentUser?.id ?? "" },
1,
-1,
)
).items;
openListSelectModal();
} else if (item.value == "direction") {
window
@@ -175,7 +184,6 @@
text: `${$_("added-trail-to")} "${list.name}"`,
});
}
await lists_index();
} catch (e) {
console.error(e);
@@ -206,6 +214,7 @@
on:confirm={deleteTrail}
></ConfirmModal>
<ListSelectModal
{lists}
bind:openModal={openListSelectModal}
on:change={(e) => handleListSelection(e.detail)}
></ListSelectModal>

View File

@@ -1,6 +1,7 @@
import { object, string } from "yup";
import type { Trail } from "./trail";
import type { ListShare } from "./list_share";
import type { UserAnonymous } from "./user";
export class List {
id?: string;
@@ -12,6 +13,7 @@ export class List {
expand?: {
trails?: Trail[]
list_share_via_list?: ListShare[]
author?: UserAnonymous;
}
author?: string;
@@ -29,8 +31,11 @@ export class List {
export interface ListFilter {
q: string,
sort: "name" | "size" | "created";
sortOrder: "+" | "-"
sort?: "name" | "size" | "created";
author?: string;
public?: boolean;
shared?: boolean;
sortOrder?: "+" | "-"
}
export const listSchema = object<List>({

View File

@@ -1,18 +1,22 @@
import { List, type ListFilter } from "$lib/models/list";
import type { Trail } from "$lib/models/trail";
import { pb } from "$lib/pocketbase";
import { getFileURL } from "$lib/util/file_util";
import { ClientResponseError } from "pocketbase";
import { writable, type Writable } from "svelte/store";
import { ClientResponseError, type ListResult } from "pocketbase";
import { get, writable, type Writable } from "svelte/store";
import { fetchGPX } from "./trail_store";
export const lists: Writable<List[]> = writable([])
let lists: List[] = []
export const list: Writable<List | null> = writable(null)
export const listTrail: Writable<Trail | null> = writable(null);
export async function lists_index(filter?: ListFilter, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
export async function lists_index(filter?: ListFilter, page: number = 1, perPage: number = 5, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
const filterText = filter ? buildFilterText(filter) : ""
const r = await f('/api/v1/list?' + new URLSearchParams({
sort: `${filter?.sortOrder ?? "-"}${filter?.sort ?? "name"}`,
"per-page": perPage.toString(),
page: page.toString(),
filter: filterText
}), {
method: 'GET',
})
@@ -21,15 +25,13 @@ export async function lists_index(filter?: ListFilter, f: (url: RequestInfo | UR
throw new ClientResponseError(await r.json())
}
const fetchedLists: List[] = await r.json();
const fetchedLists: ListResult<List> = await r.json();
lists.set(fetchedLists);
const result = page > 1 ? [...lists, ...fetchedLists.items] : fetchedLists.items
if (fetchedLists.length > 0) {
// list.set(fetchedLists[0])
}
lists = result;
return fetchedLists;
return { ...fetchedLists, items: result };
}
@@ -40,7 +42,7 @@ export async function lists_show(id: string, f: (url: RequestInfo | URL, config?
const response = await r.json()
for (const trail of response.expand?.trails ?? []) {
const gpxData: string = await fetchGPX(trail);
const gpxData: string = await fetchGPX(trail, f);
trail.expand.gpx_data = gpxData;
}
@@ -49,7 +51,6 @@ export async function lists_show(id: string, f: (url: RequestInfo | URL, config?
throw new ClientResponseError(response)
}
list.set(response);
return response;
@@ -85,7 +86,9 @@ export async function lists_create(list: List, avatar?: File) {
body: formData,
})
if (!r.ok) {
if (r.ok) {
return await r.json()
} else {
throw new ClientResponseError(await r.json())
}
}
@@ -152,4 +155,28 @@ export async function lists_delete(list: List) {
if (!r.ok) {
throw new ClientResponseError(await r.json())
}
}
function buildFilterText(filter: ListFilter): string {
let filterText = `(name~"${filter.q}"||description~"${filter.q}")`
if (filter.author?.length) {
filterText += `&&author="${filter.author}"`
}
if (pb.authStore.model) {
if (filter.public === false && filter.shared === false) {
filterText += `&&author="${pb.authStore.model.id}"`
} else if (filter.public === true && filter.shared === false) {
filterText += `&&(public=true||list_share_via_list.user!="${pb.authStore.model.id}"||author="${pb.authStore.model.id}")`
} else if (filter.public === false && filter.shared === true) {
filterText += `&&(public=false||list_share_via_list.user="${pb.authStore.model.id}"||author="${pb.authStore.model.id}")`
}
}
return filterText
}

View File

@@ -3,12 +3,35 @@ import { pb } from '$lib/pocketbase';
import { error, json, type RequestEvent } from '@sveltejs/kit';
export async function GET(event: RequestEvent) {
const page = event.url.searchParams.get("page") ?? "0";
const perPage = event.url.searchParams.get("per-page") ?? "10";
const sort = event.url.searchParams.get('sort') ?? ""
const filter = event.url.searchParams.get("filter") ?? "";
const expand = "trails,trails.waypoints,trails.category,list_share_via_list";
try {
const r: List[] = await pb.collection('lists').getFullList<List>({
expand: "trails,trails.waypoints,trails.category,list_share_via_list",
sort: sort,
})
let r;
if (parseInt(perPage) < 0) {
r = {
items: await pb.collection('lists')
.getFullList<List>({ expand: expand, sort: sort, filter: filter })
}
} else {
r = await pb.collection('lists')
.getList<List>(parseInt(page), parseInt(perPage), { expand: expand, sort: sort ?? "", filter: filter ?? "" })
}
for (const t of r.items) {
if (!t.author || !pb.authStore.model) {
continue;
}
if (!t.expand) {
t.expand = {}
}
t.expand!.author = await pb.collection("users_anonymous").getOne(t.author);
}
return json(r)
} catch (e: any) {
throw error(e.status, e);

View File

@@ -2,6 +2,12 @@
import { goto } from "$app/navigation";
import { page } from "$app/stores";
import type { DropdownItem } from "$lib/components/base/dropdown.svelte";
import Search, {
type SearchItem,
} from "$lib/components/base/search.svelte";
import type { SelectItem } from "$lib/components/base/select.svelte";
import Select from "$lib/components/base/select.svelte";
import SkeletonCard from "$lib/components/base/skeleton_card.svelte";
import ConfirmModal from "$lib/components/confirm_modal.svelte";
import ListCard from "$lib/components/list/list_card.svelte";
import ListPanel from "$lib/components/list/list_panel.svelte";
@@ -9,18 +15,29 @@
import MapWithElevationMaplibre from "$lib/components/trail/map_with_elevation_maplibre.svelte";
import TrailInfoPanel from "$lib/components/trail/trail_info_panel.svelte";
import TrailList from "$lib/components/trail/trail_list.svelte";
import UserSearch from "$lib/components/user_search.svelte";
import { List } from "$lib/models/list";
import type { Trail } from "$lib/models/trail";
import {
lists,
lists_delete,
lists_index
} from "$lib/stores/list_store";
import { lists_delete, lists_index } from "$lib/stores/list_store";
import { fetchGPX } from "$lib/stores/trail_store";
import { currentUser } from "$lib/stores/user_store";
import * as M from "maplibre-gl";
import { onMount, tick } from "svelte";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
import { slide } from "svelte/transition";
export let data;
const sortOptions: SelectItem[] = [
{ text: $_("alphabetical"), value: "name" },
{ text: $_("creation-date"), value: "created" },
];
let pagination = {
page: data.lists.page,
totalPages: data.lists.totalPages,
};
let openConfirmModal: () => void;
let openShareModal: () => void;
@@ -30,19 +47,28 @@
let markers: M.Marker[];
let showMap: boolean = true;
let selectedList: List | null = null;
let selectedList: List | null = $page.url.searchParams.get("list")
? data.lists.items[0]
: null;
let selectedTrail: Trail | null = null;
let activeTrailIndex: number = -1;
let loading: boolean = false;
let loadingNextPage: boolean = false;
let filterExpanded: boolean = false;
let loadAllListsOnNextBack = false;
let userQuery = "";
onMount(() => {
if ($page.url.searchParams.get("list")) {
const listToFocus = $lists.find(
(l) => l.id == $page.url.searchParams.get("list"),
);
if (listToFocus) {
setCurrentList(listToFocus);
}
if ($page.url.searchParams.get("list") && selectedList) {
setCurrentList(selectedList);
// only the requested list has been loaded at this point
// load all lists the next time the user presses the back button
loadAllListsOnNextBack = true;
}
});
@@ -66,7 +92,7 @@
return;
}
await lists_delete(selectedList);
await lists_index();
await updateFilter();
selectedList = null;
}
@@ -82,9 +108,14 @@
}
}
selectedList = item;
document.getElementById("list-container")?.scrollTo({ top: 0 });
}
async function back() {
if (loadAllListsOnNextBack) {
await updateFilter();
loadAllListsOnNextBack = false;
}
if (selectedTrail) {
mapWithElevation.unFocusTrail(selectedTrail);
selectedTrail = null;
@@ -111,6 +142,80 @@
function unHighlightTrail(trail: Trail) {
mapWithElevation.unHighlightTrail(trail.id!);
}
async function onListScroll(e: any) {
const container = e.target as HTMLDivElement;
const scrollTop = container.scrollTop;
const scrollHeight = container.scrollHeight;
const clientHeight = container.clientHeight;
if (
scrollTop + clientHeight >= scrollHeight * 0.8 &&
pagination.page !== pagination.totalPages &&
!loadingNextPage
) {
loadingNextPage = true;
await loadNextPage();
loadingNextPage = false;
}
}
async function loadNextPage() {
pagination.page += 1;
data.lists = await lists_index(data.filter, pagination.page);
}
async function updateFilter() {
loading = true;
if (selectedList || selectedTrail) {
selectedList = null;
selectedTrail = null;
map.flyTo({
animate: true,
zoom: 1,
center: [0, 0],
});
}
pagination.page = 0;
data.lists = await lists_index(data.filter, pagination.page);
loading = false;
}
async function setSort(value: "name" | "created") {
data.filter.sort = value;
await updateFilter();
}
async function setSortOrder() {
if (data.filter.sortOrder === "+") {
data.filter.sortOrder = "-";
} else {
data.filter.sortOrder = "+";
}
await updateFilter();
}
async function setPublicFilter(e: Event) {
data.filter.public = (e.target as HTMLInputElement).checked;
updateFilter();
}
async function setAuthorFilter(item: SearchItem) {
data.filter.author = item.value.id;
await updateFilter();
}
async function clearAuthorFilter() {
data.filter.author = "";
await updateFilter();
}
async function setSharedFilter(e: Event) {
data.filter.shared = (e.target as HTMLInputElement).checked;
updateFilter();
}
</script>
<svelte:head>
@@ -118,10 +223,10 @@
</svelte:head>
<main class="grid grid-cols-1 md:grid-cols-[430px_1fr] gap-4 lg:gap-4 md:mx-4">
<div
class="list-list md:mx-auto rounded-xl border border-input-border max-h-full w-full order-1 md:order-none"
class="list-list relative md:mx-auto rounded-xl border border-input-border max-h-full w-full order-1 md:order-none"
>
<div
class="flex gap-x-4 items-center justify-between p-4 bg-background z-50 rounded-xl"
class="flex gap-x-3 items-center px-3 py-4 bg-background z-50 rounded-xl"
>
<button
class="btn-icon"
@@ -129,29 +234,101 @@
disabled={!selectedList}
on:click={back}><i class="fa fa-arrow-left"></i></button
>
<a class="btn-primary btn-large text-center" href="/lists/edit/new"
><i class="fa fa-plus mr-2"></i>{$_("new-list")}</a
<Search bind:value={data.filter.q} on:update={updateFilter}
></Search>
<button
class="btn-icon"
on:click={() => (filterExpanded = !filterExpanded)}
><i class="fa fa-sliders"></i></button
>
<a
class="btn-primary tooltip"
data-title={$_("new-list")}
href="/lists/edit/new"><i class="fa fa-plus"></i></a
>
<!-- <button type="button" class="btn-icon" on:click={toggleMap}
><i class="fa-regular fa-{showMap ? 'rectangle-list' : 'map'}"
></i></button
> -->
</div>
{#if filterExpanded}
<div
class="absolute bg-background z-10 shadow-lg rounded-b-xl w-full px-14"
in:slide
out:slide
>
<p class="text-sm font-medium pb-1">{$_("sort")}</p>
<div class="flex items-center gap-2">
<Select
bind:value={data.filter.sort}
items={sortOptions}
on:change={(e) => setSort(e.detail)}
></Select>
<button
id="sort-order-btn"
class="btn-icon"
class:rotated={data.filter.sortOrder == "-"}
on:click={() => setSortOrder()}
><i class="fa fa-arrow-up"></i></button
>
</div>
{#if $currentUser}
<hr class="my-4 border-separator" />
<UserSearch
on:click={(e) => setAuthorFilter(e.detail)}
on:clear={clearAuthorFilter}
bind:value={userQuery}
clearAfterSelect={false}
label={$_("author")}
></UserSearch>
<div class="flex items-center my-4">
<input
id="public-checkbox"
type="checkbox"
checked={data.filter.public}
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
on:change={setPublicFilter}
/>
<label for="public-checkbox" class="ms-2 text-sm"
>{$_("public")}</label
>
</div>
<div class="flex items-center my-4">
<input
id="shared-checkbox"
type="checkbox"
checked={data.filter.shared}
class="w-4 h-4 bg-input-background accent-primary border-input-border focus:ring-input-ring focus:ring-2"
on:change={setSharedFilter}
/>
<label for="shared-checkbox" class="ms-2 text-sm"
>{$_("shared")}</label
>
</div>
{/if}
</div>
{/if}
<hr class="border-separator" />
<div>
<div
id="list-container"
class="overflow-y-scroll overflow-x-clip max-h-full"
on:scroll={onListScroll}
>
{#if !selectedList}
<div class="px-4 mt-2">
{#each $lists as item, i}
<div
class="list-list-item my-1"
on:click={() => setCurrentList(item)}
role="presentation"
>
<ListCard list={item}></ListCard>
</div>
{/each}
<div class="px-4 mt-2" class:space-y-3={loading}>
{#if loading}
{#each { length: 3 } as _, index}
<SkeletonCard fullWidth></SkeletonCard>
{/each}
{:else}
{#each data.lists.items as item, i}
<div
class="list-list-item"
on:click={() => setCurrentList(item)}
role="presentation"
>
<ListCard list={item}></ListCard>
</div>
{/each}
{/if}
</div>
{:else if selectedList && !selectedTrail}
<ListPanel
@@ -167,7 +344,7 @@
{/if}
</div>
</div>
<div id="trail-map" class="md:sticky md:top-[62px]" class:hidden={!showMap}>
<div id="trail-map" class:hidden={!showMap}>
<MapWithElevationMaplibre
trails={selectedList?.expand?.trails ?? []}
bind:map
@@ -192,7 +369,13 @@
on:confirm={deleteList}
></ConfirmModal>
{#if selectedList}
<ListShareModal list={selectedList} bind:openShareModal
<ListShareModal
list={selectedList}
bind:openShareModal
on:update={() => {
data.lists.items = data.lists.items;
selectedList = selectedList;
}}
></ListShareModal>
{/if}
</main>
@@ -202,5 +385,9 @@
#trail-map {
height: calc(100vh - 124px);
}
#list-container {
height: calc(100vh - 204px);
}
}
</style>

View File

@@ -1,6 +1,35 @@
import { lists_index } from "$lib/stores/list_store";
import type { Load } from "@sveltejs/kit";
import type { List, ListFilter } from "$lib/models/list";
import { lists_index, lists_show } from "$lib/stores/list_store";
import { error, type Load } from "@sveltejs/kit";
import { ClientResponseError, type ListResult } from "pocketbase";
export const load: Load = async ({ params, fetch, url }) => {
await lists_index(undefined, fetch);
const filter: ListFilter = {
q: "",
author: "",
shared: true,
public: true,
sort: "created",
sortOrder: "+",
};
let lists: ListResult<List>;
if (url.searchParams.get("list")) {
try {
const list = await lists_show(url.searchParams.get("list") ?? "", fetch)
lists = { items: [list], page: 1, perPage: 1, totalItems: 1, totalPages: 1 }
} catch (e) {
if (e instanceof ClientResponseError && e.status == 404) {
error(404, {
message: 'Not found'
});
}
throw e
}
} else {
lists = await lists_index(filter, 1, undefined, fetch);
}
return { lists, filter }
};

View File

@@ -82,7 +82,8 @@
if ($form.id) {
await lists_update($form, avatarFile);
} else {
await lists_create($form, avatarFile);
const createdList = await lists_create($form, avatarFile);
$form.id = createdList.id;
}
show_toast({
type: "success",

View File

@@ -14,5 +14,4 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
}
}
await lists_index(undefined, fetch);
};

View File

@@ -1,4 +1,3 @@
import { lists_index } from "$lib/stores/list_store";
import { trails_show } from "$lib/stores/trail_store";
import { error, type Load } from "@sveltejs/kit";
import { ClientResponseError } from "pocketbase";
@@ -13,7 +12,6 @@ export const load: Load = async ({ params, fetch }) => {
});
}
console.log(e);
}
await lists_index(undefined, fetch);
};

View File

@@ -22,10 +22,9 @@
import { Waypoint } from "$lib/models/waypoint";
import { categories } from "$lib/stores/category_store";
import {
lists,
lists_add_trail,
lists_index,
lists_remove_trail,
lists_remove_trail
} from "$lib/stores/list_store";
import { summitLog } from "$lib/stores/summit_log_store";
import { show_toast } from "$lib/stores/toast_store";
@@ -34,6 +33,7 @@
trails_create,
trails_update,
} from "$lib/stores/trail_store";
import { currentUser } from "$lib/stores/user_store.js";
import {
anchors,
appendToRoute,
@@ -53,11 +53,7 @@
} from "$lib/util/format_util";
import {
fromFile,
fromFIT,
fromKML,
fromTCX,
gpx2trail,
isFITFile,
gpx2trail
} from "$lib/util/gpx_util";
import {
@@ -73,7 +69,7 @@
import { scale } from "svelte/transition";
import { array, number, object, string } from "yup";
export let data: { trail: Trail };
export let data;
let map: M.Map;
@@ -455,7 +451,7 @@
} else {
await lists_add_trail(list, $form);
}
await lists_index();
await lists_index({ q: "", author: $currentUser?.id ?? "" }, 1, -1);
} catch (e) {
console.error(e);
show_toast({
@@ -844,13 +840,13 @@
on:click={beforeSummitLogModalOpen}
><i class="fa fa-plus mr-2"></i>{$_("add-entry")}</button
>
{#if $lists.length}
{#if data.lists.items.length}
<hr class="border-separator" />
<h3 class="text-xl font-semibold">
{$_("list", { values: { n: 2 } })}
</h3>
<div class="flex gap-4 flex-wrap">
{#each $lists as list}
{#each data.lists.items as list}
{#if $form.id && list.trails?.includes($form.id)}
<div
class="flex gap-2 items-center border border-input-border rounded-xl p-2"
@@ -920,6 +916,7 @@
<SummitLogModal bind:openModal={openSummitLogModal} on:save={saveSummitLog}
></SummitLogModal>
<ListSelectModal
lists={data.lists.items}
bind:openModal={openListSelectModal}
on:change={(e) => handleListSelection(e.detail)}
></ListSelectModal>

View File

@@ -1,15 +1,16 @@
import { Trail } from "$lib/models/trail";
import { pb } from "$lib/pocketbase";
import { categories_index } from "$lib/stores/category_store";
import { lists_index } from "$lib/stores/list_store";
import { trails_show } from "$lib/stores/trail_store";
import { error, type Load } from "@sveltejs/kit";
export const load: Load = async ({ params, fetch, data }) => {
export const load: Load = async ({ params, fetch }) => {
if (!params.id) {
return error(400, "Bad Request")
}
const categories = await categories_index(fetch)
const lists = await lists_index(undefined, fetch)
const lists = await lists_index({ q: "", author: pb.authStore.model?.id ?? "" }, 1, -1, fetch)
let trail: Trail;
if (params.id === "new") {
@@ -18,5 +19,5 @@ export const load: Load = async ({ params, fetch, data }) => {
trail = await trails_show(params.id, true, fetch);
}
return { trail: trail }
return { trail: trail, lists: lists }
};

View File

@@ -2,9 +2,11 @@
import TrailInfoPanel from "$lib/components/trail/trail_info_panel.svelte";
import { trail } from "$lib/stores/trail_store";
import { _ } from "svelte-i18n";
export let data;
</script>
<TrailInfoPanel trail={$trail} mode="overview"></TrailInfoPanel>
<TrailInfoPanel trail={data.trail} mode="overview"></TrailInfoPanel>
<svelte:head>
<title>{$trail.name} | {$_("trail", { values: { n: 1 } })} | wanderer</title

View File

@@ -1,3 +1,4 @@
import { pb } from "$lib/pocketbase";
import { lists_index } from "$lib/stores/list_store";
import { trails_show } from "$lib/stores/trail_store";
import { error, type Load } from "@sveltejs/kit";
@@ -5,7 +6,9 @@ import { ClientResponseError } from "pocketbase";
export const load: Load = async ({ params, fetch }) => {
try {
await trails_show(params.id!, true, fetch)
const trail = await trails_show(params.id!, true, fetch)
return { trail }
} catch (e) {
if (e instanceof ClientResponseError && e.status == 404) {
error(404, {
@@ -13,7 +16,6 @@ export const load: Load = async ({ params, fetch }) => {
});
}
console.log(e);
}
await lists_index(undefined, fetch);
};