wip: search page

This commit is contained in:
2025-07-21 14:38:02 +01:00
parent 5d0fa51e01
commit 251e2bc553
6 changed files with 103 additions and 44 deletions

View File

@ -7,6 +7,7 @@ import {
Login,
Settings,
Entity,
SearchPage,
} from "./pages";
import { SearchImageContextProvider } from "@contexts/SearchImageContext";
import { WithNotifications } from "@contexts/Notifications";
@ -29,6 +30,7 @@ export const App = () => {
<Route path="/" component={WithNotifications}>
<Route path="/" component={WithTopbarAndDock}>
<Route path="/" component={FrontPage} />
<Route path="/search" component={SearchPage} />
<Route path="/image/:imageId" component={ImagePage} />
<Route path="/entity/:entityId" component={Entity} />
<Route path="/gallery/:entity" component={Gallery} />

View File

@ -8,7 +8,7 @@ export const Dock: Component = () => {
<A href="/" class="w-full flex justify-center items-center">
<IconHome />
</A>
<A href="/" class="w-full flex justify-center items-center">
<A href="/search" class="w-full flex justify-center items-center">
<IconSearch />
</A>
<A href="/" class="w-full flex justify-center items-center">

View File

@ -11,15 +11,10 @@ import {
CategoryUnion,
getUserImages,
JustTheImageWhatAreTheseNames,
UserImage,
} from "../network";
import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage";
export type ImageWithRawData = Awaited<
ReturnType<typeof getUserImages>
>["ImageProperties"][number] & {
rawData: string[];
};
type TaggedCategory<T extends CategoryUnion["type"]> = Extract<
CategoryUnion,
{ type: T }
@ -30,7 +25,7 @@ type CategoriesSpecificData = {
};
export type SearchImageStore = {
images: Accessor<ImageWithRawData[]>;
images: Accessor<UserImage[]>;
userImages: Accessor<JustTheImageWhatAreTheseNames>;
@ -44,51 +39,17 @@ export type SearchImageStore = {
onRefetchImages: () => void;
};
// How wonderfully functional
const getAllValues = (object: object): Array<string> => {
const loop = (acc: Array<string>, next: object): Array<string> => {
for (const [key, _value] of Object.entries(next)) {
if (key === "ID") {
continue;
}
const value: unknown = _value;
switch (typeof value) {
case "object":
if (value != null) {
acc.push(...loop(acc, value));
}
break;
case "string":
case "number":
case "boolean":
acc.push(value.toString());
break;
default:
break;
}
}
return acc;
};
return loop([], object);
};
const SearchImageContext = createContext<SearchImageStore>();
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
const [data, { refetch }] = createResource(getUserImages);
const imageData = createMemo<ImageWithRawData[]>(() => {
const imageData = createMemo(() => {
const d = data();
if (d == null) {
return [];
}
return d.ImageProperties.map((d) => ({
...d,
rawData: getAllValues(d),
}));
return d.ImageProperties;
});
const processingImages = () => data()?.ProcessingImages ?? [];

View File

@ -4,3 +4,4 @@ export * from "./image";
export * from "./settings";
export * from "./login";
export * from "./entity";
export * from "./search";

View File

@ -0,0 +1,48 @@
import { Component, createSignal } from "solid-js";
import { Search } from "@kobalte/core/search";
import { IconSearch } from "@tabler/icons-solidjs";
import { useSearch } from "./search";
import { UserImage } from "@network/index";
export const SearchPage: Component = () => {
const fuse = useSearch();
const [searchItems, setSearchItems] = createSignal<UserImage[]>([]);
return (
<Search
options={searchItems()}
onInputChange={(e) => {
const items = fuse().search(e);
setSearchItems(items.map((i) => i.item.image));
}}
itemComponent={(props) => (
<Search.Item item={props.item}>
<Search.ItemLabel>
{JSON.stringify(props.item.rawValue)}
</Search.ItemLabel>
</Search.Item>
)}
>
<Search.Label />
<Search.Control class="flex">
<Search.Indicator class="bg-neutral-200 p-4 rounded-l-xl">
<Search.Icon>
<IconSearch />
</Search.Icon>
</Search.Indicator>
<Search.Input
class="w-full p-4 font-bold text-xl rounded-r-xl"
placeholder="Woking Station..."
/>
</Search.Control>
<Search.Portal>
<Search.Content class="w-full rounded-xl bg-white p-4">
<Search.Arrow />
<Search.Listbox />
<Search.NoResult>No result found</Search.NoResult>
</Search.Content>
</Search.Portal>
</Search>
);
};

View File

@ -0,0 +1,47 @@
import { useSearchImageContext } from "@contexts/SearchImageContext";
import { UserImage } from "@network/index";
import { createMemo } from "solid-js";
import Fuse from "fuse.js";
const getSearchTerms = (image: UserImage): Array<string> => {
switch (image.type) {
case "location":
return [
image.data.Name,
image.data.Description,
image.data.Address,
image.data.CreatedAt,
].filter((i) => i != null);
case "event":
return [
image.data.Name,
image.data.Description,
image.data.CreatedAt,
].filter((i) => i != null);
case "contact":
return [
image.data.Name,
image.data.Description,
image.data.CreatedAt,
image.data.Email,
image.data.PhoneNumber,
].filter((i) => i != null);
case "note":
return [
image.data.Name,
image.data.Description,
image.data.CreatedAt,
image.data.Content,
].filter((i) => i != null);
}
};
export const useSearch = () => {
const { images } = useSearchImageContext();
const searchTerms = createMemo(() =>
images().map((i) => ({ image: i, searchTerms: getSearchTerms(i) })),
);
return () => new Fuse(searchTerms(), { keys: ["searchTerms"] });
};