feat: gallery for each category

This commit is contained in:
2025-07-18 11:46:12 +01:00
parent e508f03abb
commit 3c3a25bcfc
3 changed files with 123 additions and 70 deletions

View File

@ -4,14 +4,14 @@ import { listen } from "@tauri-apps/api/event";
import { readFile } from "@tauri-apps/plugin-fs"; import { readFile } from "@tauri-apps/plugin-fs";
import { platform } from "@tauri-apps/plugin-os"; import { platform } from "@tauri-apps/plugin-os";
import { import {
type Component, type Component,
type ParentProps, type ParentProps,
createEffect, createEffect,
onCleanup, onCleanup,
} from "solid-js"; } from "solid-js";
import { import {
type ShareEvent, type ShareEvent,
listenForShareEvents, listenForShareEvents,
} from "tauri-plugin-sharetarget-api"; } from "tauri-plugin-sharetarget-api";
import { Login } from "./Login"; import { Login } from "./Login";
import { ProtectedRoute } from "./ProtectedRoute"; import { ProtectedRoute } from "./ProtectedRoute";
@ -22,83 +22,83 @@ import { SearchImageContextProvider } from "./contexts/SearchImageContext";
import { sendImageFile } from "./network"; import { sendImageFile } from "./network";
import { Image } from "./Image"; import { Image } from "./Image";
import { WithEntityDialog } from "./WithEntityDialog"; import { WithEntityDialog } from "./WithEntityDialog";
import { Gallery } from "./gallery";
const currentPlatform = platform(); const currentPlatform = platform();
console.log("Current Platform: ", currentPlatform); console.log("Current Platform: ", currentPlatform);
const AppWrapper: Component<ParentProps> = ({ children }) => { const AppWrapper: Component<ParentProps> = ({ children }) => {
return <div class="flex w-full justify-center h-screen">{children}</div>; return <div class="flex w-full justify-center h-screen">{children}</div>;
}; };
export const App = () => { export const App = () => {
createEffect(() => { createEffect(() => {
// TODO: Don't use window.location.href // TODO: Don't use window.location.href
const unlisten = listen("focus-search", () => { const unlisten = listen("focus-search", () => {
window.location.href = "/"; window.location.href = "/";
}); });
onCleanup(() => { onCleanup(() => {
unlisten.then((fn) => fn()); unlisten.then((fn) => fn());
}); });
}); });
createEffect(() => { createEffect(() => {
if (currentPlatform !== "android") { if (currentPlatform !== "android") {
return; return;
} }
let listener: PluginListener; let listener: PluginListener;
const setupListener = async () => { const setupListener = async () => {
console.log("Setting up listener"); console.log("Setting up listener");
listener = await listenForShareEvents( listener = await listenForShareEvents(async (intent: ShareEvent) => {
async (intent: ShareEvent) => { console.log(intent);
console.log(intent); const contents = await readFile(intent.stream ?? "").catch(
const contents = await readFile(intent.stream ?? "").catch( (error: Error) => {
(error: Error) => { console.warn("fetching shared content failed:");
console.warn("fetching shared content failed:"); throw error;
throw error; },
}, );
);
const f = new File([contents], intent.name ?? "no-name", { const f = new File([contents], intent.name ?? "no-name", {
type: intent.content_type, type: intent.content_type,
}); });
sendImageFile(f.name, f); sendImageFile(f.name, f);
}, });
); };
};
setupListener(); setupListener();
return () => { return () => {
listener?.unregister(); listener?.unregister();
}; };
}); });
return ( return (
<SearchImageContextProvider> <SearchImageContextProvider>
<ImageViewer /> <ImageViewer />
<Router> <Router>
<Route path="/" component={AppWrapper}> <Route path="/" component={AppWrapper}>
<Route path="/login" component={Login} /> <Route path="/login" component={Login} />
<Route path="/" component={ProtectedRoute}> <Route path="/" component={ProtectedRoute}>
<Route path="/" component={WithEntityDialog}> <Route path="/" component={WithEntityDialog}>
<Route path="/" component={Search} /> <Route path="/" component={Search} />
<Route path="/image/:imageId" component={Image} /> <Route path="/image/:imageId" component={Image} />
</Route> <Route path="/gallery/:entity" component={Gallery} />
<Route path="/settings" component={Settings} /> </Route>
</Route> <Route path="/settings" component={Settings} />
</Route> </Route>
<Route </Route>
path="*" <Route
component={() => { path="*"
return <Navigate href="/" />; component={() => {
}} return <Navigate href="/" />;
/> }}
</Router> />
</SearchImageContextProvider> </Router>
); </SearchImageContextProvider>
);
}; };

View File

@ -1,9 +1,11 @@
import { Component, createEffect, For } from "solid-js"; import { Component, For } from "solid-js";
import { import {
SearchImageStore, SearchImageStore,
useSearchImageContext, useSearchImageContext,
} from "../contexts/SearchImageContext"; } from "../contexts/SearchImageContext";
import { A } from "@solidjs/router";
// TODO: lots of stuff to do with Entities, this could be seperated into a centralized place.
const CategoryColor: Record< const CategoryColor: Record<
keyof ReturnType<SearchImageStore["categories"]>, keyof ReturnType<SearchImageStore["categories"]>,
string string
@ -21,7 +23,8 @@ export const Categories: Component = () => {
<div class="w-full grid grid-cols-4 auto-rows-[minmax(100px,1fr)] gap-4 rounded-xl bg-white p-4"> <div class="w-full grid grid-cols-4 auto-rows-[minmax(100px,1fr)] gap-4 rounded-xl bg-white p-4">
<For each={Object.entries(categories())}> <For each={Object.entries(categories())}>
{([category, group]) => ( {([category, group]) => (
<div <A
href={`/gallery/${category}`}
class={ class={
"col-span-2 flex flex-col justify-center items-center rounded-lg p-4 border border-neutral-200 " + "col-span-2 flex flex-col justify-center items-center rounded-lg p-4 border border-neutral-200 " +
"capitalize " + "capitalize " +
@ -32,7 +35,7 @@ export const Categories: Component = () => {
> >
<p class="text-xl font-bold">{category}s</p> <p class="text-xl font-bold">{category}s</p>
<p class="text-lg">{group.length}</p> <p class="text-lg">{group.length}</p>
</div> </A>
)} )}
</For> </For>
</div> </div>

View File

@ -0,0 +1,50 @@
import { Component, For, Show } from "solid-js";
import { useParams } from "@solidjs/router";
import { union, literal, safeParse, InferOutput, parse } from "valibot";
import { useSearchImageContext } from "../contexts/SearchImageContext";
import { SearchCard } from "../components/search-card/SearchCard";
const entityValidator = union([
literal("event"),
literal("note"),
literal("location"),
literal("contact"),
]);
const EntityGallery: Component<{
entity: InferOutput<typeof entityValidator>;
}> = (props) => {
// Just to be doubly sure.
parse(entityValidator, props.entity);
// These names are being silly. Entity or Category?
const { images } = useSearchImageContext();
const filteredCategories = () =>
images().filter((i) => i.type === props.entity);
return (
<div class="flex flex-col gap-4 capitalize">
<h2>{props.entity}s</h2>
<div class="grid grid-cols-3">
<For each={filteredCategories()}>
{(category) => <SearchCard item={category} />}
</For>
</div>
</div>
);
};
export const Gallery: Component = () => {
const params = useParams();
const validated = safeParse(entityValidator, params.entity);
return (
<Show
when={validated.success}
fallback={<p>Sorry, this entity is not supported</p>}
>
<EntityGallery entity={validated.output as any} />
</Show>
);
};