adds lists search and filter
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let fullWidth: boolean = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div
|
<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"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<!-- Image placeholder -->
|
<!-- Image placeholder -->
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<button class="btn-secondary" on:click={closeModal}
|
<button class="btn-secondary" on:click={closeModal}
|
||||||
>{$_("cancel")}</button
|
>{$_("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
|
>{$_(action)}</button
|
||||||
>
|
>
|
||||||
</div></Modal
|
</div></Modal
|
||||||
|
|||||||
@@ -51,18 +51,15 @@
|
|||||||
<i class="fa fa-table-list text-5xl"></i>
|
<i class="fa fa-table-list text-5xl"></i>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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">
|
<div class="flex items-center gap-3">
|
||||||
<h5
|
<h5
|
||||||
class="text-xl font-semibold overflow-hidden overflow-ellipsis basis-full"
|
class="text-xl font-semibold overflow-hidden overflow-ellipsis"
|
||||||
>
|
>
|
||||||
{list.name}
|
{list.name}
|
||||||
</h5>
|
</h5>
|
||||||
{#if list.public}
|
{#if list.public}
|
||||||
<span
|
<span class="tooltip" data-title={$_("public")}>
|
||||||
class="tooltip"
|
|
||||||
data-title={$_("public")}
|
|
||||||
>
|
|
||||||
<i class="fa fa-globe"></i>
|
<i class="fa fa-globe"></i>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -70,6 +67,21 @@
|
|||||||
<ShareInfo type="list" subject={list}></ShareInfo>
|
<ShareInfo type="list" subject={list}></ShareInfo>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</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
|
<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"
|
class="grid grid-cols-2 mt-1 mb-2 gap-x-4 gap-y-1 text-sm text-gray-500 whitespace-nowrap flex-wrap"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -42,8 +42,12 @@
|
|||||||
list.author == $currentUser?.id ||
|
list.author == $currentUser?.id ||
|
||||||
list.expand?.list_share_via_list?.some((s) => s.permission == "edit");
|
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 = [
|
$: dropdownItems = [
|
||||||
...(list.author == $currentUser?.id
|
...(allowShare
|
||||||
? [{ text: $_("share"), value: "share", icon: "share" }]
|
? [{ text: $_("share"), value: "share", icon: "share" }]
|
||||||
: []),
|
: []),
|
||||||
...(allowEdit
|
...(allowEdit
|
||||||
@@ -122,7 +126,23 @@
|
|||||||
|
|
||||||
<div class="p-4 md:p-6">
|
<div class="p-4 md:p-6">
|
||||||
<h4 class="text-2xl font-semibold mb-4">{list.name}</h4>
|
<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 />
|
<hr />
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-2 my-4 gap-4 font-semibold whitespace-nowrap flex-wrap justify-around"
|
class="grid grid-cols-2 my-4 gap-4 font-semibold whitespace-nowrap flex-wrap justify-around"
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
import type { List } from "$lib/models/list";
|
import type { List } from "$lib/models/list";
|
||||||
import { lists } from "$lib/stores/list_store";
|
|
||||||
import { trail } from "$lib/stores/trail_store";
|
import { trail } from "$lib/stores/trail_store";
|
||||||
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import Modal from "../base/modal.svelte";
|
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 openModal: (() => void) | undefined = undefined;
|
||||||
export let closeModal: (() => void) | undefined = undefined;
|
export let closeModal: (() => void) | undefined = undefined;
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
<slot {openModal} />
|
<slot {openModal} />
|
||||||
|
|
||||||
<ul slot="content">
|
<ul slot="content">
|
||||||
{#each $lists as list}
|
{#each lists as list}
|
||||||
<li
|
<li
|
||||||
class="flex gap-4 items-center p-4 hover:bg-menu-item-background-hover rounded-xl transition-colors cursor-pointer"
|
class="flex gap-4 items-center p-4 hover:bg-menu-item-background-hover rounded-xl transition-colors cursor-pointer"
|
||||||
on:click={() => handleSelect(list)}
|
on:click={() => handleSelect(list)}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import type { List } from "$lib/models/list";
|
import type { List } from "$lib/models/list";
|
||||||
import { ListShare } from "$lib/models/list_share";
|
import { ListShare } from "$lib/models/list_share";
|
||||||
import { TrailShare } from "$lib/models/trail_share";
|
import { TrailShare } from "$lib/models/trail_share";
|
||||||
import type { User } from "$lib/models/user";
|
|
||||||
import {
|
import {
|
||||||
list_share_create,
|
list_share_create,
|
||||||
list_share_delete,
|
list_share_delete,
|
||||||
@@ -11,19 +10,15 @@
|
|||||||
list_share_update,
|
list_share_update,
|
||||||
shares,
|
shares,
|
||||||
} from "$lib/stores/list_share_store";
|
} from "$lib/stores/list_share_store";
|
||||||
import { lists } from "$lib/stores/list_store";
|
|
||||||
import { show_toast } from "$lib/stores/toast_store";
|
|
||||||
import {
|
import {
|
||||||
trail_share_create,
|
trail_share_create,
|
||||||
trail_share_delete,
|
trail_share_delete,
|
||||||
trail_share_index,
|
trail_share_index,
|
||||||
} from "$lib/stores/trail_share_store";
|
} from "$lib/stores/trail_share_store";
|
||||||
import { users_search } from "$lib/stores/user_store";
|
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import Button from "../base/button.svelte";
|
import Button from "../base/button.svelte";
|
||||||
import Search, { type SearchItem } from "../base/search.svelte";
|
|
||||||
import type { SelectItem } from "../base/select.svelte";
|
import type { SelectItem } from "../base/select.svelte";
|
||||||
import Select from "../base/select.svelte";
|
import Select from "../base/select.svelte";
|
||||||
import UserSearch from "../user_search.svelte";
|
import UserSearch from "../user_search.svelte";
|
||||||
@@ -116,8 +111,8 @@
|
|||||||
trails: list.expand?.trails ?? [],
|
trails: list.expand?.trails ?? [],
|
||||||
list_share_via_list: fetchedShares,
|
list_share_via_list: fetchedShares,
|
||||||
};
|
};
|
||||||
lists.set($lists);
|
sharesLoading = false;
|
||||||
sharesLoading = false;
|
dispatch("update", list)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
></i>
|
></i>
|
||||||
{#if showInfo}
|
{#if showInfo}
|
||||||
<ul
|
<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}}
|
in:fly={{y: -10, duration: 150}} out:fly={{y: -10, duration: 150}}
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
let openExportModal: () => void;
|
let openExportModal: () => void;
|
||||||
let openShareModal: () => void;
|
let openShareModal: () => void;
|
||||||
|
|
||||||
|
let lists: List[] = [];
|
||||||
|
|
||||||
const allowEdit =
|
const allowEdit =
|
||||||
trail.author == $currentUser?.id ||
|
trail.author == $currentUser?.id ||
|
||||||
trail.expand.trail_share_via_trail?.some((s) => s.permission == "edit");
|
trail.expand.trail_share_via_trail?.some((s) => s.permission == "edit");
|
||||||
@@ -76,6 +78,13 @@
|
|||||||
: `/trail/view/${trail.id!}`,
|
: `/trail/view/${trail.id!}`,
|
||||||
);
|
);
|
||||||
} else if (item.value == "list") {
|
} else if (item.value == "list") {
|
||||||
|
lists = (
|
||||||
|
await lists_index(
|
||||||
|
{ q: "", author: $currentUser?.id ?? "" },
|
||||||
|
1,
|
||||||
|
-1,
|
||||||
|
)
|
||||||
|
).items;
|
||||||
openListSelectModal();
|
openListSelectModal();
|
||||||
} else if (item.value == "direction") {
|
} else if (item.value == "direction") {
|
||||||
window
|
window
|
||||||
@@ -175,7 +184,6 @@
|
|||||||
text: `${$_("added-trail-to")} "${list.name}"`,
|
text: `${$_("added-trail-to")} "${list.name}"`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await lists_index();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
@@ -206,6 +214,7 @@
|
|||||||
on:confirm={deleteTrail}
|
on:confirm={deleteTrail}
|
||||||
></ConfirmModal>
|
></ConfirmModal>
|
||||||
<ListSelectModal
|
<ListSelectModal
|
||||||
|
{lists}
|
||||||
bind:openModal={openListSelectModal}
|
bind:openModal={openListSelectModal}
|
||||||
on:change={(e) => handleListSelection(e.detail)}
|
on:change={(e) => handleListSelection(e.detail)}
|
||||||
></ListSelectModal>
|
></ListSelectModal>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
import type { Trail } from "./trail";
|
import type { Trail } from "./trail";
|
||||||
import type { ListShare } from "./list_share";
|
import type { ListShare } from "./list_share";
|
||||||
|
import type { UserAnonymous } from "./user";
|
||||||
|
|
||||||
export class List {
|
export class List {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -12,6 +13,7 @@ export class List {
|
|||||||
expand?: {
|
expand?: {
|
||||||
trails?: Trail[]
|
trails?: Trail[]
|
||||||
list_share_via_list?: ListShare[]
|
list_share_via_list?: ListShare[]
|
||||||
|
author?: UserAnonymous;
|
||||||
|
|
||||||
}
|
}
|
||||||
author?: string;
|
author?: string;
|
||||||
@@ -29,8 +31,11 @@ export class List {
|
|||||||
|
|
||||||
export interface ListFilter {
|
export interface ListFilter {
|
||||||
q: string,
|
q: string,
|
||||||
sort: "name" | "size" | "created";
|
sort?: "name" | "size" | "created";
|
||||||
sortOrder: "+" | "-"
|
author?: string;
|
||||||
|
public?: boolean;
|
||||||
|
shared?: boolean;
|
||||||
|
sortOrder?: "+" | "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const listSchema = object<List>({
|
export const listSchema = object<List>({
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import { List, type ListFilter } from "$lib/models/list";
|
import { List, type ListFilter } from "$lib/models/list";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { pb } from "$lib/pocketbase";
|
import { pb } from "$lib/pocketbase";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { ClientResponseError, type ListResult } from "pocketbase";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { get, writable, type Writable } from "svelte/store";
|
||||||
import { writable, type Writable } from "svelte/store";
|
|
||||||
import { fetchGPX } from "./trail_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 list: Writable<List | null> = writable(null)
|
||||||
export const listTrail: Writable<Trail | 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({
|
const r = await f('/api/v1/list?' + new URLSearchParams({
|
||||||
sort: `${filter?.sortOrder ?? "-"}${filter?.sort ?? "name"}`,
|
sort: `${filter?.sortOrder ?? "-"}${filter?.sort ?? "name"}`,
|
||||||
|
"per-page": perPage.toString(),
|
||||||
|
page: page.toString(),
|
||||||
|
filter: filterText
|
||||||
}), {
|
}), {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
@@ -21,15 +25,13 @@ export async function lists_index(filter?: ListFilter, f: (url: RequestInfo | UR
|
|||||||
throw new ClientResponseError(await r.json())
|
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) {
|
lists = result;
|
||||||
// list.set(fetchedLists[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
const response = await r.json()
|
||||||
|
|
||||||
for (const trail of response.expand?.trails ?? []) {
|
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;
|
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)
|
throw new ClientResponseError(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
list.set(response);
|
list.set(response);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -85,7 +86,9 @@ export async function lists_create(list: List, avatar?: File) {
|
|||||||
body: formData,
|
body: formData,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!r.ok) {
|
if (r.ok) {
|
||||||
|
return await r.json()
|
||||||
|
} else {
|
||||||
throw new ClientResponseError(await r.json())
|
throw new ClientResponseError(await r.json())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,4 +155,28 @@ export async function lists_delete(list: List) {
|
|||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new ClientResponseError(await r.json())
|
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
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,35 @@ import { pb } from '$lib/pocketbase';
|
|||||||
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
import { error, json, type RequestEvent } from '@sveltejs/kit';
|
||||||
|
|
||||||
export async function GET(event: RequestEvent) {
|
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 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 {
|
try {
|
||||||
const r: List[] = await pb.collection('lists').getFullList<List>({
|
let r;
|
||||||
expand: "trails,trails.waypoints,trails.category,list_share_via_list",
|
if (parseInt(perPage) < 0) {
|
||||||
sort: sort,
|
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)
|
return json(r)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw error(e.status, e);
|
throw error(e.status, e);
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import type { DropdownItem } from "$lib/components/base/dropdown.svelte";
|
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 ConfirmModal from "$lib/components/confirm_modal.svelte";
|
||||||
import ListCard from "$lib/components/list/list_card.svelte";
|
import ListCard from "$lib/components/list/list_card.svelte";
|
||||||
import ListPanel from "$lib/components/list/list_panel.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 MapWithElevationMaplibre from "$lib/components/trail/map_with_elevation_maplibre.svelte";
|
||||||
import TrailInfoPanel from "$lib/components/trail/trail_info_panel.svelte";
|
import TrailInfoPanel from "$lib/components/trail/trail_info_panel.svelte";
|
||||||
import TrailList from "$lib/components/trail/trail_list.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 { List } from "$lib/models/list";
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import {
|
import { lists_delete, lists_index } from "$lib/stores/list_store";
|
||||||
lists,
|
|
||||||
lists_delete,
|
|
||||||
lists_index
|
|
||||||
} from "$lib/stores/list_store";
|
|
||||||
import { fetchGPX } from "$lib/stores/trail_store";
|
import { fetchGPX } from "$lib/stores/trail_store";
|
||||||
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
import * as M from "maplibre-gl";
|
import * as M from "maplibre-gl";
|
||||||
|
|
||||||
import { onMount, tick } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
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 openConfirmModal: () => void;
|
||||||
let openShareModal: () => void;
|
let openShareModal: () => void;
|
||||||
@@ -30,19 +47,28 @@
|
|||||||
let markers: M.Marker[];
|
let markers: M.Marker[];
|
||||||
let showMap: boolean = true;
|
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 selectedTrail: Trail | null = null;
|
||||||
|
|
||||||
let activeTrailIndex: number = -1;
|
let activeTrailIndex: number = -1;
|
||||||
|
|
||||||
|
let loading: boolean = false;
|
||||||
|
let loadingNextPage: boolean = false;
|
||||||
|
let filterExpanded: boolean = false;
|
||||||
|
|
||||||
|
let loadAllListsOnNextBack = false;
|
||||||
|
|
||||||
|
let userQuery = "";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if ($page.url.searchParams.get("list")) {
|
if ($page.url.searchParams.get("list") && selectedList) {
|
||||||
const listToFocus = $lists.find(
|
setCurrentList(selectedList);
|
||||||
(l) => l.id == $page.url.searchParams.get("list"),
|
|
||||||
);
|
// only the requested list has been loaded at this point
|
||||||
if (listToFocus) {
|
// load all lists the next time the user presses the back button
|
||||||
setCurrentList(listToFocus);
|
loadAllListsOnNextBack = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,7 +92,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await lists_delete(selectedList);
|
await lists_delete(selectedList);
|
||||||
await lists_index();
|
await updateFilter();
|
||||||
selectedList = null;
|
selectedList = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +108,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectedList = item;
|
selectedList = item;
|
||||||
|
document.getElementById("list-container")?.scrollTo({ top: 0 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function back() {
|
async function back() {
|
||||||
|
if (loadAllListsOnNextBack) {
|
||||||
|
await updateFilter();
|
||||||
|
loadAllListsOnNextBack = false;
|
||||||
|
}
|
||||||
if (selectedTrail) {
|
if (selectedTrail) {
|
||||||
mapWithElevation.unFocusTrail(selectedTrail);
|
mapWithElevation.unFocusTrail(selectedTrail);
|
||||||
selectedTrail = null;
|
selectedTrail = null;
|
||||||
@@ -111,6 +142,80 @@
|
|||||||
function unHighlightTrail(trail: Trail) {
|
function unHighlightTrail(trail: Trail) {
|
||||||
mapWithElevation.unHighlightTrail(trail.id!);
|
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>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -118,10 +223,10 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
<main class="grid grid-cols-1 md:grid-cols-[430px_1fr] gap-4 lg:gap-4 md:mx-4">
|
<main class="grid grid-cols-1 md:grid-cols-[430px_1fr] gap-4 lg:gap-4 md:mx-4">
|
||||||
<div
|
<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
|
<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
|
<button
|
||||||
class="btn-icon"
|
class="btn-icon"
|
||||||
@@ -129,29 +234,101 @@
|
|||||||
disabled={!selectedList}
|
disabled={!selectedList}
|
||||||
on:click={back}><i class="fa fa-arrow-left"></i></button
|
on:click={back}><i class="fa fa-arrow-left"></i></button
|
||||||
>
|
>
|
||||||
<a class="btn-primary btn-large text-center" href="/lists/edit/new"
|
<Search bind:value={data.filter.q} on:update={updateFilter}
|
||||||
><i class="fa fa-plus mr-2"></i>{$_("new-list")}</a
|
></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>
|
</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" />
|
<hr class="border-separator" />
|
||||||
|
|
||||||
<div>
|
<div
|
||||||
|
id="list-container"
|
||||||
|
class="overflow-y-scroll overflow-x-clip max-h-full"
|
||||||
|
on:scroll={onListScroll}
|
||||||
|
>
|
||||||
{#if !selectedList}
|
{#if !selectedList}
|
||||||
<div class="px-4 mt-2">
|
<div class="px-4 mt-2" class:space-y-3={loading}>
|
||||||
{#each $lists as item, i}
|
{#if loading}
|
||||||
<div
|
{#each { length: 3 } as _, index}
|
||||||
class="list-list-item my-1"
|
<SkeletonCard fullWidth></SkeletonCard>
|
||||||
on:click={() => setCurrentList(item)}
|
{/each}
|
||||||
role="presentation"
|
{:else}
|
||||||
>
|
{#each data.lists.items as item, i}
|
||||||
<ListCard list={item}></ListCard>
|
<div
|
||||||
</div>
|
class="list-list-item"
|
||||||
{/each}
|
on:click={() => setCurrentList(item)}
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<ListCard list={item}></ListCard>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if selectedList && !selectedTrail}
|
{:else if selectedList && !selectedTrail}
|
||||||
<ListPanel
|
<ListPanel
|
||||||
@@ -167,7 +344,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="trail-map" class="md:sticky md:top-[62px]" class:hidden={!showMap}>
|
<div id="trail-map" class:hidden={!showMap}>
|
||||||
<MapWithElevationMaplibre
|
<MapWithElevationMaplibre
|
||||||
trails={selectedList?.expand?.trails ?? []}
|
trails={selectedList?.expand?.trails ?? []}
|
||||||
bind:map
|
bind:map
|
||||||
@@ -192,7 +369,13 @@
|
|||||||
on:confirm={deleteList}
|
on:confirm={deleteList}
|
||||||
></ConfirmModal>
|
></ConfirmModal>
|
||||||
{#if selectedList}
|
{#if selectedList}
|
||||||
<ListShareModal list={selectedList} bind:openShareModal
|
<ListShareModal
|
||||||
|
list={selectedList}
|
||||||
|
bind:openShareModal
|
||||||
|
on:update={() => {
|
||||||
|
data.lists.items = data.lists.items;
|
||||||
|
selectedList = selectedList;
|
||||||
|
}}
|
||||||
></ListShareModal>
|
></ListShareModal>
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
@@ -202,5 +385,9 @@
|
|||||||
#trail-map {
|
#trail-map {
|
||||||
height: calc(100vh - 124px);
|
height: calc(100vh - 124px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#list-container {
|
||||||
|
height: calc(100vh - 204px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,35 @@
|
|||||||
import { lists_index } from "$lib/stores/list_store";
|
import type { List, ListFilter } from "$lib/models/list";
|
||||||
import type { Load } from "@sveltejs/kit";
|
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 }) => {
|
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 }
|
||||||
};
|
};
|
||||||
@@ -82,7 +82,8 @@
|
|||||||
if ($form.id) {
|
if ($form.id) {
|
||||||
await lists_update($form, avatarFile);
|
await lists_update($form, avatarFile);
|
||||||
} else {
|
} else {
|
||||||
await lists_create($form, avatarFile);
|
const createdList = await lists_create($form, avatarFile);
|
||||||
|
$form.id = createdList.id;
|
||||||
}
|
}
|
||||||
show_toast({
|
show_toast({
|
||||||
type: "success",
|
type: "success",
|
||||||
|
|||||||
@@ -14,5 +14,4 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
await lists_index(undefined, fetch);
|
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { lists_index } from "$lib/stores/list_store";
|
|
||||||
import { trails_show } from "$lib/stores/trail_store";
|
import { trails_show } from "$lib/stores/trail_store";
|
||||||
import { error, type Load } from "@sveltejs/kit";
|
import { error, type Load } from "@sveltejs/kit";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
@@ -13,7 +12,6 @@ export const load: Load = async ({ params, fetch }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
await lists_index(undefined, fetch);
|
|
||||||
};
|
};
|
||||||
@@ -22,10 +22,9 @@
|
|||||||
import { Waypoint } from "$lib/models/waypoint";
|
import { Waypoint } from "$lib/models/waypoint";
|
||||||
import { categories } from "$lib/stores/category_store";
|
import { categories } from "$lib/stores/category_store";
|
||||||
import {
|
import {
|
||||||
lists,
|
|
||||||
lists_add_trail,
|
lists_add_trail,
|
||||||
lists_index,
|
lists_index,
|
||||||
lists_remove_trail,
|
lists_remove_trail
|
||||||
} from "$lib/stores/list_store";
|
} from "$lib/stores/list_store";
|
||||||
import { summitLog } from "$lib/stores/summit_log_store";
|
import { summitLog } from "$lib/stores/summit_log_store";
|
||||||
import { show_toast } from "$lib/stores/toast_store";
|
import { show_toast } from "$lib/stores/toast_store";
|
||||||
@@ -34,6 +33,7 @@
|
|||||||
trails_create,
|
trails_create,
|
||||||
trails_update,
|
trails_update,
|
||||||
} from "$lib/stores/trail_store";
|
} from "$lib/stores/trail_store";
|
||||||
|
import { currentUser } from "$lib/stores/user_store.js";
|
||||||
import {
|
import {
|
||||||
anchors,
|
anchors,
|
||||||
appendToRoute,
|
appendToRoute,
|
||||||
@@ -53,11 +53,7 @@
|
|||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import {
|
import {
|
||||||
fromFile,
|
fromFile,
|
||||||
fromFIT,
|
gpx2trail
|
||||||
fromKML,
|
|
||||||
fromTCX,
|
|
||||||
gpx2trail,
|
|
||||||
isFITFile,
|
|
||||||
} from "$lib/util/gpx_util";
|
} from "$lib/util/gpx_util";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -73,7 +69,7 @@
|
|||||||
import { scale } from "svelte/transition";
|
import { scale } from "svelte/transition";
|
||||||
import { array, number, object, string } from "yup";
|
import { array, number, object, string } from "yup";
|
||||||
|
|
||||||
export let data: { trail: Trail };
|
export let data;
|
||||||
|
|
||||||
let map: M.Map;
|
let map: M.Map;
|
||||||
|
|
||||||
@@ -455,7 +451,7 @@
|
|||||||
} else {
|
} else {
|
||||||
await lists_add_trail(list, $form);
|
await lists_add_trail(list, $form);
|
||||||
}
|
}
|
||||||
await lists_index();
|
await lists_index({ q: "", author: $currentUser?.id ?? "" }, 1, -1);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
show_toast({
|
show_toast({
|
||||||
@@ -844,13 +840,13 @@
|
|||||||
on:click={beforeSummitLogModalOpen}
|
on:click={beforeSummitLogModalOpen}
|
||||||
><i class="fa fa-plus mr-2"></i>{$_("add-entry")}</button
|
><i class="fa fa-plus mr-2"></i>{$_("add-entry")}</button
|
||||||
>
|
>
|
||||||
{#if $lists.length}
|
{#if data.lists.items.length}
|
||||||
<hr class="border-separator" />
|
<hr class="border-separator" />
|
||||||
<h3 class="text-xl font-semibold">
|
<h3 class="text-xl font-semibold">
|
||||||
{$_("list", { values: { n: 2 } })}
|
{$_("list", { values: { n: 2 } })}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex gap-4 flex-wrap">
|
<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)}
|
{#if $form.id && list.trails?.includes($form.id)}
|
||||||
<div
|
<div
|
||||||
class="flex gap-2 items-center border border-input-border rounded-xl p-2"
|
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 bind:openModal={openSummitLogModal} on:save={saveSummitLog}
|
||||||
></SummitLogModal>
|
></SummitLogModal>
|
||||||
<ListSelectModal
|
<ListSelectModal
|
||||||
|
lists={data.lists.items}
|
||||||
bind:openModal={openListSelectModal}
|
bind:openModal={openListSelectModal}
|
||||||
on:change={(e) => handleListSelection(e.detail)}
|
on:change={(e) => handleListSelection(e.detail)}
|
||||||
></ListSelectModal>
|
></ListSelectModal>
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Trail } from "$lib/models/trail";
|
import { Trail } from "$lib/models/trail";
|
||||||
|
import { pb } from "$lib/pocketbase";
|
||||||
import { categories_index } from "$lib/stores/category_store";
|
import { categories_index } from "$lib/stores/category_store";
|
||||||
import { lists_index } from "$lib/stores/list_store";
|
import { lists_index } from "$lib/stores/list_store";
|
||||||
import { trails_show } from "$lib/stores/trail_store";
|
import { trails_show } from "$lib/stores/trail_store";
|
||||||
import { error, type Load } from "@sveltejs/kit";
|
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) {
|
if (!params.id) {
|
||||||
return error(400, "Bad Request")
|
return error(400, "Bad Request")
|
||||||
}
|
}
|
||||||
const categories = await categories_index(fetch)
|
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;
|
let trail: Trail;
|
||||||
if (params.id === "new") {
|
if (params.id === "new") {
|
||||||
@@ -18,5 +19,5 @@ export const load: Load = async ({ params, fetch, data }) => {
|
|||||||
trail = await trails_show(params.id, true, fetch);
|
trail = await trails_show(params.id, true, fetch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { trail: trail }
|
return { trail: trail, lists: lists }
|
||||||
};
|
};
|
||||||
@@ -2,9 +2,11 @@
|
|||||||
import TrailInfoPanel from "$lib/components/trail/trail_info_panel.svelte";
|
import TrailInfoPanel from "$lib/components/trail/trail_info_panel.svelte";
|
||||||
import { trail } from "$lib/stores/trail_store";
|
import { trail } from "$lib/stores/trail_store";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
|
export let data;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TrailInfoPanel trail={$trail} mode="overview"></TrailInfoPanel>
|
<TrailInfoPanel trail={data.trail} mode="overview"></TrailInfoPanel>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{$trail.name} | {$_("trail", { values: { n: 1 } })} | wanderer</title
|
<title>{$trail.name} | {$_("trail", { values: { n: 1 } })} | wanderer</title
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { pb } from "$lib/pocketbase";
|
||||||
import { lists_index } from "$lib/stores/list_store";
|
import { lists_index } from "$lib/stores/list_store";
|
||||||
import { trails_show } from "$lib/stores/trail_store";
|
import { trails_show } from "$lib/stores/trail_store";
|
||||||
import { error, type Load } from "@sveltejs/kit";
|
import { error, type Load } from "@sveltejs/kit";
|
||||||
@@ -5,7 +6,9 @@ import { ClientResponseError } from "pocketbase";
|
|||||||
|
|
||||||
export const load: Load = async ({ params, fetch }) => {
|
export const load: Load = async ({ params, fetch }) => {
|
||||||
try {
|
try {
|
||||||
await trails_show(params.id!, true, fetch)
|
const trail = await trails_show(params.id!, true, fetch)
|
||||||
|
|
||||||
|
return { trail }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof ClientResponseError && e.status == 404) {
|
if (e instanceof ClientResponseError && e.status == 404) {
|
||||||
error(404, {
|
error(404, {
|
||||||
@@ -13,7 +16,6 @@ export const load: Load = async ({ params, fetch }) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(e);
|
console.log(e);
|
||||||
|
|
||||||
}
|
}
|
||||||
await lists_index(undefined, fetch);
|
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user