adds search api

This commit is contained in:
Christian Beutel
2024-03-10 00:52:45 +01:00
parent 963b589b2d
commit 01e1053569
25 changed files with 271 additions and 87 deletions

View File

@@ -0,0 +1,87 @@
<script lang="ts">
import { range } from "$lib/util/array_util";
import { createEventDispatcher } from "svelte";
export let page: number;
export let totalPages: number;
const maxPagesToRender = 6;
const dispatch = createEventDispatcher();
function update(clickedPage: number) {
if (page !== clickedPage) {
dispatch("pagination", clickedPage);
}
}
$: numberGenerator = () => {
if (totalPages < 4) {
return [];
}
if (totalPages <= maxPagesToRender) {
return range(totalPages - 2, 2);
}
if (page < maxPagesToRender - 2) {
return range(maxPagesToRender - 1, 2);
}
if (page > totalPages - 3) {
return range(totalPages - 2, totalPages - 5);
}
if (page > 2 && page <= totalPages - 3) {
return range(page + Math.max(1, maxPagesToRender - 5), page - 2);
}
return [];
};
</script>
<div class="flex gap-x-4 justify-center">
{#if totalPages > 1}
<button
class:text-gray-500={page == 1}
disabled={page == 1}
on:click={() => update(page - 1)}
><i class="fa fa-caret-left"></i></button
>
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == 1}
on:click={() => update(1)}>1</button
>
{#if totalPages > maxPagesToRender && page >= maxPagesToRender - 2 && page > 2}
<span>...</span>
{:else if totalPages > 2}
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == 2}
on:click={() => update(2)}>2</button
>
{/if}
{#each numberGenerator() as i}
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == i + 1}
on:click={() => update(i + 1)}>{i + 1}</button
>
{/each}
{#if totalPages > maxPagesToRender && page < totalPages - 3}
<span>...</span>
{:else if totalPages > 3}
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == totalPages - 1}
on:click={() => update(totalPages - 1)}>{totalPages - 1}</button
>
{/if}
<button
class="w-8 aspect-square rounded-lg transition-colors border-input-border hover:bg-menu-item-background-hover"
class:border={page == totalPages}
on:click={() => update(totalPages)}>{totalPages}</button
>
<button
class:text-gray-500={page == totalPages}
disabled={page == totalPages}
on:click={() => update(page + 1)}
><i class="fa fa-caret-right"></i></button
>
{/if}
</div>

View File

@@ -90,14 +90,14 @@
show_toast({
type: "success",
icon: "check",
text: `Removed trail from "${list.name}"`,
text: `${$_("removed-trail-from")} "${list.name}"`,
});
} else {
await lists_add_trail(list, trail);
show_toast({
type: "success",
icon: "check",
text: `Added trail to "${list.name}"`,
text: `${$_("added-trail-to")} "${list.name}"`,
});
}
await lists_index();

View File

@@ -6,13 +6,19 @@
import TrailCard from "./trail_card.svelte";
import TrailListItem from "./trail_list_item.svelte";
import { _ } from "svelte-i18n";
import Pagination from "../base/pagination.svelte";
import TextField from "../base/text_field.svelte";
export let filter: TrailFilter;
export let trails: Trail[];
export let pagination: { page: number; totalPages: number } = {
page: 1,
totalPages: 1,
};
const displayOptions: SelectItem[] = [
{ text: $_('card', {values: {n: 2}}), value: "cards" },
{ text: $_('list', {values: {n: 1}}), value: "list" },
{ text: $_("card", { values: { n: 2 } }), value: "cards" },
{ text: $_("list", { values: { n: 1 } }), value: "list" },
];
let selectedDisplayOption = displayOptions[0].value;
@@ -20,10 +26,10 @@
let dispatch = createEventDispatcher();
const sortOptions: SelectItem[] = [
{ text: $_('alphabetical'), value: "name" },
{ text: $_('creation-date'), value: "created" },
{ text: $_('distance'), value: "distance" },
{ text: $_('elevation-gain'), value: "elevation_gain" },
{ text: $_("alphabetical"), value: "name" },
{ text: $_("creation-date"), value: "created" },
{ text: $_("distance"), value: "distance" },
{ text: $_("elevation-gain"), value: "elevation_gain" },
];
onMount(() => {
@@ -37,11 +43,11 @@
localStorage.setItem("displayOption", selectedDisplayOption);
}
async function setSort() {
function setSort() {
dispatch("update", filter);
}
async function setSortOrder() {
function setSortOrder() {
if (filter.sortOrder === "+") {
filter.sortOrder = "-";
} else {
@@ -52,9 +58,16 @@
</script>
<div class="min-w-0">
<div class="flex items-start gap-8 justify-end mx-4">
<div>
<p class="text-sm text-gray-500 pb-2">{$_('sort')}</p>
<div class="flex items-end flex-wrap md:flex-nowrap gap-x-6 gap-y-2 mx-4">
<div class="basis-full order-1 md:order-none mt-6 md:mt-0">
<Pagination
page={pagination.page}
totalPages={pagination.totalPages}
on:pagination
></Pagination>
</div>
<div class="lg:shrink-0">
<p class="text-sm text-gray-500 pb-2">{$_("sort")}</p>
<div class="flex items-center gap-2">
<Select
bind:value={filter.sort}
@@ -70,8 +83,8 @@
>
</div>
</div>
<div>
<p class="text-sm text-gray-500 pb-2">{$_('display-as')}</p>
<div class="lg:shrink-0">
<p class="text-sm text-gray-500 pb-2">{$_("display-as")}</p>
<Select
bind:value={selectedDisplayOption}

View File

@@ -4,6 +4,7 @@
"add-entry": "Eintrag hinzufügen",
"add-to-list": "Zu Liste hinzufügen",
"add-waypoint": "Wegpunkt hinzufügen",
"added-trail-to": "Route hinzugefügt zu",
"alphabetical": "Alphabetisch",
"already-account": "Du hast bereits ein Konto?",
"avatar": "Avatar",
@@ -40,11 +41,12 @@
"english": "Englisch",
"est-duration": "Gesch. Dauer",
"explore": "Erkunden",
"explore-some-trails": "Erkunden Sie einige Routen",
"features": "Feature",
"german": "Deutsch",
"hero_section_0_text": "Entdecke spannende Routen, speichere deine Favoriten und erlebe die Schönheit der Natur. Finde dein nächstes Abenteuer!",
"hero_section_1_text": "Hier sind einige Routen, die dir gefallen könnten. Oder du wirfst einen Blick auf die vollständige Liste.",
"hero_section_2_text": "Wusstest due schon? Du kannst nicht nur deine Routen speichern. Es gibt viele Kategorien für alle deine Abenteuer.",
"hero_section_2_text": "Wusstest du schon? Du kannst nicht nur deine Routen speichern. Es gibt viele Kategorien für alle deine Abenteuer.",
"icon": "Icon",
"imperial": "Amerikanisch",
"language": "Sprache",
@@ -74,6 +76,7 @@
"public": "Öffentlich",
"radius": "Radius",
"register": "Registrieren",
"removed-trail-from": "Route entfernt aus",
"save": "Speichern",
"save-trail": "Route speichern",
"search-cities": "Städte suchen",
@@ -88,6 +91,7 @@
"summit-book": "Gipfelbuch",
"text": "Text",
"trail": "{n, plural, =1 {Route} other {Routen}}",
"trails-for-you": "Routen für dich",
"units": "Einheiten",
"upload-gpx": "GPX hochladen",
"username": "Nutzername",

View File

@@ -4,6 +4,7 @@
"add-entry": "Add Entry",
"add-to-list": "Add to list",
"add-waypoint": "Add Waypoint",
"added-trail-to": "Added trail to",
"alphabetical": "Alphabetical",
"already-account": "Already have an account?",
"avatar": "Avatar",
@@ -40,6 +41,7 @@
"english": "English",
"est-duration": "Est. duration",
"explore": "Explore",
"explore-some-trails": "Explore some trails",
"features": "Features",
"german": "German",
"hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!",
@@ -74,6 +76,7 @@
"public": "Public",
"radius": "Radius",
"register": "Register",
"removed-trail-from": "Removed trail from",
"save": "Save",
"save-trail": "Save Trail",
"search-cities": "Search cities",
@@ -88,6 +91,7 @@
"summit-book": "Summit Book",
"text": "Text",
"trail": "{n, plural, =1 {Trail} other {Trails}}",
"trails-for-you": "Trails for you",
"units": "Units",
"upload-gpx": "Upload GPX",
"username": "Username",

View File

@@ -1,11 +1,20 @@
import { pb } from "$lib/pocketbase";
import type { Category } from "$lib/models/category";
import { writable, type Writable } from "svelte/store";
import { ClientResponseError } from "pocketbase";
export const categories: Writable<Category[]> = writable([])
export async function categories_index() {
const response: Category[] = (await pb.collection('categories').getFullList<Category>())
export async function categories_index(f: Function = fetch) {
const r = await f('/api/v1/category', {
method: 'GET',
})
if (!r.ok) {
throw new ClientResponseError(await r.json())
}
const response = await r.json();
categories.set(response);

View File

@@ -4,19 +4,20 @@ import type { Waypoint } from "$lib/models/waypoint";
import { pb } from "$lib/pocketbase";
import { getFileURL } from "$lib/util/file_util";
import { util } from "$lib/vendor/svelte-form-lib/util";
import type { LatLng } from "leaflet";
import { ClientResponseError } from "pocketbase";
import { get, writable, type Writable } from "svelte/store";
import { summit_logs_create, summit_logs_delete, summit_logs_update } from "./summit_log_store";
import { waypoints_create, waypoints_delete, waypoints_update } from "./waypoint_store";
import type { LatLng } from "leaflet";
import { ClientResponseError } from "pocketbase";
export const trails: Writable<Trail[]> = writable([])
export const trail: Writable<Trail> = writable(new Trail(""));
export const editTrail: Writable<Trail> = writable(new Trail(""));
export async function trails_index(data: { perPage: number, random?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> } = { perPage: 5, random: false, f: fetch }) {
export async function trails_index(data: { perPage: number, random?: boolean, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> } = { perPage: 30, random: false, f: fetch }) {
const r = await data.f('/api/v1/trail?' + new URLSearchParams({
"per-page": data.perPage.toString(),
expand: "category,waypoints,summit_logs",
sort: data.random ? "@random" : ""
}), {
@@ -32,7 +33,7 @@ export async function trails_index(data: { perPage: number, random?: boolean, f:
}
}
export async function trails_search_filter(filter: TrailFilter, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
export async function trails_search_filter(filter: TrailFilter, page: number = 1, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
let filterText: string = `distance >= ${filter.distanceMin} AND distance <= ${filter.distanceMax} AND elevation_gain >= ${filter.elevationGainMin} AND elevation_gain <= ${filter.elevationGainMax}`;
if (filter.category.length > 0) {
@@ -46,7 +47,7 @@ export async function trails_search_filter(filter: TrailFilter, f: (url: Request
}
let r = await f("/api/v1/search/trails", {
method: "POST",
body: JSON.stringify({ q: filter.q, options: { filter: filterText } }),
body: JSON.stringify({ q: filter.q, options: { filter: filterText, hitsPerPage: 20, page: page } }),
});
const result = await r.json();
@@ -68,7 +69,7 @@ export async function trails_search_filter(filter: TrailFilter, f: (url: Request
if (r.ok) {
trails.set(response.items);
return response.items;
return result;
} else {
throw new ClientResponseError(response)
}
@@ -120,7 +121,6 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
const response = await r.json()
if (r.ok) {
for (const trail of response.items) {
const gpxData: string = await fetchGPX(trail);
trail.expand.gpx_data = gpxData;
@@ -150,7 +150,7 @@ export async function trails_show(id: string, loadGPX?: boolean, f: (url: Reques
}
if (loadGPX) {
const gpxData: string = await fetchGPX(response);
const gpxData: string = await fetchGPX(response, f);
response.expand.gpx_data = gpxData;
}
@@ -317,12 +317,12 @@ export async function trails_delete(trail: Trail) {
}
}
async function fetchGPX(trail: Trail) {
async function fetchGPX(trail: Trail, f: (url: RequestInfo | URL, config?: RequestInit) => Promise<Response> = fetch) {
if (!trail.gpx) {
return "";
}
const gpxUrl = getFileURL(trail, trail.gpx);
const response: Response = await fetch(gpxUrl);
const response: Response = await f(gpxUrl);
const gpxData = await response.text();
return gpxData

View File

@@ -51,9 +51,9 @@ export async function logout() {
}
export async function users_update(id: string, user: User | { [K in keyof User]?: User[K] } | FormData) {
const r = await fetch('/api/v1/user', {
const r = await fetch('/api/v1/user/' + id, {
method: 'POST',
body: JSON.stringify({ id: id, user: user })
body: JSON.stringify(user)
})
if (r.ok) {
@@ -65,9 +65,8 @@ export async function users_update(id: string, user: User | { [K in keyof User]?
}
export async function users_delete(user: User) {
const r = await fetch('/api/v1/user', {
const r = await fetch('/api/v1/user/' + user.id, {
method: 'DELETE',
body: JSON.stringify({ id: user.id })
})
if (!r.ok) {

View File

@@ -0,0 +1,3 @@
export function range(to: number, from: number = 0): ReadonlyArray<number> {
return [...Array(to - from).keys()].map(i => i + from);
}

View File

@@ -1,9 +1,11 @@
import { pb } from "$lib/pocketbase";
import type { FileOptions } from "pocketbase";
export function getFileURL(record: { [key: string]: any; }, filename?: string, options?: FileOptions) {
if(!filename) {
export function getFileURL(record: { [key: string]: any; }, filename?: string) {
if (!filename) {
return "";
}
return pb.files.getUrl(record, filename, options);
// return pb.files.getUrl(record, filename);
return `/api/v1/files/${record.collectionId}/${record.id}/${filename}`
}