130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
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<DownloadProgressTickProps> = ({
|
|
bundleId,
|
|
}): ReactElement | null => {
|
|
const socketRef = useRef<Socket>();
|
|
const [tick, setTick] = useState<DownloadTickData | null>(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 (
|
|
<div className="mt-2 p-2 border rounded-md bg-white shadow-sm">
|
|
{/* Downloaded vs Total */}
|
|
<div className="mt-1 flex items-center space-x-2">
|
|
<span className="text-sm text-gray-700">{downloaded} of {total}</span>
|
|
</div>
|
|
|
|
{/* Progress bar */}
|
|
<div className="relative mt-2 h-2 bg-gray-200 rounded overflow-hidden">
|
|
<div
|
|
className="absolute inset-y-0 left-0 bg-green-500"
|
|
style={{ width: `${percent}%` }}
|
|
/>
|
|
</div>
|
|
<div className="mt-1 text-xs text-gray-600">{percent}% complete</div>
|
|
|
|
{/* Speed and Time Left */}
|
|
<div className="mt-2 flex space-x-4 text-xs text-gray-600">
|
|
<span>Speed: {speed}</span>
|
|
<span>Time left: {minutesLeft} min</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DownloadProgressTick;
|