wip: search page
This commit is contained in:
@ -7,6 +7,7 @@ import {
|
|||||||
Login,
|
Login,
|
||||||
Settings,
|
Settings,
|
||||||
Entity,
|
Entity,
|
||||||
|
SearchPage,
|
||||||
} from "./pages";
|
} from "./pages";
|
||||||
import { SearchImageContextProvider } from "@contexts/SearchImageContext";
|
import { SearchImageContextProvider } from "@contexts/SearchImageContext";
|
||||||
import { WithNotifications } from "@contexts/Notifications";
|
import { WithNotifications } from "@contexts/Notifications";
|
||||||
@ -29,6 +30,7 @@ export const App = () => {
|
|||||||
<Route path="/" component={WithNotifications}>
|
<Route path="/" component={WithNotifications}>
|
||||||
<Route path="/" component={WithTopbarAndDock}>
|
<Route path="/" component={WithTopbarAndDock}>
|
||||||
<Route path="/" component={FrontPage} />
|
<Route path="/" component={FrontPage} />
|
||||||
|
<Route path="/search" component={SearchPage} />
|
||||||
<Route path="/image/:imageId" component={ImagePage} />
|
<Route path="/image/:imageId" component={ImagePage} />
|
||||||
<Route path="/entity/:entityId" component={Entity} />
|
<Route path="/entity/:entityId" component={Entity} />
|
||||||
<Route path="/gallery/:entity" component={Gallery} />
|
<Route path="/gallery/:entity" component={Gallery} />
|
||||||
|
@ -8,7 +8,7 @@ export const Dock: Component = () => {
|
|||||||
<A href="/" class="w-full flex justify-center items-center">
|
<A href="/" class="w-full flex justify-center items-center">
|
||||||
<IconHome />
|
<IconHome />
|
||||||
</A>
|
</A>
|
||||||
<A href="/" class="w-full flex justify-center items-center">
|
<A href="/search" class="w-full flex justify-center items-center">
|
||||||
<IconSearch />
|
<IconSearch />
|
||||||
</A>
|
</A>
|
||||||
<A href="/" class="w-full flex justify-center items-center">
|
<A href="/" class="w-full flex justify-center items-center">
|
||||||
|
@ -11,15 +11,10 @@ import {
|
|||||||
CategoryUnion,
|
CategoryUnion,
|
||||||
getUserImages,
|
getUserImages,
|
||||||
JustTheImageWhatAreTheseNames,
|
JustTheImageWhatAreTheseNames,
|
||||||
|
UserImage,
|
||||||
} from "../network";
|
} from "../network";
|
||||||
import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage";
|
import { groupPropertiesWithImage } from "../utils/groupPropertiesWithImage";
|
||||||
|
|
||||||
export type ImageWithRawData = Awaited<
|
|
||||||
ReturnType<typeof getUserImages>
|
|
||||||
>["ImageProperties"][number] & {
|
|
||||||
rawData: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type TaggedCategory<T extends CategoryUnion["type"]> = Extract<
|
type TaggedCategory<T extends CategoryUnion["type"]> = Extract<
|
||||||
CategoryUnion,
|
CategoryUnion,
|
||||||
{ type: T }
|
{ type: T }
|
||||||
@ -30,7 +25,7 @@ type CategoriesSpecificData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type SearchImageStore = {
|
export type SearchImageStore = {
|
||||||
images: Accessor<ImageWithRawData[]>;
|
images: Accessor<UserImage[]>;
|
||||||
|
|
||||||
userImages: Accessor<JustTheImageWhatAreTheseNames>;
|
userImages: Accessor<JustTheImageWhatAreTheseNames>;
|
||||||
|
|
||||||
@ -44,51 +39,17 @@ export type SearchImageStore = {
|
|||||||
onRefetchImages: () => void;
|
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>();
|
const SearchImageContext = createContext<SearchImageStore>();
|
||||||
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
export const SearchImageContextProvider: Component<ParentProps> = (props) => {
|
||||||
const [data, { refetch }] = createResource(getUserImages);
|
const [data, { refetch }] = createResource(getUserImages);
|
||||||
|
|
||||||
const imageData = createMemo<ImageWithRawData[]>(() => {
|
const imageData = createMemo(() => {
|
||||||
const d = data();
|
const d = data();
|
||||||
if (d == null) {
|
if (d == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.ImageProperties.map((d) => ({
|
return d.ImageProperties;
|
||||||
...d,
|
|
||||||
rawData: getAllValues(d),
|
|
||||||
}));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const processingImages = () => data()?.ProcessingImages ?? [];
|
const processingImages = () => data()?.ProcessingImages ?? [];
|
||||||
|
@ -4,3 +4,4 @@ export * from "./image";
|
|||||||
export * from "./settings";
|
export * from "./settings";
|
||||||
export * from "./login";
|
export * from "./login";
|
||||||
export * from "./entity";
|
export * from "./entity";
|
||||||
|
export * from "./search";
|
||||||
|
48
frontend/src/pages/search/index.tsx
Normal file
48
frontend/src/pages/search/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
47
frontend/src/pages/search/search.ts
Normal file
47
frontend/src/pages/search/search.ts
Normal 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"] });
|
||||||
|
};
|
Reference in New Issue
Block a user