diff --git a/demo/src/main.rs b/demo/src/main.rs index a6c4afe..ec96ad0 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -27,6 +27,7 @@ fn main() { } // TODO: Use token to make request + // Check the hash of the value let value_hash = sha2::Sha256::digest(key_data.value.as_bytes()); let hash_match = hex::encode(value_hash) == key_data.value_hash; diff --git a/frontend/src/components/StatefulDemo.tsx b/frontend/src/components/Demo.tsx similarity index 58% rename from frontend/src/components/StatefulDemo.tsx rename to frontend/src/components/Demo.tsx index d71cea9..99bc9ec 100644 --- a/frontend/src/components/StatefulDemo.tsx +++ b/frontend/src/components/Demo.tsx @@ -1,10 +1,10 @@ import Badge from "@/components/Badge"; import Emboldened from "@/components/Emboldened"; +import useSocket from "@/components/useSocket"; import { cn, plural, type ClassValue } from "@/util"; import { useRef, useState } from "preact/hooks"; -import { useEffect } from "preact/hooks"; -type StatefulDemoProps = { +type DemoProps = { class?: ClassValue; }; @@ -13,51 +13,16 @@ type SessionData = { downloads: string[]; }; -const StatefulDemo = ({ class: className }: StatefulDemoProps) => { - useEffect(() => { - const socket = new WebSocket( - (window.location.protocol === "https:" ? "wss://" : "ws://") + - (import.meta.env.DEV != undefined - ? "localhost:5800" - : window.location.host) + - "/ws" - ); - - socket.onmessage = (event) => { - const data = JSON.parse(event.data); - - if (data.type == undefined) - throw new Error("Received message without type"); - - switch (data.type) { - case "session": - setSession(data.session); - break; - default: - console.warn("Received unknown message type", data.type); - } - }; - - socket.onclose = () => { - console.log("WebSocket connection closed"); - }; - - return () => { - socket.close(); - }; - }, []); +const Demo = ({ class: className }: DemoProps) => { + const { id, downloads } = useSocket(); // TODO: Toasts + const randomBits = (bits: number) => Math.floor(Math.random() * 2 ** bits) .toString(16) .padStart(bits / 4, "0") .toUpperCase(); - const [session, setSession] = useState({ - id: "0×" + randomBits(32), - downloads: Array.from({ length: 7 }).map(() => "0×" + randomBits(16)), - }); - const [highlightedIndex, setHighlightedIndex] = useState(null); const highlightedTimeoutRef = useRef(null); @@ -81,19 +46,18 @@ const StatefulDemo = ({ class: className }: StatefulDemoProps) => { browser. Each download gets a unique identifier bound to the user session.
- {session != null ? ( - <> - Your session is{" "} - {session.id}. You have{" "} - - {session.downloads.length} - {" "} - known {plural("download", session.downloads.length)}. - - ) : null} + Your session is{" "} + + {id} + + . You have{" "} + + {downloads?.length ?? null} + {" "} + known {plural("download", downloads?.length ?? 0)}.

- {session?.downloads.map((download, i) => ( + {downloads?.map((download, i) => ( { {download} ))} + download

@@ -123,4 +88,4 @@ const StatefulDemo = ({ class: className }: StatefulDemoProps) => { ); }; -export default StatefulDemo; +export default Demo; diff --git a/frontend/src/components/Emboldened.tsx b/frontend/src/components/Emboldened.tsx index fa20ded..e518486 100644 --- a/frontend/src/components/Emboldened.tsx +++ b/frontend/src/components/Emboldened.tsx @@ -1,29 +1,38 @@ import { cn, type ClassValue } from "@/util"; type EmboldenedProps = { - children: string | number; + children: string | number | null; + skeletonWidth?: string; className?: ClassValue; copyable?: boolean; }; -const Emboldened = ({ children, copyable, className }: EmboldenedProps) => { +const Emboldened = ({ + children, + skeletonWidth, + copyable, + className, +}: EmboldenedProps) => { function copyToClipboard() { // Copy to clipboard - navigator.clipboard.writeText(children.toString()); + if (children != null) navigator.clipboard.writeText(children.toString()); } - return ( - {children} + {children ?? ( + + {skeletonWidth ?? "?"} + + )} ); }; diff --git a/frontend/src/components/useSocket.ts b/frontend/src/components/useSocket.ts new file mode 100644 index 0000000..b878c91 --- /dev/null +++ b/frontend/src/components/useSocket.ts @@ -0,0 +1,63 @@ +import { useEffect, useState } from "preact/hooks"; + +interface Download { + token: number; + filename: string; + last_used: string; + download_time: string; +} + +interface UseSocketResult { + sessionId: string | null; + downloads: Download[] | null; + deleteDownload: (id: string) => void; +} + +function useSocket(): UseSocketResult { + const [sessionId, setSessionId] = useState(null); + const [sessionDownloads, setSessionDownloads] = useState( + null + ); + + function deleteDownload() {} + + useEffect(() => { + const socket = new WebSocket( + (window.location.protocol === "https:" ? "wss://" : "ws://") + + (import.meta.env.DEV != undefined + ? "localhost:5800" + : window.location.host) + + "/ws" + ); + + socket.onmessage = (event) => { + const data = JSON.parse(event.data); + + if (data.type == undefined) + throw new Error("Received message without type"); + + switch (data.type) { + case "state": + const downloads = data.downloads as Download[]; + setSessionId(data.session); + setSessionDownloads(downloads); + break; + default: + console.warn("Received unknown message type", data.type); + } + }; + + socket.onclose = () => { + console.log("WebSocket connection closed"); + }; + + return () => { + // Close the socket when the component is unmounted + socket.close(); + }; + }, []); + + return { sessionId, sessionDownloads, deleteDownload }; +} + +export default useSocket; diff --git a/frontend/src/pages/index.astro b/frontend/src/pages/index.astro index c1426af..f2d1afb 100644 --- a/frontend/src/pages/index.astro +++ b/frontend/src/pages/index.astro @@ -1,6 +1,6 @@ --- import Base from "@/layouts/Base.astro"; -import StatefulDemo from "@/components/StatefulDemo.tsx"; +import Demo from "@/components/Demo"; --- @@ -43,7 +43,7 @@ import StatefulDemo from "@/components/StatefulDemo.tsx"; Demo


- +