diff --git a/backend/events.go b/backend/events.go index 4c021cb..201281b 100644 --- a/backend/events.go +++ b/backend/events.go @@ -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() } } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ac04e58..56ac39d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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 ( - diff --git a/frontend/src/ProtectedRoute.tsx b/frontend/src/ProtectedRoute.tsx index dbed383..6bd8bd8 100644 --- a/frontend/src/ProtectedRoute.tsx +++ b/frontend/src/ProtectedRoute.tsx @@ -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 => { diff --git a/frontend/src/Search.tsx b/frontend/src/Search.tsx index 83a6746..d7357e4 100644 --- a/frontend/src/Search.tsx +++ b/frontend/src/Search.tsx @@ -168,6 +168,8 @@ export const Search = () => { +

Images

+
{(imageId) => ( diff --git a/frontend/src/components/image-status/ImageStatus.tsx b/frontend/src/components/image-status/ImageStatus.tsx deleted file mode 100644 index f49a6fc..0000000 --- a/frontend/src/components/image-status/ImageStatus.tsx +++ /dev/null @@ -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> | undefined, - ) => void; - processingImage: Accessor< - Awaited> | undefined - >; -}; - -type EventData = "in-progress" | "complete"; - -export const ImageStatus: Component = (props) => { - const { onRefetchImages } = useSearchImageContext(); - - let processing = false; - - const onEvent = (e: MessageEvent) => { - 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; -}; diff --git a/frontend/src/notifications/ProcessingImages.tsx b/frontend/src/notifications/ProcessingImages.tsx index 73ab363..13b3eb9 100644 --- a/frontend/src/notifications/ProcessingImages.tsx +++ b/frontend/src/notifications/ProcessingImages.tsx @@ -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 ( - - {([id]) => processing} - +
+

Processing Images

+ + {([id]) => processing} + +
); }; diff --git a/frontend/src/notifications/index.ts b/frontend/src/notifications/index.ts index 7002552..5bd5e8a 100644 --- a/frontend/src/notifications/index.ts +++ b/frontend/src/notifications/index.ts @@ -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["Status"] + InferOutput["Status"] | undefined >; }; @@ -35,7 +35,20 @@ export const Notifications = () => { } const dataEventListener = (e: MessageEvent) => { - 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, };