feat: processing images and notification centre

This commit is contained in:
2025-07-02 14:12:19 +01:00
parent c62378c20a
commit 6482a76a51
4 changed files with 108 additions and 23 deletions

View File

@@ -1,16 +1,86 @@
import { type Component, For } from "solid-js";
import { Popover } from "@kobalte/core/popover";
import { type Component, For, Show } from "solid-js";
import { base } from "../network";
import { useNotifications } from "../ProtectedRoute";
import { useSearchImageContext } from "../contexts/SearchImageContext";
const LoadingCircle: Component<{ status: "loading" | "complete" }> = (
props,
) => {
switch (props.status) {
case "loading":
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
fill="none"
stroke-width="3"
class="stroke-amber-400"
/>
</svg>
);
case "complete":
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="12"
cy="12"
r="10"
fill="none"
stroke-width="3"
class="stroke-emerald-400"
/>
</svg>
);
}
};
export const ProcessingImages: Component = () => {
const notifications = useNotifications();
const notifications = useNotifications();
return (
<div class="w-full">
<h2 class="text-xl">Processing Images</h2>
<For each={Object.entries(notifications.state.ProcessingImages)}>
{([id]) => <img alt="processing" src={`${base}/image/${id}`} />}
</For>
</div>
);
return (
<Popover>
<Popover.Trigger class="w-full flex justify-between rounded-xl bg-slate-800 text-gray-100 px-4 py-2">
<p class="text-md">Processing Images</p>
<Show
when={Object.keys(notifications.state.ProcessingImages).length === 0}
fallback={<LoadingCircle status="loading" />}
>
<LoadingCircle status="complete" />
</Show>
</Popover.Trigger>
<Popover.Content class="w-96 flex flex-col gap-2 bg-slate-800 rounded-xl">
<For each={Object.entries(notifications.state.ProcessingImages)}>
{([id, _image]) => (
<Show when={_image}>
{(image) => (
<div class="flex gap-2">
<img
class="w-16 h-16 aspect-square"
alt="processing"
src={`${base}/image/${id}`}
/>
<div class="flex flex-col gap-1">
<p class="text-slate-100">{image().ImageName}</p>
</div>
</div>
)}
</Show>
)}
</For>
</Popover.Content>
</Popover>
);
};

View File

@@ -15,6 +15,7 @@ import { useSearchImageContext } from "../contexts/SearchImageContext";
const processingImagesValidator = strictObject({
ImageID: pipe(string(), uuid()),
ImageName: string(),
Status: union([
literal("not-started"),
literal("in-progress"),
@@ -25,7 +26,7 @@ const processingImagesValidator = strictObject({
type NotificationState = {
ProcessingImages: Record<
string,
InferOutput<typeof processingImagesValidator>["Status"] | undefined
InferOutput<typeof processingImagesValidator> | undefined
>;
};
@@ -69,7 +70,7 @@ export const Notifications = (onCompleteImage: () => void) => {
setState("ProcessingImages", ImageID, undefined);
onCompleteImage();
} else {
setState("ProcessingImages", ImageID, Status);
setState("ProcessingImages", ImageID, processingImage.output);
}
};
@@ -89,7 +90,16 @@ export const Notifications = (onCompleteImage: () => void) => {
}
upsertImageProcessing(
Object.fromEntries(images.map((i) => [i.ImageID, i.Status])),
Object.fromEntries(
images.map((i) => [
i.ImageID,
{
ImageID: i.ImageID,
ImageName: i.Image.ImageName,
Status: i.Status,
},
]),
),
);
});