web


git clone https://radroots.dev/git/web.git
Log | Files | Refs | Submodules | README | LICENSE

commit 65b3e5dc3bda5840d7d5c5e416fb6039b9991203
parent 26e259726f7d13da8ef95412181f0ec099e3b28e
Author: triesap <137732411+triesap@users.noreply.github.com>
Date:   Mon,  9 Dec 2024 06:36:03 +0000

Add `/search` page basis with search service. Add/edit components, conf.

Diffstat:
Asrc/lib/components/search_result_container.svelte | 16++++++++++++++++
Asrc/lib/components/search_result_display.svelte | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/conf.ts | 3+++
Asrc/routes/(app)/search/+page.svelte | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 339 insertions(+), 0 deletions(-)

diff --git a/src/lib/components/search_result_container.svelte b/src/lib/components/search_result_container.svelte @@ -0,0 +1,16 @@ +<script lang="ts"> + import type { CallbackPromise } from "@radroots/svelte-lib"; + + export let basis: { + callback: CallbackPromise; + }; +</script> + +<button + class={`group flex flex-row h-[4rem] w-full px-4 justify-between items-center bg-layer-1-surface round-24 layer-1-active-surface el-re`} + on:click={async () => { + await basis.callback(); + }} +> + <slot /> +</button> diff --git a/src/lib/components/search_result_display.svelte b/src/lib/components/search_result_display.svelte @@ -0,0 +1,204 @@ +<script lang="ts"> + import type { ClientSearchResult } from "$lib/util/client-search"; + import { ascii, Glyph, ls, route } from "@radroots/svelte-lib"; + import SearchResultContainer from "./search_result_container.svelte"; + + export let basis: ClientSearchResult; +</script> + +{#if `location_gcs` in basis && basis.location_gcs.id} + <SearchResultContainer + basis={{ + callback: async () => { + if (basis.location_gcs.kind === `farm_land`) + await route(`/farm/land/view`, [ + [`id`, basis.location_gcs.id], + ]); + }, + }} + > + <div class={`flex flex-row gap-4 justify-start items-center`}> + <div + class={`flex flex-row h-[1.5rem] w-[1.5rem] justify-center items-center bg-stone-500 round-24`} + > + <Glyph + basis={{ + classes: `text-white`, + dim: `xs`, + weight: `bold`, + key: `compass`, + }} + /> + </div> + <div class={`flex flex-row gap-1 justify-start items-center`}> + <div + class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-stone-600 rounded-md`} + > + <p + class={`font-sans font-[900] text-[0.7rem] text-white uppercase`} + > + {`${$ls(`common.location`)}`} + </p> + </div> + <p + class={`font-sans font-[700] text-layer-0-glyph/30 -translate-y-[1px]`} + > + {ascii.bullet} + </p> + + {#if basis.location_gcs.kind === `farm_land`} + <div + class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-lime-500 rounded-md`} + > + <p + class={`font-sans font-[900] text-[0.7rem] text-white uppercase`} + > + {`${$ls(`common.farm_land`)}`} + </p> + </div> + {/if} + </div> + </div> + <div + class={`flex flex-row flex-grow pl-4 pr-2 justify-end items-center overflow-hidden`} + > + {#if basis.location_gcs.label} + <p + class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph`} + > + {`${basis.location_gcs.label}`} + </p> + {:else} + <p + class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph truncate`} + > + {`${basis.location_gcs.gc_name}, ${basis.location_gcs.gc_admin1_id}`} + </p> + {/if} + </div> + </SearchResultContainer> +{:else if `nostr_profile` in basis && basis.nostr_profile.id} + <SearchResultContainer + basis={{ + callback: async () => { + await route(`/settings/profile`); + }, + }} + > + <div class={`flex flex-row gap-4 justify-start items-center`}> + <div + class={`flex flex-row h-[1.5rem] w-[1.5rem] justify-center items-center bg-blue-400 round-24`} + > + <Glyph + basis={{ + classes: `text-white`, + dim: `xs`, + weight: `bold`, + key: `user`, + }} + /> + </div> + <div class={`flex flex-row gap-1 justify-start items-center`}> + <div + class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-stone-600 rounded-md`} + > + <p + class={`font-sans font-[900] text-[0.7rem] text-white uppercase`} + > + {`${$ls(`common.profile`)}`} + </p> + </div> + <p + class={`font-sans font-[700] text-layer-0-glyph/30 -translate-y-[1px]`} + > + {ascii.bullet} + </p> + <div + class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-purple-400 rounded-md`} + > + <p + class={`font-sans font-[900] text-[0.7rem] text-white uppercase`} + > + {#if basis.result_k === `name`} + {`${$ls(`common.name`)}`} + {:else} + {`@todo`} + {/if} + </p> + </div> + </div> + </div> + <div + class={`flex flex-row flex-grow pl-4 pr-2 justify-end items-center overflow-hidden`} + > + {#if basis.nostr_profile.name} + <p + class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph tracking-wide truncate`} + > + {#if basis.result_k === `name`} + {`${basis.nostr_profile.name}`} + {:else if basis.result_v} + {`${basis.result_v}`} + {:else} + {`@todo`} + {/if} + </p> + {/if} + </div> + </SearchResultContainer> +{:else if `nostr_relay` in basis && basis.nostr_relay.id} + <SearchResultContainer + basis={{ + callback: async () => {}, + }} + > + <div class={`flex flex-row gap-4 justify-start items-center`}> + <div + class={`flex flex-row h-[1.5rem] w-[1.5rem] justify-center items-center bg-blue-400 round-24`} + > + <Glyph + basis={{ + classes: `text-white`, + dim: `xs`, + weight: `bold`, + key: `user`, + }} + /> + </div> + <div class={`flex flex-row gap-1 justify-start items-center`}> + <div + class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-stone-600 rounded-md`} + > + <p + class={`font-sans font-[900] text-[0.7rem] text-white uppercase`} + > + {`${$ls(`common.relay`)}`} + </p> + </div> + <p + class={`font-sans font-[700] text-layer-0-glyph/30 -translate-y-[1px]`} + > + {ascii.bullet} + </p> + <div + class={`flex flex-row h-[1.2rem] px-2 justify-center items-center bg-yellow-400 rounded-md`} + > + <p + class={`font-sans font-[900] text-[0.7rem] text-white uppercase`} + > + {`${$ls(`common.url`)}`} + </p> + </div> + </div> + </div> + <div + class={`flex flex-row flex-grow pr-2 justify-end items-center overflow-hidden`} + > + <p + class={`font-sand font-[500] text-[0.9rem] text-layer-0-glyph tracking-wide truncate`} + > + {`${basis.nostr_relay.url}`} + </p> + </div> + </SearchResultContainer> +{/if} diff --git a/src/lib/conf.ts b/src/lib/conf.ts @@ -43,6 +43,9 @@ export const cfg = { entry_focus: 2000, load_notify: 3000, }, + debounce: { + search: 200 + }, cmd: { layout_route: `*-route` }, diff --git a/src/routes/(app)/search/+page.svelte b/src/routes/(app)/search/+page.svelte @@ -0,0 +1,116 @@ +<script lang="ts"> + import { db } from "$lib/client"; + import SearchResultDisplay from "$lib/components/search_result_display.svelte"; + import { cfg } from "$lib/conf"; + import type { + LocationGcs, + NostrProfile, + NostrRelay, + TradeProduct, + } from "@radroots/models"; + import { + catch_err, + debounce_input, + fmt_id, + Glyph, + InputElement, + LayoutView, + ls, + NavToolbar, + PageHeader, + } from "@radroots/svelte-lib"; + import { SearchService, type SearchServiceResult } from "@radroots/utils"; + import { onMount } from "svelte"; + + type LoadData = { + location_gcs: LocationGcs[]; + nostr_profile: NostrProfile[]; + nostr_relay: NostrRelay[]; + trade_product: TradeProduct[]; + }; + let ld: LoadData | undefined = undefined; + + let client_search: SearchService | undefined = undefined; + let search_val = ``; + let search_results: SearchServiceResult[] = []; + + onMount(async () => { + try { + await init_page(); + } catch (e) { + } finally { + } + }); + + const init_page = async (): Promise<void> => { + try { + ld = await load_data(); + if (ld) client_search = new SearchService(ld); + } catch (e) { + await catch_err(e, `init_page`); + } + }; + + const load_data = async (): Promise<LoadData | undefined> => { + try { + const location_gcs = await db.location_gcs_get({ list: [`all`] }); + const nostr_profile = await db.nostr_profile_get({ + list: [`all`], + }); + const nostr_relay = await db.nostr_relay_get({ list: [`all`] }); + const trade_product = await db.trade_product_get({ + list: [`all`], + }); + return { + location_gcs: + `results` in location_gcs ? location_gcs.results : [], + nostr_profile: + `results` in nostr_profile ? nostr_profile.results : [], + nostr_relay: + `results` in nostr_relay ? nostr_relay.results : [], + trade_product: + `results` in trade_product ? trade_product.results : [], + } satisfies LoadData; + } catch (e) { + await catch_err(e, `load_page`); + } + }; + + const handle_search_input = debounce_input((val: string) => { + if (client_search) search_results = client_search.search(val); + }, cfg.debounce.search); +</script> + +<LayoutView> + <NavToolbar /> + <PageHeader basis={{ label: `${$ls(`common.search`)}` }}></PageHeader> + <div class={`flex flex-col w-full px-4 gap-6 justify-center items-center`}> + <div + class={`relative flex flex-row w-full justify-center items-center bg-layer-1-surface rounded-2xl`} + > + <Glyph + basis={{ + classes: `absolute left-4 text-layer-0-glyph-shade`, + dim: `sm`, + weight: `bold`, + key: `magnifying-glass`, + }} + /> + <InputElement + bind:value={search_val} + basis={{ + id: fmt_id(`search`), + sync: true, + classes: `pl-12 text-layer-0-glyph`, + placeholder: `Enter search query`, + callback: async ({ value }) => handle_search_input(value), + }} + /> + </div> + <div class={`flex flex-col w-full gap-4 justify-center items-center`}> + {#each search_results as li (li.id)} + <SearchResultDisplay basis={li} /> + {/each} + </div> + </div> +</LayoutView>