feat: frontend receiving processing images and showing them as such
This commit is contained in:
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -151,6 +152,7 @@ func CreateEventsHandler(notifier *Notifier[Notification]) http.HandlerFunc {
|
||||
// EG: The user could attempt to create many connections
|
||||
// and they just get a 500, with no explanation.
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.(http.Flusher).Flush()
|
||||
return
|
||||
}
|
||||
|
||||
@ -163,8 +165,15 @@ func CreateEventsHandler(notifier *Notifier[Notification]) http.HandlerFunc {
|
||||
w.(http.Flusher).Flush()
|
||||
return
|
||||
case msg := <-listener:
|
||||
|
||||
msgString, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Sending msg %s\n", msg)
|
||||
fmt.Fprintf(w, "event: data\ndata: %s\n\n", msg)
|
||||
fmt.Fprintf(w, "event: data\ndata: %s\n\n", string(msgString))
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import { ProtectedRoute } from "./ProtectedRoute";
|
||||
import { Search } from "./Search";
|
||||
import { Settings } from "./Settings";
|
||||
import { ImageViewer } from "./components/ImageViewer";
|
||||
import { ImageStatus } from "./components/image-status/ImageStatus";
|
||||
import { SearchImageContextProvider } from "./contexts/SearchImageContext";
|
||||
import { type sendImage, sendImageFile } from "./network";
|
||||
import { Image } from "./Image";
|
||||
@ -86,10 +85,6 @@ export const App = () => {
|
||||
return (
|
||||
<SearchImageContextProvider>
|
||||
<ImageViewer onSendImage={setProcessingImage} />
|
||||
<ImageStatus
|
||||
processingImage={processingImage}
|
||||
onSetProcessingImage={setProcessingImage}
|
||||
/>
|
||||
<Router>
|
||||
<Route path="/" component={AppWrapper}>
|
||||
<Route path="/login" component={Login} />
|
||||
|
@ -1,14 +1,8 @@
|
||||
import {
|
||||
type Component,
|
||||
type JSX,
|
||||
ParentProps,
|
||||
Show,
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { Navigate } from "@solidjs/router";
|
||||
import { save_token } from "tauri-plugin-ios-shared-token-api";
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
import { jwtDecode } from "jwt-decode";
|
||||
import { type Component, type ParentProps, Show, useContext } from "solid-js";
|
||||
import { save_token } from "tauri-plugin-ios-shared-token-api";
|
||||
import { Notifications, NotificationsContext } from "./notifications";
|
||||
|
||||
export const isTokenValid = (): boolean => {
|
||||
|
@ -168,6 +168,8 @@ export const Search = () => {
|
||||
|
||||
<ProcessingImages />
|
||||
|
||||
<h2 class="text-xl">Images</h2>
|
||||
|
||||
<div class="w-full grid grid-cols-9 gap-2 grid-flow-row-dense py-4">
|
||||
<For each={Object.keys(imagesWithProperties())}>
|
||||
{(imageId) => (
|
||||
|
@ -1,67 +0,0 @@
|
||||
import { createEffect, type Accessor, type Component } from "solid-js";
|
||||
import { base, type sendImage } from "../../network";
|
||||
import { useSearchImageContext } from "../../contexts/SearchImageContext";
|
||||
|
||||
type ImageStatusProps = {
|
||||
onSetProcessingImage: (
|
||||
image: Awaited<ReturnType<typeof sendImage>> | undefined,
|
||||
) => void;
|
||||
processingImage: Accessor<
|
||||
Awaited<ReturnType<typeof sendImage>> | undefined
|
||||
>;
|
||||
};
|
||||
|
||||
type EventData = "in-progress" | "complete";
|
||||
|
||||
export const ImageStatus: Component<ImageStatusProps> = (props) => {
|
||||
const { onRefetchImages } = useSearchImageContext();
|
||||
|
||||
let processing = false;
|
||||
|
||||
const onEvent = (e: MessageEvent<EventData>) => {
|
||||
console.log("Processing Events: ", e.data);
|
||||
|
||||
const processingImage = props.processingImage();
|
||||
if (processingImage == null) {
|
||||
throw new Error("Processing Image should not be null");
|
||||
}
|
||||
|
||||
if (e.data !== "complete") {
|
||||
props.onSetProcessingImage({
|
||||
...processingImage,
|
||||
Status: e.data,
|
||||
});
|
||||
|
||||
processing = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
props.onSetProcessingImage(undefined);
|
||||
onRefetchImages();
|
||||
};
|
||||
|
||||
createEffect(() => {
|
||||
const image = props.processingImage();
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (processing) {
|
||||
return;
|
||||
}
|
||||
|
||||
processing = true;
|
||||
const eventSourceUrl = `${base}/image-events/${image.ID}`;
|
||||
|
||||
const eventSource = new EventSource(eventSourceUrl);
|
||||
|
||||
eventSource.addEventListener("data", onEvent);
|
||||
|
||||
return () => {
|
||||
eventSource.removeEventListener("data", onEvent);
|
||||
};
|
||||
});
|
||||
|
||||
return null;
|
||||
};
|
@ -1,13 +1,16 @@
|
||||
import { type Component, For } from "solid-js";
|
||||
import { useNotifications } from "../ProtectedRoute";
|
||||
import { base } from "../network";
|
||||
import { useNotifications } from "../ProtectedRoute";
|
||||
|
||||
export const ProcessingImages: Component = () => {
|
||||
const notifications = useNotifications();
|
||||
|
||||
return (
|
||||
<For each={Object.entries(notifications.state.ProcessingImages)}>
|
||||
{([id]) => <img alt="processing" src={`${base}/image/${id}`} />}
|
||||
</For>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
@ -10,17 +10,17 @@ import {
|
||||
uuid,
|
||||
} from "valibot";
|
||||
import { base } from "../network";
|
||||
import { createContext } from "solid-js";
|
||||
import { createContext, onCleanup } from "solid-js";
|
||||
|
||||
const processingImagesValidator = strictObject({
|
||||
ImageID: pipe(string(), uuid()),
|
||||
Status: union([literal("in-process"), literal("complete")]),
|
||||
Status: union([literal("in-progress"), literal("complete")]),
|
||||
});
|
||||
|
||||
type NotificationState = {
|
||||
ProcessingImages: Record<
|
||||
string,
|
||||
InferOutput<typeof processingImagesValidator>["Status"]
|
||||
InferOutput<typeof processingImagesValidator>["Status"] | undefined
|
||||
>;
|
||||
};
|
||||
|
||||
@ -35,7 +35,20 @@ export const Notifications = () => {
|
||||
}
|
||||
|
||||
const dataEventListener = (e: MessageEvent<unknown>) => {
|
||||
const processingImage = safeParse(processingImagesValidator, e.data);
|
||||
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;
|
||||
@ -43,32 +56,25 @@ export const Notifications = () => {
|
||||
|
||||
const { ImageID, Status } = processingImage.output;
|
||||
|
||||
if (ImageID in state.ProcessingImages) {
|
||||
if (state.ProcessingImages[ImageID] != null) {
|
||||
const localImageStatus = state.ProcessingImages[ImageID];
|
||||
if (localImageStatus !== "in-process" || Status !== "complete") {
|
||||
if (localImageStatus !== "in-progress" || Status !== "complete") {
|
||||
console.error(
|
||||
"Invalid state, an image present must always be in process",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState("ProcessingImages", (images) => {
|
||||
delete images[ImageID];
|
||||
|
||||
// TODO: this might not work because it's not a new object.
|
||||
return images;
|
||||
});
|
||||
setState("ProcessingImages", ImageID, undefined);
|
||||
} else {
|
||||
if (Status !== "in-process") {
|
||||
if (Status !== "in-progress") {
|
||||
console.error(
|
||||
"Invalid state, at this point a new image that isn't present should be in-progress",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState("ProcessingImages", {
|
||||
[ImageID]: Status,
|
||||
});
|
||||
setState("ProcessingImages", ImageID, Status);
|
||||
}
|
||||
};
|
||||
|
||||
@ -76,6 +82,11 @@ export const Notifications = () => {
|
||||
|
||||
events.addEventListener("data", dataEventListener);
|
||||
|
||||
onCleanup(() => {
|
||||
events.removeEventListener("data", dataEventListener);
|
||||
events.close();
|
||||
});
|
||||
|
||||
return {
|
||||
state,
|
||||
};
|
||||
|
Reference in New Issue
Block a user