ionian/packages/client/src/page/galleryPage.tsx
2024-04-13 01:31:46 +09:00

87 lines
3.3 KiB
TypeScript

import { useLocation, useSearch } from "wouter";
import { Button } from "@/components/ui/button.tsx";
import { GalleryCard } from "@/components/gallery/GalleryCard.tsx";
import TagBadge from "@/components/gallery/TagBadge.tsx";
import { useSearchGalleryInfinite } from "../hook/useSearchGallery.ts";
import { Spinner } from "../components/Spinner.tsx";
import TagInput from "@/components/gallery/TagInput.tsx";
import { useState } from "react";
export default function Gallery() {
const search = useSearch();
const searchParams = new URLSearchParams(search);
const word = searchParams.get("word") ?? undefined;
const tags = searchParams.get("allow_tag") ?? undefined;
const limit = searchParams.get("limit");
const cursor = searchParams.get("cursor");
const { data, error, isLoading, size, setSize } = useSearchGalleryInfinite({
word, tags,
limit: limit ? Number.parseInt(limit) : undefined,
cursor: cursor ? Number.parseInt(cursor) : undefined
});
if (isLoading) {
return <div className="p-4">Loading...</div>
}
if (error) {
return <div className="p-4">Error: {String(error)}</div>
}
const isLoadingMore = data && size > 0 && (data[size - 1] === undefined);
const isReachingEnd = data && data[size - 1]?.hasMore === false;
return (<div className="p-4 grid gap-2 overflow-auto h-screen items-start content-start">
<Search />
{(word || tags) &&
<div className="bg-primary rounded-full p-1 sticky top-0 shadow-md">
{word && <span className="text-primary-foreground ml-4">Search: {word}</span>}
{tags && <span className="text-primary-foreground ml-4">Tags: <ul className="inline-flex">{tags.split(",").map(x => <TagBadge tagname={x} key={x} />)}</ul></span>}
</div>
}
{
data?.length === 0 && <div className="p-4 text-3xl">No results</div>
}
{
// TODO: date based grouping
data?.map((docs) => {
return docs?.data?.map((x) => {
return (
<GalleryCard doc={x} key={x.id} />
);
});
})
}
{
<Button className="w-full" onClick={() => setSize(size + 1)}
disabled={isReachingEnd || isLoadingMore}
> {isLoadingMore && <Spinner className="mr-1" />}{size + 1} Load more</Button>
}
</div>
);
}
function Search() {
const search = useSearch();
const [, navigate] = useLocation();
const searchParams = new URLSearchParams(search);
const [tags, setTags] = useState(searchParams.get("allow_tag")?.split(",") ?? []);
const [word, setWord] = useState(searchParams.get("word") ?? "");
return <div className="flex space-x-2">
<TagInput className="flex-1" input={word} onInputChange={setWord}
tags={tags} onTagsChange={setTags}
/>
<Button className="flex-none" onClick={()=>{
const params = new URLSearchParams();
if (tags.length > 0) {
for (const tag of tags) {
params.append("allow_tag", tag);
}
}
if (word) {
params.set("word", word);
}
navigate(`/search?${params.toString()}`);
}}>Search</Button>
</div>;
}