import prettyBytes from "pretty-bytes"; import React, { ReactElement, useEffect, useRef, useState } from "react"; import { useStore } from "../../store"; import type { Socket } from "socket.io-client"; /** * @typedef {Object} DownloadProgressTickProps * @property {string} bundleId - The bundle ID to filter ticks by (as string) */ interface DownloadProgressTickProps { bundleId: string; } /** * Shape of the download tick data received over the socket. * * @typedef DownloadTickData * @property {number} id - Unique download ID * @property {string} name - File name (e.g. "movie.mkv") * @property {number} downloaded_bytes - Bytes downloaded so far * @property {number} size - Total size in bytes * @property {number} speed - Current download speed (bytes/sec) * @property {number} seconds_left - Estimated seconds remaining * @property {{ id: string; str: string; completed: boolean; downloaded: boolean; failed: boolean; hook_error: any }} status * - Status object (e.g. `{ id: "queued", str: "Running (15.1%)", ... }`) * @property {{ online: number; total: number; str: string }} sources * - Peer count (e.g. `{ online: 1, total: 1, str: "1/1 online" }`) * @property {string} target - Download destination (e.g. "/Downloads/movie.mkv") */ interface DownloadTickData { id: number; name: string; downloaded_bytes: number; size: number; speed: number; seconds_left: number; status: { id: string; str: string; completed: boolean; downloaded: boolean; failed: boolean; hook_error: any; }; sources: { online: number; total: number; str: string; }; target: string; } export const DownloadProgressTick: React.FC = ({ bundleId, }): ReactElement | null => { const socketRef = useRef(); const [tick, setTick] = useState(null); useEffect(() => { const socket = useStore.getState().getSocket("manual"); socketRef.current = socket; socket.emit("call", "socket.listenFileProgress", { namespace: "/manual", config: { protocol: `ws`, hostname: `192.168.1.119:5600`, username: `admin`, password: `password`, }, }); /** * Handler for each "downloadTick" event. * Only update state if event.id matches bundleId. * * @param {DownloadTickData} data - Payload from the server */ const onDownloadTick = (data: DownloadTickData) => { // Compare numeric data.id to string bundleId if (data.id === parseInt(bundleId, 10)) { setTick(data); } }; socket.on("downloadTick", onDownloadTick); return () => { socket.off("downloadTick", onDownloadTick); }; }, [socketRef, bundleId]); if (!tick) { return <>Nothing detected.; } // Compute human-readable values and percentages const downloaded = prettyBytes(tick.downloaded_bytes); const total = prettyBytes(tick.size); const percent = tick.size > 0 ? Math.round((tick.downloaded_bytes / tick.size) * 100) : 0; const speed = prettyBytes(tick.speed) + "/s"; const minutesLeft = Math.round(tick.seconds_left / 60); return (
{/* Downloaded vs Total */}
{downloaded} of {total}
{/* Progress bar */}
{percent}% complete
{/* Speed and Time Left */}
Speed: {speed} Time left: {minutesLeft} min
); }; export default DownloadProgressTick;