feat: enhance layout of search

This commit is contained in:
2023-08-06 17:47:26 +02:00
parent 0e0d26c939
commit 6f650b568d
25 changed files with 518 additions and 172 deletions

View File

@ -34,6 +34,16 @@ export const menus: Record<string, Menu> = {
return !getCookie("session_cookie");
},
},
{
title: "Search",
icon: "IconSearch",
cb: () => {
window.location.href += "?q=";
},
visible: () => {
return !!getCookie("session_cookie") && window.location.search === "";
},
},
{
title: "Logout",
icon: "IconLogout",

View File

@ -45,6 +45,6 @@ export const addMovieInfos: MenuEntry = {
visible: () => {
const loc = globalThis["location"];
if (!getCookie("session_cookie")) return false;
return loc?.pathname?.includes("movie");
return loc?.pathname?.includes("movie") && !loc.pathname.endsWith("movies");
},
};

View File

@ -1,7 +1,15 @@
import { useEffect, useRef, useState } from "preact/hooks";
import useDebouncedCallback from "@lib/hooks/useDebouncedCallback.ts";
import { IconSearch } from "@components/icons.tsx";
import { IconGhost, IconLoader2, IconSearch } from "@components/icons.tsx";
import { useEventListener } from "@lib/hooks/useEventListener.ts";
import { SearchResponse } from "https://raw.githubusercontent.com/bradenmacdonald/typesense-deno/main/src/Typesense/Documents.ts";
import { Recipe } from "@lib/resource/recipes.ts";
import { Movie } from "@lib/resource/movies.ts";
import { Article } from "@lib/resource/articles.ts";
import { useTraceUpdate } from "@lib/hooks/useTraceProps.ts";
import { SearchResult } from "@lib/types.ts";
import { resources } from "@lib/resources.ts";
import { isLocalImage } from "@lib/string.ts";
export const RedirectSearchHandler = () => {
useEventListener("keydown", (e: KeyboardEvent) => {
@ -18,11 +26,63 @@ export const RedirectSearchHandler = () => {
return <></>;
};
const SearchResultImage = ({ src }: { src: string }) => {
const imageSrc = isLocalImage(src)
? `/api/images?image=${src}&width=50&height=50`
: src;
return (
<img
class="object-cover w-12 h-12 rounded-full"
src={imageSrc}
alt="preview image"
/>
);
};
export const SearchResultItem = (
{ item, showEmoji = false }: {
item: NonNullable<SearchResult["hits"]>[number];
showEmoji?: boolean;
},
) => {
const doc = item.document;
const resourceType = resources[doc.type];
const href = (resourceType) ? `${resourceType.link}/${doc.id}` : "";
return (
<a
href={href}
class="p-2 text-white flex gap-4 items-center rounded-2xl hover:bg-gray-700"
>
{doc?.image && <SearchResultImage src={doc.image} />}
{`${showEmoji && resourceType ? resourceType.emoji : ""}`} {doc?.name}
</a>
);
};
export const SearchResultList = (
{ result, showEmoji }: { result: SearchResult; showEmoji?: boolean },
) => {
return (
<div class="mt-4">
{result?.hits
? (
<div class="flex flex-col gap-4">
{result.hits.map((hit) => (
<SearchResultItem item={hit} showEmoji={showEmoji} />
))}
</div>
)
: <div style={{ color: "#818181" }}>No Results</div>}
</div>
);
};
const SearchComponent = (
{ q, type }: { q: string; type?: string },
) => {
const [searchQuery, setSearchQuery] = useState(q);
const [data, setData] = useState<any[]>([]);
const [data, setData] = useState<SearchResult>();
const [isLoading, setIsLoading] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
if ("history" in globalThis) {
@ -38,8 +98,8 @@ const SearchComponent = (
setIsLoading(true);
const fetchUrl = new URL(window.location.href);
fetchUrl.pathname = "/api/resources";
if (searchQuery) {
fetchUrl.searchParams.set("q", encodeURIComponent(searchQuery));
if (query) {
fetchUrl.searchParams.set("q", encodeURIComponent(query));
} else {
return;
}
@ -66,29 +126,48 @@ const SearchComponent = (
const handleInputChange = (event: Event) => {
const target = event.target as HTMLInputElement;
setSearchQuery(target.value);
debouncedFetchData(target.value); // Call the debounced fetch function with the updated search query
if (target.value !== searchQuery) {
setSearchQuery(target.value);
debouncedFetchData(target.value); // Call the debounced fetch function with the updated search query
}
};
useEffect(() => {
debouncedFetchData(q);
}, []);
console.log({ data, isLoading });
return (
<div class="max-w-full">
<div class="bg-white flex items-center gap-1 w-full py-3 px-4 pr-12 rounded-xl border border-gray-300 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-600 focus:border-blue-600">
<IconSearch class="w-4 h-4" />
<div class="mt-2">
<div
class="flex items-center gap-1 rounded-xl w-full shadow-2xl"
style={{ background: "#2B2930", color: "#818181" }}
>
{isLoading
? <IconLoader2 class="w-4 h-4 ml-4 mr-2 animate-spin" />
: <IconSearch class="w-4 h-4 ml-4 mr-2" />}
<input
type="text"
class=""
style={{ fontSize: "1.2em" }}
class="bg-transparent py-3 w-full"
ref={inputRef}
value={searchQuery}
onInput={handleInputChange}
/>
</div>
{isLoading ? <div>Loading...</div> : (
<div>
{data.map((d) => (
<pre class="text-white">{JSON.stringify(d.document, null, 2)}</pre>
))}
</div>
)}
{data?.hits?.length && !isLoading
? <SearchResultList showEmoji={!type} result={data} />
: isLoading
? <div />
: (
<div
class="flex items-center gap-2 p-2 my-4 mx-3"
style={{ color: "#818181" }}
>
<IconGhost class="animate-hover" />
No Results
</div>
)}
</div>
);
};