moves meilisearch indexing to pocketbase
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<T.AmbientLight color="0xffffff" intensity={1.1} />
|
||||
<T.AmbientLight color="#ffffff" intensity={1.1} />
|
||||
|
||||
<Earth rotation={rotation}></Earth>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user