From 5866072aa7e8c4f05b7f787d27d7b3e30cf565aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:32:46 -0500 Subject: [PATCH] feat: search --- .../website/src/components/Gallery/List.tsx | 48 +++++++++++++++++++ .../website/src/components/Gallery/Search.tsx | 26 ++++++++++ .../TankSearch/components/SearchBar.tsx | 2 +- .../src/pages/tools/gallery/_index.tsx | 16 +++++++ .../src/pages/tools/gallery/index.astro | 41 +++++----------- .../website/src/stores/galleryEphemeral.ts | 16 +++++++ 6 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 packages/website/src/components/Gallery/List.tsx create mode 100644 packages/website/src/components/Gallery/Search.tsx create mode 100644 packages/website/src/pages/tools/gallery/_index.tsx create mode 100644 packages/website/src/stores/galleryEphemeral.ts diff --git a/packages/website/src/components/Gallery/List.tsx b/packages/website/src/components/Gallery/List.tsx new file mode 100644 index 000000000..066b63cf9 --- /dev/null +++ b/packages/website/src/components/Gallery/List.tsx @@ -0,0 +1,48 @@ +import { Flex, Text } from '@radix-ui/themes'; +import fuzzysort from 'fuzzysort'; +import { useMemo } from 'react'; +import { GalleryEphemeral } from '../../stores/galleryEphemeral'; + +interface GalleryListProps { + avatars: Avatar[]; +} + +export interface Avatar { + name: string; + id: string; +} + +export function GalleryList({ avatars }: GalleryListProps) { + const search = GalleryEphemeral.use((state) => state.search); + const filtered = useMemo(() => { + if (search === undefined) { + return avatars; + } else { + return fuzzysort + .go(search, avatars, { key: 'name' }) + .map((result) => result.obj); + } + }, [search]); + + return ( + + {filtered.map((avatar) => { + return ( + + + + {avatar.name} + + + ); + })} + + ); +} diff --git a/packages/website/src/components/Gallery/Search.tsx b/packages/website/src/components/Gallery/Search.tsx new file mode 100644 index 000000000..43ac28fd0 --- /dev/null +++ b/packages/website/src/components/Gallery/Search.tsx @@ -0,0 +1,26 @@ +import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; +import { TextField } from '@radix-ui/themes'; +import { useRef } from 'react'; +import { GalleryEphemeral } from '../../stores/galleryEphemeral'; + +export function GallerySearch() { + const input = useRef(null); + const mutateGalleryEphemeral = GalleryEphemeral.useMutation(); + + return ( + { + mutateGalleryEphemeral((draft) => { + const trimmed = event.target.value.trim(); + draft.search = trimmed.length === 0 ? undefined : trimmed; + }); + }} + > + + + + + ); +} diff --git a/packages/website/src/components/TankSearch/components/SearchBar.tsx b/packages/website/src/components/TankSearch/components/SearchBar.tsx index e3d0efbbe..d06c0d09f 100644 --- a/packages/website/src/components/TankSearch/components/SearchBar.tsx +++ b/packages/website/src/components/TankSearch/components/SearchBar.tsx @@ -61,7 +61,7 @@ export function SearchBar({ topResult, onSelect }: SearchBarProps) { ); return ( - + + + + + ); +} diff --git a/packages/website/src/pages/tools/gallery/index.astro b/packages/website/src/pages/tools/gallery/index.astro index 1b317cac9..f49a17430 100644 --- a/packages/website/src/pages/tools/gallery/index.astro +++ b/packages/website/src/pages/tools/gallery/index.astro @@ -1,45 +1,28 @@ --- -import { assertSecret, type BlitzGlossary } from '@blitzkit/core'; -import { Flex, Text } from '@radix-ui/themes'; +import type { Avatar } from '../../../components/Gallery/List'; import { PageWrapper } from '../../../components/PageWrapper'; import { galleryTool } from '../../../constants/tools'; +import { fetchGlossary } from '../../../core/blitz/fetchGlossary'; import ToolLayout from '../../../layouts/ToolLayout.astro'; +import { Page } from './_index'; -const response = await fetch(assertSecret(import.meta.env.WOTB_GLOSSARY), { - cache: 'force-cache', -}); -const avatars = Object.entries((await response.json()) as BlitzGlossary) +const glossary = await fetchGlossary(); +const avatars = Object.entries(glossary) .filter( ([key, value]) => key.startsWith('avatar') && !key.endsWith('_part') && value.image_url !== null, ) - .sort(([, a], [, b]) => a.title.localeCompare(b.title)); + .map( + ([key, value]) => + ({ id: key.replace('avatar_', ''), name: value.title }) satisfies Avatar, + ) + .sort((a, b) => a.name.localeCompare(b.name)); --- - - - { - avatars.map(([key, value]) => { - return ( - - - - {value.title} - - - ); - }) - } - + + diff --git a/packages/website/src/stores/galleryEphemeral.ts b/packages/website/src/stores/galleryEphemeral.ts new file mode 100644 index 000000000..54e01999f --- /dev/null +++ b/packages/website/src/stores/galleryEphemeral.ts @@ -0,0 +1,16 @@ +import { create } from 'zustand'; +import { subscribeWithSelector } from 'zustand/middleware'; +import { createContextualStore } from '../core/zustand/createContextualStore'; + +interface GalleryEphemeral { + searching: boolean; + search?: string; +} + +export const GalleryEphemeral = createContextualStore(() => { + return create()( + subscribeWithSelector(() => ({ + searching: false, + })), + ); +});