171 lines
6.7 KiB
TypeScript
171 lines
6.7 KiB
TypeScript
import { IconSearch } from "@tabler/icons-solidjs";
|
|
import clsx from "clsx";
|
|
import Fuse from "fuse.js";
|
|
import { For, createEffect, createResource, createSignal } from "solid-js";
|
|
import { SearchCardEvent } from "./components/search-card/SearchCardEvent";
|
|
import { SearchCardLocation } from "./components/search-card/SearchCardLocation";
|
|
import { UserImage, getUserImages } from "./network";
|
|
import { getCardSize } from "./utils/getCardSize";
|
|
|
|
const getCardComponent = (item: UserImage) => {
|
|
switch (item.type) {
|
|
case "location":
|
|
return <SearchCardLocation item={item} />;
|
|
case "event":
|
|
return <SearchCardEvent item={item} />;
|
|
// case "Contact":
|
|
// return <SearchCardContact item={item} />;
|
|
// case "Website":
|
|
// return <SearchCardWebsite item={item} />;
|
|
// case "Note":
|
|
// return <SearchCardNote item={item} />;
|
|
// case "Receipt":
|
|
// return <SearchCardReceipt item={item} />;
|
|
default:
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// How wonderfully functional
|
|
const getAllValues = (object: object): Array<string> => {
|
|
const loop = (acc: Array<string>, next: object): Array<string> => {
|
|
for (const _value of Object.values(next)) {
|
|
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);
|
|
};
|
|
|
|
function App() {
|
|
const [searchResults, setSearchResults] = createSignal<UserImage[]>([]);
|
|
const [searchQuery, setSearchQuery] = createSignal("");
|
|
const [selectedItem, setSelectedItem] = createSignal<UserImage | null>(
|
|
null,
|
|
);
|
|
|
|
const [data] = createResource(() =>
|
|
getUserImages().then((data) =>
|
|
data.map((d) => ({
|
|
...d,
|
|
rawData: getAllValues(d),
|
|
})),
|
|
),
|
|
);
|
|
|
|
let fuze = new Fuse<UserImage>(data() ?? [], {
|
|
keys: [
|
|
{ name: "rawData", weight: 1 },
|
|
{ name: "title", weight: 1 },
|
|
],
|
|
threshold: 0.4,
|
|
});
|
|
|
|
createEffect(() => {
|
|
fuze = new Fuse<UserImage>(data() ?? [], {
|
|
keys: [
|
|
{ name: "data.Name", weight: 2 },
|
|
{ name: "rawData", weight: 1 },
|
|
],
|
|
threshold: 0.4,
|
|
});
|
|
});
|
|
|
|
const onInputChange = (event: InputEvent) => {
|
|
const query = (event.target as HTMLInputElement).value;
|
|
setSearchQuery(query);
|
|
setSearchResults(fuze.search(query).map((s) => s.item));
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<main class="container pt-2">
|
|
<div class="px-4">
|
|
<div class="inline-flex justify-between w-full rounded-xl text-base leading-none outline-none bg-white border border-neutral-200 text-neutral-900">
|
|
<div class="appearance-none inline-flex justify-center items-center w-auto outline-none rounded-l-md px-2.5 text-gray-900">
|
|
<IconSearch
|
|
size={20}
|
|
class="m-auto size-5 text-neutral-600"
|
|
/>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={searchQuery()}
|
|
onInput={onInputChange}
|
|
placeholder="Search for stuff..."
|
|
class="appearance-none inline-flex w-full min-h-[40px] text-base bg-transparent rounded-l-md outline-none placeholder:text-gray-600"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="px-4 mt-4 bg-white rounded-t-2xl">
|
|
<div class="h-[254px] mt-4 overflow-scroll scrollbar-hide">
|
|
{searchResults().length > 0 ? (
|
|
<div class="w-full grid grid-cols-9 gap-2 grid-flow-row-dense py-4">
|
|
<For each={searchResults()}>
|
|
{(item) => (
|
|
<div
|
|
onClick={() =>
|
|
setSelectedItem(item)
|
|
}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
setSelectedItem(item);
|
|
}
|
|
}}
|
|
class={clsx(
|
|
"h-[144px] border relative border-neutral-200 cursor-pointer overflow-hidden rounded-xl",
|
|
{
|
|
"col-span-3":
|
|
getCardSize(
|
|
item.type,
|
|
) === "1/1",
|
|
"col-span-6":
|
|
getCardSize(
|
|
item.type,
|
|
) === "2/1",
|
|
},
|
|
)}
|
|
>
|
|
<span class="sr-only">
|
|
{item.data.Name}
|
|
</span>
|
|
{getCardComponent(item)}
|
|
</div>
|
|
)}
|
|
</For>
|
|
</div>
|
|
) : searchQuery() !== "" ? (
|
|
<div class="text-center text-lg m-auto text-neutral-700">
|
|
No results found
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="w-full border-t h-10 bg-white px-4 flex items-center border-neutral-100">
|
|
footer
|
|
</div>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default App;
|