moves meilisearch indexing to pocketbase

This commit is contained in:
Christian Beutel
2024-02-20 16:27:30 +01:00
parent e9cb72b250
commit 2363c677b7
16 changed files with 716 additions and 52 deletions

View File

@@ -17,6 +17,7 @@
export let items: SearchItem[] = [];
export let placeholder: string = "Search...";
export let large: boolean = false;
export let extraClasses: string = "";
const dispatch = createEventDispatcher();
@@ -53,13 +54,13 @@
}
</script>
<div class="relative text-gray-600">
<div class="relative text-gray-600 {extraClasses}">
<span class="absolute top-1/2 -translate-y-1/2 left-0 pl-4">
<i class="fa fa-search" class:text-xl={large}></i>
</span>
{#if value.length > 0}
<button
class="absolute top-1/2 -translate-y-1/2 right-12 h-6 w-6 mr-4 hover:bg-gray-300 hover:bg-opacity-50 rounded-full"
class="absolute top-1/2 -translate-y-1/2 right-0 h-6 w-6 mr-4 hover:bg-gray-300 hover:bg-opacity-50 rounded-full"
on:click={clear}
in:fade={{ duration: 150 }}
out:fade={{ duration: 150 }}
@@ -72,12 +73,13 @@
name="q"
autocomplete="off"
extraClasses="{large
? 'pl-12 text-xl min-w-80 w-[33vw] max-w-[532px] rounded-xl'
? 'pl-12 py-4 text-xl min-w-80 w-[33vw] max-w-[532px] rounded-xl'
: 'pl-10'}"
{placeholder}
bind:value
on:input={onSearchType}
on:focusin={() => (searching = true)}
on:focusout={() => (searching = false)}
></TextField>
{#if dropDownOpen}
@@ -92,7 +94,7 @@
<li
class="menu-item flex items-center px-4 py-3 cursor-pointer hover:bg-gray-100 focus:bg-gray-200 transition-colors"
tabindex="0"
on:mouseup|stopPropagation={() => handleItemClick(item)}
on:mousedown|stopPropagation={() => handleItemClick(item)}
on:keydown|stopPropagation={() => handleItemClick(item)}
>
<i class="fa fa-{item.icon} mr-6"></i>

View File

@@ -22,7 +22,7 @@ Command: npx @threlte/gltf@2.0.1 static/models/earth.glb
<slot name="fallback" />
{:then gltf}
<!-- Clouds -->
<T.Group rotation={[-rotation / 2, rotation / 2, 0]}>
<T.Group rotation={[-rotation / 4, rotation / 4, 0]}>
<T.Mesh
geometry={gltf.nodes.Icosphere002.geometry}
material={gltf.materials.Material}
@@ -94,7 +94,7 @@ Command: npx @threlte/gltf@2.0.1 static/models/earth.glb
position={[0.5, 2, -0.5]}
scale={0.88}
>
<T.PointLight position={[2, 0, 0]} color="#f4de51" intensity={4.5}
<T.PointLight position={[2, 0, 0]} color="#ffffff" intensity={4.5}
></T.PointLight>
<T.DirectionalLight color="#f4de51" intensity={2.5} castShadow

View File

@@ -1,7 +1,8 @@
<script lang="ts">
import { goto } from "$app/navigation";
import LogoText from "$lib/components/logo/logo_text.svelte";
import { logout, currentUser } from "$lib/stores/user_store";
import { ms } from "$lib/meilisearch";
import { currentUser, logout } from "$lib/stores/user_store";
import Dropdown from "./base/dropdown.svelte";
const dropdownItems = [

View File

@@ -16,6 +16,6 @@
}}
/>
<T.AmbientLight color="0xffffff" intensity={1.1} />
<T.AmbientLight color="#ffffff" intensity={1.1} />
<Earth rotation={rotation}></Earth>

View File

@@ -27,7 +27,7 @@
}
</script>
<div class="trail-card rounded-2xl shadow-md sm:w-72 cursor-pointer">
<div class="trail-card rounded-2xl border sm:w-72 cursor-pointer" on:mouseenter on:mouseleave role="listitem">
<div class="w-full min-h-40 max-h-48 overflow-hidden rounded-t-2xl">
<img src={trail.thumbnail} alt="" />
</div>
@@ -36,7 +36,10 @@
<div class="flex justify-between items-center">
<h4 class="font-semibold text-lg">{trail.name}</h4>
{#if $currentUser && $currentUser.id == trail.author && mode == "edit"}
<Dropdown on:change={(e) => handleDropdownClick(trail, e.detail)} items={dropdownItems}></Dropdown>
<Dropdown
on:change={(e) => handleDropdownClick(trail, e.detail)}
items={dropdownItems}
></Dropdown>
{/if}
</div>
<h5><i class="fa fa-location-dot mr-3"></i>{trail.location}</h5>

View File

@@ -27,7 +27,7 @@
</script>
<li
class="flex gap-8 p-4 rounded-xl shadow-md cursor-pointer hover:bg-gray-100 transition-colors"
class="flex gap-8 p-4 rounded-xl border cursor-pointer hover:bg-gray-100 transition-colors"
>
<div class="shrink-0">
<img
@@ -36,7 +36,7 @@
alt=""
/>
</div>
<div class="min-w-0">
<div class="min-w-0 basis-full">
<div class="flex items-center justify-between">
<h4 class="font-semibold text-lg">{trail.name}</h4>

View File

@@ -1,10 +1,20 @@
import { PUBLIC_MEILISEARCH_URL } from '$env/static/public'
import { PUBLIC_MEILISEARCH_API_KEY } from '$env/static/public'
import { PUBLIC_MEILISEARCH_API_TOKEN } from '$env/static/public'
import { MeiliSearch } from 'meilisearch'
import { pb } from './pocketbase'
export function createInstance() {
return new MeiliSearch({ host: PUBLIC_MEILISEARCH_URL, apiKey: PUBLIC_MEILISEARCH_API_KEY })
if(pb.authStore.model) {
console.log(pb.authStore.model.token);
return new MeiliSearch({ host: PUBLIC_MEILISEARCH_URL, apiKey: pb.authStore.model.token })
}
return new MeiliSearch({ host: PUBLIC_MEILISEARCH_URL, apiKey: PUBLIC_MEILISEARCH_API_TOKEN })
}
export const ms = createInstance()
export function regenerateInstance() {
ms = createInstance();
}
export let ms = createInstance()

View File

@@ -163,8 +163,6 @@ export async function trails_create(trail: Trail, formData: { [key: string]: any
.collection("trails")
.update<Trail>(model.id!, { thumbnail: thumbnail }, { expand: "category" });
index_trail(model);
return model;
}
@@ -243,8 +241,6 @@ export async function trails_update(oldTrail: Trail, newTrail: Trail, formData:
trail.set(model);
index_trail(model);
return model;
}
@@ -261,8 +257,6 @@ export async function trails_delete(trail: Trail) {
}
}
ms.index('trails').deleteDocument(trail.id!)
const success = await pb
.collection("trails")
.delete(trail.id!);
@@ -313,6 +307,7 @@ function index_trail(trail: Trail) {
ms.index('trails').addDocuments([
{
"id": trail.id,
"author": trail.author,
"name": trail.name,
"description": trail.description,
"location": trail.location,
@@ -322,6 +317,7 @@ function index_trail(trail: Trail) {
"category": trail.expand.category?.id,
"completed": trail.expand.summit_logs?.length > 0,
"created": trail.created,
"public": trail.public,
"_geo": {
"lat": trail.lat,
"lng": trail.lon,

View File

@@ -1,3 +1,4 @@
import { regenerateInstance } from "$lib/meilisearch";
import { pb } from "$lib/pocketbase";
import { type AuthModel } from 'pocketbase';
import { writable, type Writable } from "svelte/store";
@@ -18,8 +19,10 @@ export async function users_create(user: User) {
export async function login(user: User) {
const authData = await pb.collection('users').authWithPassword(user.email ?? user.username!, user.password);
regenerateInstance()
}
export async function logout() {
pb.authStore.clear();
regenerateInstance()
}

View File

@@ -11,23 +11,22 @@
import { trails } from "$lib/stores/trail_store";
import { currentUser } from "$lib/stores/user_store";
import { country_codes } from "$lib/util/country_code_util";
import { Canvas, } from "@threlte/core";
import { Canvas } from "@threlte/core";
let searchDropdownItems: SearchItem[] = [];
async function search(q: string) {
async function search(q: string) {
const response = await ms.multiSearch({
queries: [
{
indexUid: "trails",
q: q,
limit: 5,
limit: 3,
},
{
indexUid: "cities500",
q: q,
limit: 5,
limit: 3,
},
],
});
@@ -57,31 +56,34 @@
}
</script>
<section class="hero grid grid-cols-2" style="height: calc(100vh - 112px)">
<div class="flex flex-col justify-center gap-8 max-w-md mx-auto -mt-24">
<section
class="hero grid grid-cols-1 md:grid-cols-2"
style="height: calc(100vh - 112px)"
>
<div class="flex flex-col justify-center gap-8 max-w-md mx-8 md:mx-auto -mt-24">
<h2 class="text-7xl font-bold">
Welcome to <span class="-tracking-[0.075em]">wanderer</span>
</h2>
<h5>
Explore diverse trails, save your favorites, and experience the
Explore exciting trails, save your favorites, and experience the
beauty of nature. Find your next adventure!
</h5>
<Search
on:update={(e) => search(e.detail)}
on:click={(e) => handleSearchClick(e.detail)}
large={true}
placeholder="Search trails..."
placeholder="Search for trails, places..."
items={searchDropdownItems}
></Search>
</div>
<div>
<div class="hidden md:block">
<Canvas>
<Scene></Scene>
</Canvas>
</div>
</section>
<section class="max-w-7xl mx-auto mt-8 px-8 xl:px-0">
<h2 class="text-5xl md:text-6xl font-bold text-primary">
<h2 class="text-4xl md:text-5xl font-bold text-primary">
{$currentUser ? "Your" : "Explore"} trails
</h2>
<div
@@ -96,7 +98,7 @@
</div>
</section>
<section class="max-w-7xl mx-auto mt-8 px-8 xl:px-0">
<h2 class="text-5xl md:text-6xl font-bold text-primary">By category</h2>
<h2 class="text-4xl md:text-5xl font-bold text-primary">By category</h2>
<div
id="categories"
class="grid grid-cols- sm:grid-cols-2 lg:grid-cols-4 justify-items-center gap-8 py-8"

View File

@@ -1,20 +1,18 @@
<script lang="ts">
import { goto } from "$app/navigation";
import Search, {
type SearchItem,
} from "$lib/components/base/search.svelte";
import TrailCard from "$lib/components/trail/trail_card.svelte";
import { ms } from "$lib/meilisearch";
import type { Trail } from "$lib/models/trail";
import {
trails,
trails_search_bounding_box,
} from "$lib/stores/trail_store";
import { country_codes } from "$lib/util/country_code_util";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
import type {
GPX,
Icon,
LatLng,
LayerGroup,
LeafletEvent,
Map,
Marker,
} from "leaflet";
import type { GPX, Icon, LatLng, LeafletEvent, Map, Marker } from "leaflet";
import "leaflet.awesome-markers/dist/leaflet.awesome-markers.css";
import "leaflet/dist/leaflet.css";
import { onMount } from "svelte";
@@ -22,6 +20,9 @@
let L: any;
let map: Map;
let gpxLayers: Record<string, GPX> = {};
let startMarkers: Record<string, Marker> = {};
let searchDropdownItems: SearchItem[] = [];
onMount(async () => {
L = (await import("leaflet")).default;
@@ -52,6 +53,44 @@
);
});
async function search(q: string) {
const response = await ms.multiSearch({
queries: [
{
indexUid: "trails",
q: q,
limit: 3,
},
{
indexUid: "cities500",
q: q,
limit: 3,
},
],
});
const trailItems = response.results[0].hits.map((t) => ({
text: t.name,
description: `Trail | ${t.location}`,
value: t,
icon: "route",
}));
const cityItems = response.results[1].hits.map((c) => ({
text: c.name,
description: `City | ${
country_codes[c["country code"] as keyof typeof country_codes]
}`,
value: c,
icon: "city",
}));
searchDropdownItems = [...trailItems, ...cityItems];
}
function handleSearchClick(item: SearchItem) {
map.setView([item.value._geo.lat, item.value._geo.lng], 12);
}
async function searchTrails(northEast: LatLng, southWest: LatLng) {
const changes = await trails_search_bounding_box(northEast, southWest);
@@ -66,6 +105,7 @@
for (const deletedTrail of changes.deleted) {
map.removeLayer(gpxLayers[deletedTrail.id!]);
delete gpxLayers[deletedTrail.id!];
delete startMarkers[deletedTrail.id!];
}
}
@@ -94,6 +134,7 @@
.on("addpoint", function (e: any) {
if (e.point_type === "start") {
const marker: Marker = e.point as Marker;
startMarkers[trail.id!] = marker;
marker.bindPopup(
`<a href="/trail/view/${trail.id}">
<li class="flex items-center gap-4 cursor-pointer text-black">
@@ -116,13 +157,6 @@
</li>
</a>`,
);
// marker.on("mouseover", (e) => {
// marker.openPopup();
// });
// marker.on("mouseout", (e) => {
// marker.closePopup();
// });
}
})
.on("loaded", function (e: LeafletEvent) {
@@ -132,14 +166,36 @@
.addTo(map);
});
}
function handleTrailCardMouseEnter(trail: Trail) {
const marker: Marker = startMarkers[trail.id!];
marker?.openPopup();
}
function handleTrailCardMouseLeave(trail: Trail) {
const marker: Marker = startMarkers[trail.id!];
marker?.closePopup();
}
</script>
<main class="grid grid-cols-[400px_1fr]">
<div
class="overflow-y-scroll overflow-x-hidden flex flex-col items-center gap-4 px-8"
>
<Search
extraClasses="w-full"
on:update={(e) => search(e.detail)}
on:click={(e) => handleSearchClick(e.detail)}
placeholder="Search for trails, places..."
items={searchDropdownItems}
></Search>
{#each $trails as trail}
<TrailCard {trail} mode="edit"></TrailCard>
<TrailCard
{trail}
mode="edit"
on:mouseenter={() => handleTrailCardMouseEnter(trail)}
on:mouseleave={() => handleTrailCardMouseLeave(trail)}
></TrailCard>
{/each}
</div>
<div class="rounded-xl" id="map"></div>

View File

@@ -205,7 +205,7 @@
<div class="flex flex-wrap md:flex-nowrap mt-6 gap-8">
<div class="basis-full">
{#if activeTab == 0}
<article class="text-justify whitespace-pre-line">
<article class="text-justify whitespace-pre-line text-sm">
{$trail.description}
</article>
{/if}

View File

@@ -296,7 +296,7 @@
class="flex items-start flex-wrap gap-8 py-8 max-w-full"
>
{#each $trails as trail}
<a class="max-w-full" href="/trail/view/{trail.id}">
<a class="max-w-full" class:basis-full={selectedDisplayOption === "list"} href="/trail/view/{trail.id}">
{#if selectedDisplayOption === "cards"}
<TrailCard {trail} mode="edit"></TrailCard>
{:else}