rqbit/crates/librqbit/webui/src/components/modal/FileSelectionModal.tsx
Igor Katson 5eb01ac226
Ability to change the list of files at any time, including through UI (#115)
* Now can update the list of files without pausing/unpausing

* Shrink a few functions

* Reopen write when updating files

* Todos

* opened_file abstraction

* iter_pieces_within iterator

* Simplify iter_pieces_within

* Simplify iter_pieces_within

* Add "iter_file_details"

* temporarily broken: readonly by default

* Live torrent - reopen files

* Reopen files after changing the list

* Now reopening files read only when they are completed

* Fix a bug in opened_file.rs

* update todos

* update help

* Reconnect all peers that are idling

* Add a couple fields to OpenedFile

* Add a couple fields to OpenedFile

* Small cleanups - use the new iterator where possible

* size_of_piece_in_file function

* Updating have

* Include file progress

* Almost nothing

* ugly progress bars

* bad UI, saving

* its not so bad

* Works now

* update progress bar a bit

* Reopen read-only on pause

* Zero bytes isnt too bad! Doesnt break anything

* fix per file progress bars

* progress bar not as ugly anymore?

* ui tweaks

* fix a react bug

* TODO.md update

* Fix js + TODOs

* Compute per-file progress on init

* Fix stats updating live

* Nothing

* Nothing

* cleanup ui a bit

* Nothing

* Final fixes

* Trying to fix rust 1.73

* Sorting filenames

* remove unnecessary indentation

* Remove unnecessary comment
2024-04-06 09:20:03 +01:00

146 lines
4.4 KiB
TypeScript

import { useContext, useEffect, useState } from "react";
import { AddTorrentResponse, AddTorrentOptions } from "../../api-types";
import { APIContext } from "../../context";
import { ErrorComponent } from "../ErrorComponent";
import { ErrorWithLabel } from "../../rqbit-web";
import { Spinner } from "../Spinner";
import { Modal } from "./Modal";
import { ModalBody } from "./ModalBody";
import { ModalFooter } from "./ModalFooter";
import { Button } from "../buttons/Button";
import { Fieldset } from "../forms/Fieldset";
import { FormInput } from "../forms/FormInput";
import { Form } from "../forms/Form";
import { FileListInput } from "../FileListInput";
import { useTorrentStore } from "../../stores/torrentStore";
export const FileSelectionModal = (props: {
onHide: () => void;
listTorrentResponse: AddTorrentResponse | null;
listTorrentError: ErrorWithLabel | null;
listTorrentLoading: boolean;
data: string | File;
}) => {
let {
onHide,
listTorrentResponse,
listTorrentError,
listTorrentLoading,
data,
} = props;
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
const [uploading, setUploading] = useState(false);
const [uploadError, setUploadError] = useState<ErrorWithLabel | null>(null);
const [unpopularTorrent, setUnpopularTorrent] = useState(false);
const [outputFolder, setOutputFolder] = useState<string>("");
const refreshTorrents = useTorrentStore((state) => state.refreshTorrents);
const API = useContext(APIContext);
useEffect(() => {
setSelectedFiles(
new Set(listTorrentResponse?.details.files.map((_, i) => i)),
);
setOutputFolder(listTorrentResponse?.output_folder || "");
}, [listTorrentResponse]);
const clear = () => {
onHide();
setSelectedFiles(new Set());
setUploadError(null);
setUploading(false);
};
const handleUpload = async () => {
if (!listTorrentResponse) {
return;
}
setUploading(true);
let initialPeers = listTorrentResponse.seen_peers
? listTorrentResponse.seen_peers.slice(0, 32)
: null;
let opts: AddTorrentOptions = {
overwrite: true,
only_files: Array.from(selectedFiles),
initial_peers: initialPeers,
output_folder: outputFolder,
};
if (unpopularTorrent) {
opts.peer_opts = {
connect_timeout: 20,
read_write_timeout: 60,
};
}
API.uploadTorrent(data, opts)
.then(
() => {
onHide();
refreshTorrents();
},
(e) => {
setUploadError({ text: "Error starting torrent", details: e });
},
)
.finally(() => setUploading(false));
};
const getBody = () => {
if (listTorrentLoading) {
return <Spinner label="Loading torrent contents" />;
} else if (listTorrentError) {
return <ErrorComponent error={listTorrentError}></ErrorComponent>;
} else if (listTorrentResponse) {
return (
<Form>
<FormInput
label="Output folder"
name="output_folder"
inputType="text"
value={outputFolder}
onChange={(e) => setOutputFolder(e.target.value)}
/>
<Fieldset>
<FileListInput
selectedFiles={selectedFiles}
setSelectedFiles={setSelectedFiles}
torrentDetails={listTorrentResponse.details}
torrentStats={null}
/>
</Fieldset>
{/* <Fieldset label="Options">
<FormCheckbox
label="Increase timeouts"
checked={unpopularTorrent}
onChange={() => setUnpopularTorrent(!unpopularTorrent)}
help="This might be useful for unpopular torrents with few peers. It will slow down fast torrents though."
name="increase_timeouts"
/>
</Fieldset> */}
</Form>
);
}
};
return (
<Modal isOpen={true} onClose={clear} title="Add Torrent">
<ModalBody>
{getBody()}
<ErrorComponent error={uploadError} />
</ModalBody>
<ModalFooter>
{uploading && <Spinner />}
<Button onClick={clear} variant="cancel">
Cancel
</Button>
<Button
onClick={handleUpload}
variant="primary"
disabled={listTorrentLoading || uploading || selectedFiles.size == 0}
>
OK
</Button>
</ModalFooter>
</Modal>
);
};