diff --git a/frontend/src/Search.tsx b/frontend/src/Search.tsx index 46afda4..d958ec7 100644 --- a/frontend/src/Search.tsx +++ b/frontend/src/Search.tsx @@ -3,12 +3,12 @@ import { IconRefresh, IconSearch, IconSettings } from "@tabler/icons-solidjs"; import { listen } from "@tauri-apps/api/event"; import Fuse from "fuse.js"; import { - For, - Show, - createEffect, - createSignal, - onCleanup, - onMount, + For, + Show, + createEffect, + createSignal, + onCleanup, + onMount, } from "solid-js"; import { SearchCard } from "./components/search-card/SearchCard"; import { invoke } from "@tauri-apps/api/core"; @@ -18,180 +18,175 @@ import { useSearchImageContext } from "./contexts/SearchImageContext"; import { A } from "@solidjs/router"; import { useSetEntity } from "./WithEntityDialog"; import { ProcessingImages } from "./notifications/ProcessingImages"; +import { Categories } from "./front"; export const Search = () => { - const [searchResults, setSearchResults] = createSignal([]); - const [searchQuery, setSearchQuery] = createSignal(""); - const setEntity = useSetEntity(); + const [searchResults, setSearchResults] = createSignal([]); + const [searchQuery, setSearchQuery] = createSignal(""); + const setEntity = useSetEntity(); - const { images, imagesWithProperties, onRefetchImages } = - useSearchImageContext(); + const { images, imagesWithProperties, onRefetchImages } = + useSearchImageContext(); - let fuze = new Fuse(images(), { - keys: [ - { name: "rawData", weight: 1 }, - { name: "title", weight: 1 }, - ], - threshold: 0.4, - }); + let fuze = new Fuse(images(), { + keys: [ + { name: "rawData", weight: 1 }, + { name: "title", weight: 1 }, + ], + threshold: 0.4, + }); - createEffect(() => { - setSearchResults(images()); + createEffect(() => { + setSearchResults(images()); - fuze = new Fuse(images(), { - keys: [ - { name: "data.Name", weight: 2 }, - { name: "rawData", weight: 1 }, - ], - threshold: 0.6, - }); - }); + fuze = new Fuse(images(), { + keys: [ + { name: "data.Name", weight: 2 }, + { name: "rawData", weight: 1 }, + ], + threshold: 0.6, + }); + }); - const onInputChange = (event: InputEvent) => { - const query = (event.target as HTMLInputElement).value; + const onInputChange = (event: InputEvent) => { + const query = (event.target as HTMLInputElement).value; - if (query.length === 0) { - setSearchResults(images()); - } else { - setSearchQuery(query); - setSearchResults(fuze.search(query).map((s) => s.item)); - } - }; + if (query.length === 0) { + setSearchResults(images()); + } else { + setSearchQuery(query); + setSearchResults(fuze.search(query).map((s) => s.item)); + } + }; - let searchInputRef: HTMLInputElement | undefined; + let searchInputRef: HTMLInputElement | undefined; - onMount(() => { - if (searchInputRef) { - searchInputRef.focus(); - } - }); + onMount(() => { + if (searchInputRef) { + searchInputRef.focus(); + } + }); - createEffect(() => { - // Listen for the focus-search event from Tauri - const unlisten = listen("focus-search", () => { - if (searchInputRef) { - searchInputRef.focus(); - } - }); + createEffect(() => { + // Listen for the focus-search event from Tauri + const unlisten = listen("focus-search", () => { + if (searchInputRef) { + searchInputRef.focus(); + } + }); - onCleanup(() => { - unlisten.then((fn) => fn()); - }); - }); + onCleanup(() => { + unlisten.then((fn) => fn()); + }); + }); - const [shortcut, setShortcut] = createSignal([]); + const [shortcut, setShortcut] = createSignal([]); - async function getCurrentShortcut() { - try { - const res: string = await invoke("get_current_shortcut"); - console.log("DBG: ", res); - setShortcut(res?.split("+")); - } catch (err) { - console.error("Failed to fetch shortcut:", err); - } - } + async function getCurrentShortcut() { + try { + const res: string = await invoke("get_current_shortcut"); + console.log("DBG: ", res); + setShortcut(res?.split("+")); + } catch (err) { + console.error("Failed to fetch shortcut:", err); + } + } - onMount(() => { - getCurrentShortcut(); - }); + onMount(() => { + getCurrentShortcut(); + }); - return ( - <> -
-
-
-
- -
- -
- - -
+ return ( + <> +
+ -
-
- 0} - fallback={ -
- No results found -
- } - > -
- - {(item) => ( -
setEntity(item)} - onKeyDown={(e) => { - if (e.key === "Enter") { - setEntity(item); - } - }} - class="h-[144px] border relative col-span-3 border-neutral-200 cursor-pointer overflow-hidden rounded-xl" - > - - {item.data.Name} - - -
- )} -
-
-
-
-
+ - +
+
+
+ +
+ +
+ + +
-

Images

+
+
+ 0} + fallback={ +
+ No results found +
+ } + > +
+ + {(item) => ( +
setEntity(item)} + onKeyDown={(e) => { + if (e.key === "Enter") { + setEntity(item); + } + }} + class="h-[144px] border relative col-span-3 border-neutral-200 cursor-pointer overflow-hidden rounded-xl" + > + {item.data.Name} + +
+ )} +
+
+
+
+
-
- - {(imageId) => ( - - One of the users images - - )} - -
+

Images

-
-

- Use{" "} - {shortcut().length > 0 - ? shortcut().join("+") - : "shortcut"}{" "} - globally to toggle and reload this window -

-
-
- - ); +
+ + {(imageId) => ( + + One of the users images + + )} + +
+ +
+

+ Use {shortcut().length > 0 ? shortcut().join("+") : "shortcut"}{" "} + globally to toggle and reload this window +

+
+
+ + ); }; diff --git a/frontend/src/contexts/SearchImageContext.tsx b/frontend/src/contexts/SearchImageContext.tsx index 1829549..f3ba757 100644 --- a/frontend/src/contexts/SearchImageContext.tsx +++ b/frontend/src/contexts/SearchImageContext.tsx @@ -7,7 +7,7 @@ import { createResource, useContext, } from "solid-js"; -import { getUserImages } from "../network"; +import { CategoryUnion, getUserImages } from "../network"; import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage"; export type ImageWithRawData = Awaited< @@ -16,7 +16,16 @@ export type ImageWithRawData = Awaited< rawData: string[]; }; -type SearchImageStore = { +type TaggedCategory = Extract< + CategoryUnion, + { type: T } +>["data"]; + +type CategoriesSpecificData = { + [K in CategoryUnion["type"]]: Array>; +}; + +export type SearchImageStore = { images: Accessor; imagesWithProperties: Accessor>; @@ -24,6 +33,8 @@ type SearchImageStore = { Awaited>["ProcessingImages"] | undefined >; + categories: Accessor; + onRefetchImages: () => void; }; @@ -87,6 +98,25 @@ export const SearchImageContextProvider: Component = (props) => { return groupPropertiesWithImage(d); }); + const categories = createMemo(() => { + const c: ReturnType = { + contact: [], + event: [], + location: [], + note: [], + }; + + for (const category of data()?.ImageProperties ?? []) { + if (category.type === "location" || category.type === "contact") { + continue; + } + + c[category.type].push(category.data as any); + } + + return c; + }); + return ( = (props) => { imagesWithProperties: imagesWithProperties, processingImages, onRefetchImages: refetch, + categories, }} > {props.children} diff --git a/frontend/src/front/index.tsx b/frontend/src/front/index.tsx new file mode 100644 index 0000000..5a0df99 --- /dev/null +++ b/frontend/src/front/index.tsx @@ -0,0 +1,44 @@ +import { Component, createEffect, For } from "solid-js"; +import { + SearchImageStore, + useSearchImageContext, +} from "../contexts/SearchImageContext"; + +const CategoryColor: Record< + keyof ReturnType, + string +> = { + contact: "bg-red-500", + location: "bg-green-500", + event: "bg-blue-500", + note: "bg-purple-500", +}; + +export const Categories: Component = () => { + const { categories } = useSearchImageContext(); + + createEffect(() => { + console.log(categories()); + }); + + return ( +
+ + {([category, group]) => ( +
+

+ {category}: {group.length} +

+
+ )} +
+
+ ); +}; diff --git a/frontend/src/network/index.ts b/frontend/src/network/index.ts index 396b35f..d04f794 100644 --- a/frontend/src/network/index.ts +++ b/frontend/src/network/index.ts @@ -154,6 +154,8 @@ const dataTypeValidator = variant("type", [ contactDataType, ]); +export type CategoryUnion = InferOutput; + const userImageValidator = strictObject({ ID: pipe(string(), uuid()), CreatedAt: pipe(string()),