126 lines
2.6 KiB
TypeScript
126 lines
2.6 KiB
TypeScript
import { createStore } from "solid-js/store";
|
|
import {
|
|
type InferOutput,
|
|
literal,
|
|
pipe,
|
|
safeParse,
|
|
strictObject,
|
|
string,
|
|
union,
|
|
uuid,
|
|
} from "valibot";
|
|
import { base } from "../network";
|
|
import { createContext, createEffect, onCleanup } from "solid-js";
|
|
import { useSearchImageContext } from "../contexts/SearchImageContext";
|
|
|
|
const processingImagesValidator = strictObject({
|
|
ImageID: pipe(string(), uuid()),
|
|
ImageName: string(),
|
|
Status: union([
|
|
literal("not-started"),
|
|
literal("in-progress"),
|
|
literal("complete"),
|
|
]),
|
|
});
|
|
|
|
type NotificationState = {
|
|
ProcessingImages: Record<
|
|
string,
|
|
InferOutput<typeof processingImagesValidator> | undefined
|
|
>;
|
|
};
|
|
|
|
export const Notifications = (onCompleteImage: () => void) => {
|
|
const [state, setState] = createStore<NotificationState>({
|
|
ProcessingImages: {},
|
|
});
|
|
|
|
const { processingImages } = useSearchImageContext();
|
|
|
|
const access = localStorage.getItem("access");
|
|
if (access == null) {
|
|
throw new Error("Access token not defined");
|
|
}
|
|
|
|
const dataEventListener = (e: MessageEvent<unknown>) => {
|
|
if (typeof e.data !== "string") {
|
|
console.error("Error type is not string");
|
|
return;
|
|
}
|
|
|
|
let jsonData: object = {};
|
|
try {
|
|
jsonData = JSON.parse(e.data);
|
|
} catch (e) {
|
|
console.error(e);
|
|
return;
|
|
}
|
|
|
|
const processingImage = safeParse(processingImagesValidator, jsonData);
|
|
if (!processingImage.success) {
|
|
console.error("Processing image could not be parsed.", e.data);
|
|
return;
|
|
}
|
|
|
|
console.log("SSE: ", processingImage);
|
|
|
|
const { ImageID, Status } = processingImage.output;
|
|
|
|
if (Status === "complete") {
|
|
setState("ProcessingImages", ImageID, undefined);
|
|
onCompleteImage();
|
|
} else {
|
|
setState("ProcessingImages", ImageID, processingImage.output);
|
|
}
|
|
};
|
|
|
|
const upsertImageProcessing = (
|
|
images: NotificationState["ProcessingImages"],
|
|
) => {
|
|
setState("ProcessingImages", (currentImages) => ({
|
|
...currentImages,
|
|
...images,
|
|
}));
|
|
};
|
|
|
|
createEffect(() => {
|
|
const images = processingImages();
|
|
if (images == null) {
|
|
return;
|
|
}
|
|
|
|
upsertImageProcessing(
|
|
Object.fromEntries(
|
|
images.map((i) => [
|
|
i.ImageID,
|
|
{
|
|
ImageID: i.ImageID,
|
|
ImageName: i.Image.ImageName,
|
|
Status: i.Status,
|
|
},
|
|
]),
|
|
),
|
|
);
|
|
});
|
|
|
|
const events = new EventSource(`${base}/notifications?token=${access}`);
|
|
|
|
events.addEventListener("data", dataEventListener);
|
|
|
|
events.onerror = (e) => {
|
|
console.error(e);
|
|
};
|
|
|
|
onCleanup(() => {
|
|
events.removeEventListener("data", dataEventListener);
|
|
events.close();
|
|
});
|
|
|
|
return {
|
|
state,
|
|
};
|
|
};
|
|
|
|
export const NotificationsContext =
|
|
createContext<ReturnType<typeof Notifications>>();
|