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
This commit is contained in:
parent
d7380217f6
commit
5eb01ac226
31 changed files with 865 additions and 512 deletions
|
|
@ -1,15 +1,18 @@
|
|||
import { useMemo, useState } from "react";
|
||||
import { TorrentDetails } from "../api-types";
|
||||
import { TorrentDetails, TorrentStats } from "../api-types";
|
||||
import { FormCheckbox } from "./forms/FormCheckbox";
|
||||
import { CiSquarePlus, CiSquareMinus } from "react-icons/ci";
|
||||
import { IconButton } from "./buttons/IconButton";
|
||||
import { formatBytes } from "../helper/formatBytes";
|
||||
import { ProgressBar } from "./ProgressBar";
|
||||
import sortBy from "lodash.sortby";
|
||||
|
||||
type TorrentFileForCheckbox = {
|
||||
id: number;
|
||||
filename: string;
|
||||
pathComponents: string[];
|
||||
length: number;
|
||||
have_bytes: number;
|
||||
};
|
||||
|
||||
type FileTree = {
|
||||
|
|
@ -19,7 +22,10 @@ type FileTree = {
|
|||
files: TorrentFileForCheckbox[];
|
||||
};
|
||||
|
||||
const newFileTree = (torrentDetails: TorrentDetails): FileTree => {
|
||||
const newFileTree = (
|
||||
torrentDetails: TorrentDetails,
|
||||
stats: TorrentStats | null,
|
||||
): FileTree => {
|
||||
const newFileTreeInner = (
|
||||
name: string,
|
||||
id: string,
|
||||
|
|
@ -43,8 +49,15 @@ const newFileTree = (torrentDetails: TorrentDetails): FileTree => {
|
|||
getGroup(file.pathComponents[0]).push(file);
|
||||
});
|
||||
|
||||
directFiles = sortBy(directFiles, (f) => f.filename);
|
||||
|
||||
let sortedGroupsByName = sortBy(
|
||||
Object.entries(groupsByName),
|
||||
([k, _]) => k,
|
||||
);
|
||||
|
||||
let childId = 0;
|
||||
for (const [key, value] of Object.entries(groupsByName)) {
|
||||
for (const [key, value] of sortedGroupsByName) {
|
||||
groups.push(newFileTreeInner(key, id + "." + childId, value, depth + 1));
|
||||
childId += 1;
|
||||
}
|
||||
|
|
@ -65,6 +78,7 @@ const newFileTree = (torrentDetails: TorrentDetails): FileTree => {
|
|||
filename: file.components[file.components.length - 1],
|
||||
pathComponents: file.components,
|
||||
length: file.length,
|
||||
have_bytes: stats ? stats.file_progress[id] ?? 0 : 0,
|
||||
};
|
||||
}),
|
||||
0,
|
||||
|
|
@ -74,15 +88,21 @@ const newFileTree = (torrentDetails: TorrentDetails): FileTree => {
|
|||
const FileTreeComponent: React.FC<{
|
||||
tree: FileTree;
|
||||
torrentDetails: TorrentDetails;
|
||||
torrentStats: TorrentStats | null;
|
||||
selectedFiles: Set<number>;
|
||||
setSelectedFiles: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||
setSelectedFiles: (_: Set<number>) => void;
|
||||
initialExpanded: boolean;
|
||||
showProgressBar?: boolean;
|
||||
disabled?: boolean;
|
||||
}> = ({
|
||||
tree,
|
||||
selectedFiles,
|
||||
setSelectedFiles,
|
||||
initialExpanded,
|
||||
torrentDetails,
|
||||
torrentStats,
|
||||
showProgressBar,
|
||||
disabled,
|
||||
}) => {
|
||||
let [expanded, setExpanded] = useState(initialExpanded);
|
||||
let children = useMemo(() => {
|
||||
|
|
@ -151,6 +171,7 @@ const FileTreeComponent: React.FC<{
|
|||
{tree.dirs.map((dir) => (
|
||||
<FileTreeComponent
|
||||
torrentDetails={torrentDetails}
|
||||
torrentStats={torrentStats}
|
||||
key={dir.name}
|
||||
tree={dir}
|
||||
selectedFiles={selectedFiles}
|
||||
|
|
@ -160,13 +181,28 @@ const FileTreeComponent: React.FC<{
|
|||
))}
|
||||
<div className="pl-1">
|
||||
{tree.files.map((file) => (
|
||||
<FormCheckbox
|
||||
checked={selectedFiles.has(file.id)}
|
||||
<div
|
||||
key={file.id}
|
||||
label={`${file.filename} (${formatBytes(file.length)})`}
|
||||
name={`file-${file.id}`}
|
||||
onChange={() => handleToggleFile(file.id)}
|
||||
></FormCheckbox>
|
||||
className={`${
|
||||
showProgressBar
|
||||
? "grid grid-cols-1 gap-1 items-start lg:grid-cols-2 mb-2 lg:mb-0"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<FormCheckbox
|
||||
checked={selectedFiles.has(file.id)}
|
||||
label={`${file.filename} (${formatBytes(file.length)})`}
|
||||
name={`file-${file.id}`}
|
||||
disabled={disabled}
|
||||
onChange={() => handleToggleFile(file.id)}
|
||||
></FormCheckbox>
|
||||
{showProgressBar && (
|
||||
<ProgressBar
|
||||
now={(file.have_bytes / file.length) * 100}
|
||||
variant={file.have_bytes == file.length ? "success" : "info"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -176,20 +212,34 @@ const FileTreeComponent: React.FC<{
|
|||
|
||||
export const FileListInput: React.FC<{
|
||||
torrentDetails: TorrentDetails;
|
||||
torrentStats: TorrentStats | null;
|
||||
selectedFiles: Set<number>;
|
||||
setSelectedFiles: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||
}> = ({ torrentDetails, selectedFiles, setSelectedFiles }) => {
|
||||
let fileTree = useMemo(() => newFileTree(torrentDetails), [torrentDetails]);
|
||||
setSelectedFiles: (_: Set<number>) => void;
|
||||
showProgressBar?: boolean;
|
||||
disabled?: boolean;
|
||||
}> = ({
|
||||
torrentDetails,
|
||||
selectedFiles,
|
||||
setSelectedFiles,
|
||||
torrentStats,
|
||||
showProgressBar,
|
||||
disabled,
|
||||
}) => {
|
||||
let fileTree = useMemo(
|
||||
() => newFileTree(torrentDetails, torrentStats),
|
||||
[torrentDetails, torrentStats],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FileTreeComponent
|
||||
torrentDetails={torrentDetails}
|
||||
tree={fileTree}
|
||||
selectedFiles={selectedFiles}
|
||||
setSelectedFiles={setSelectedFiles}
|
||||
initialExpanded={true}
|
||||
/>
|
||||
</>
|
||||
<FileTreeComponent
|
||||
torrentDetails={torrentDetails}
|
||||
torrentStats={torrentStats}
|
||||
tree={fileTree}
|
||||
selectedFiles={selectedFiles}
|
||||
setSelectedFiles={setSelectedFiles}
|
||||
initialExpanded={true}
|
||||
showProgressBar={showProgressBar}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue