librqbit 9.0.0

The main library used by rqbit torrent client. The binary is just a small wrapper on top of it.
Documentation
import { useContext, useState } from "react";
import { TorrentListItem } from "../../api-types";
import { APIContext } from "../../context";
import { ErrorWithLabel } from "../../rqbit-web";
import { useTorrentStore } from "../../stores/torrentStore";
import { useUIStore } from "../../stores/uiStore";
import { Button } from "../buttons/Button";
import { ErrorComponent } from "../ErrorComponent";
import { Spinner } from "../Spinner";
import { Modal } from "./Modal";
import { ModalBody } from "./ModalBody";
import { ModalFooter } from "./ModalFooter";

export const DeleteTorrentModal: React.FC<{
  show: boolean;
  onHide: () => void;
  torrents: Pick<TorrentListItem, "id" | "name">[];
}> = ({ show, onHide, torrents }) => {
  const [deleteFiles, setDeleteFiles] = useState(false);
  const [error, setError] = useState<ErrorWithLabel | null>(null);
  const [deleting, setDeleting] = useState(false);

  const API = useContext(APIContext);
  const refreshTorrents = useTorrentStore((state) => state.refreshTorrents);
  const clearSelection = useUIStore((state) => state.clearSelection);

  if (!show || torrents.length === 0) {
    return null;
  }

  const close = () => {
    setDeleteFiles(false);
    setError(null);
    setDeleting(false);
    onHide();
  };

  const deleteTorrents = async () => {
    setDeleting(true);
    setError(null);

    const deleteMethod = deleteFiles ? API.delete : API.forget;
    const errors: string[] = [];

    for (const torrent of torrents) {
      try {
        await deleteMethod(torrent.id);
      } catch (e) {
        const name = torrent.name || `id=${torrent.id}`;
        errors.push(`${name}: ${e}`);
      }
    }

    if (errors.length > 0) {
      setError({
        text: `Failed to delete ${errors.length} torrent${errors.length > 1 ? "s" : ""}`,
        details: { text: errors.join("\n") },
      });
      setDeleting(false);
    } else {
      clearSelection();
      refreshTorrents();
      close();
    }
  };

  const isBulk = torrents.length > 1;
  const title = isBulk
    ? `Delete ${torrents.length} torrents`
    : "Delete torrent";

  return (
    <Modal isOpen={show} onClose={onHide} title={title}>
      <ModalBody>
        <p className="text-gray-700 dark:text-slate-300 mb-3">
          {isBulk
            ? "Are you sure you want to delete the following torrents?"
            : "Are you sure you want to delete this torrent?"}
        </p>

        <div
          className={`rounded-md bg-gray-50 dark:bg-slate-700/50 p-3 ${
            isBulk ? "max-h-48 overflow-y-auto" : ""
          }`}
        >
          <ul className="space-y-1">
            {torrents.map((torrent) => (
              <li
                key={torrent.id}
                className="text-gray-800 dark:text-slate-200 truncate"
                title={torrent.name ?? undefined}
              >
                <span className="font-medium">
                  {torrent.name || `Torrent #${torrent.id}`}
                </span>
              </li>
            ))}
          </ul>
        </div>

        <div className="mt-4 flex items-center">
          <input
            type="checkbox"
            id="deleteFiles"
            className="form-checkbox h-4 w-4 text-blue-500 rounded border-gray-300 dark:border-slate-600"
            onChange={() => setDeleteFiles(!deleteFiles)}
            checked={deleteFiles}
          />
          <label
            htmlFor="deleteFiles"
            className="ml-2 text-gray-700 dark:text-slate-300"
          >
            Also delete downloaded files
          </label>
        </div>

        {error && <ErrorComponent error={error} />}
      </ModalBody>

      <ModalFooter>
        {deleting && <Spinner />}
        <Button variant="cancel" onClick={close}>
          Cancel
        </Button>
        <Button variant="danger" onClick={deleteTorrents} disabled={deleting}>
          {isBulk ? `Delete ${torrents.length} Torrents` : "Delete Torrent"}
        </Button>
      </ModalFooter>
    </Modal>
  );
};