librqbit 9.0.1

The main library used by rqbit torrent client. The binary is just a small wrapper on top of it.
Documentation
import { TorrentListItem, STATE_INITIALIZING } from "../../api-types";
import { StatusIcon } from "../StatusIcon";
import { formatBytes } from "../../helper/formatBytes";
import { getCompletionETA } from "../../helper/getCompletionETA";
import { memo } from "react";

interface TorrentTableRowProps {
  torrent: TorrentListItem;
  isSelected: boolean;
  onRowClick: (id: number, e: React.MouseEvent) => void;
  onCheckboxChange: (id: number) => void;
}

const TorrentTableRowUnmemoized: React.FC<TorrentTableRowProps> = ({
  torrent,
  isSelected,
  onRowClick,
  onCheckboxChange,
}) => {
  const stats = torrent.stats;
  const state = stats?.state ?? "";
  const error = stats?.error ?? null;
  const totalBytes = stats?.total_bytes ?? 1;
  const progressBytes = stats?.progress_bytes ?? 0;
  const finished = stats?.finished || false;
  const live = !!stats?.live;

  const progressPercentage = error
    ? 100
    : totalBytes === 0
      ? 100
      : Math.round((progressBytes / totalBytes) * 100);

  const downloadSpeed = stats?.live?.download_speed?.human_readable ?? "-";
  const uploadSpeed = stats?.live?.upload_speed?.human_readable ?? "-";
  const uploadedBytes = stats?.live?.snapshot.uploaded_bytes ?? 0;

  const peerStats = stats?.live?.snapshot.peer_stats;
  const peersDisplay = peerStats ? `${peerStats.live}/${peerStats.seen}` : "-";

  const eta = stats ? getCompletionETA(stats) : "-";
  const displayEta = finished ? "Done" : eta;

  const name = torrent.name ?? "";

  const handleRowClick = (e: React.MouseEvent) => {
    onRowClick(torrent.id, e);
  };

  const handleCheckboxClick = (e: React.MouseEvent) => {
    e.stopPropagation();
    onCheckboxChange(torrent.id);
  };

  // Common cell styles to avoid repetition
  const cellBase = "px-2 align-middle";
  const numericCell = `w-20 ${cellBase} text-right text-secondary whitespace-nowrap`;
  const centeredCell = `w-20 ${cellBase} text-center text-secondary whitespace-nowrap`;

  // Use table-fixed layout to match header column widths
  return (
    <table className="w-full table-fixed">
      <tbody>
        <tr
          onMouseDown={handleRowClick}
          className={`cursor-pointer border-b border-divider text-sm h-8 ${
            isSelected ? "bg-primary/10" : "hover:bg-surface-raised"
          }`}
        >
          <td
            className={`w-8 ${cellBase} text-center`}
            onMouseDown={handleCheckboxClick}
          >
            <input
              type="checkbox"
              checked={isSelected}
              onChange={() => {}}
              className="w-4 h-4 rounded border-divider-strong bg-surface text-primary focus:ring-primary"
            />
          </td>
          <td className="w-8 px-1 align-middle">
            <StatusIcon
              className="w-5 h-5"
              error={!!error}
              live={live}
              finished={finished}
            />
          </td>
          <td
            className={`w-12 ${cellBase} text-center text-tertiary font-mono whitespace-nowrap`}
          >
            {torrent.id}
          </td>
          <td className={cellBase}>
            <div className="truncate" title={name}>
              {name || "Loading..."}
            </div>
            {error && (
              <div className="truncate text-sm text-error" title={error}>
                {error}
              </div>
            )}
          </td>
          <td className={numericCell}>{formatBytes(totalBytes)}</td>
          <td className={`w-24 ${cellBase} text-center`}>
            <div className="flex items-center gap-2">
              <div className="flex-1 h-1.5 bg-divider rounded-full overflow-hidden">
                <div
                  className={`h-full rounded-full ${
                    error
                      ? "bg-error-bg"
                      : finished
                        ? "bg-success-bg"
                        : state === STATE_INITIALIZING
                          ? "bg-warning-bg"
                          : "bg-primary-bg"
                  }`}
                  style={{ width: `${progressPercentage}%` }}
                />
              </div>
              <span className="text-sm text-secondary w-8 text-right">
                {progressPercentage}%
              </span>
            </div>
          </td>
          <td className={numericCell}>{formatBytes(progressBytes)}</td>
          <td className={numericCell}>{downloadSpeed}</td>
          <td className={numericCell}>{uploadSpeed}</td>
          <td className={numericCell}>
            {uploadedBytes > 0 && <>{formatBytes(uploadedBytes)}</>}
          </td>
          <td className={centeredCell}>{displayEta}</td>
          <td
            className={`w-16 ${cellBase} text-center text-secondary whitespace-nowrap`}
          >
            {peersDisplay}
          </td>
        </tr>
      </tbody>
    </table>
  );
};

export const TorrentTableRow = memo(TorrentTableRowUnmemoized);