Possibility to change selected files after the fact
This commit is contained in:
parent
feae1789a9
commit
86d9d2c5f0
11 changed files with 186 additions and 52 deletions
20
crates/librqbit/webui/dist/assets/index.js
vendored
20
crates/librqbit/webui/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
2
crates/librqbit/webui/dist/manifest.json
vendored
2
crates/librqbit/webui/dist/manifest.json
vendored
|
|
@ -11,7 +11,7 @@
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-d46108e9.css"
|
"assets/index-d46108e9.css"
|
||||||
],
|
],
|
||||||
"file": "assets/index-1ee4d2cc.js",
|
"file": "assets/index-87e26627.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "index.html"
|
"src": "index.html"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,10 +167,11 @@ export interface RqbitAPI {
|
||||||
getTorrentStats: (index: number) => Promise<TorrentStats>;
|
getTorrentStats: (index: number) => Promise<TorrentStats>;
|
||||||
uploadTorrent: (
|
uploadTorrent: (
|
||||||
data: string | File,
|
data: string | File,
|
||||||
opts?: AddTorrentOptions
|
opts?: AddTorrentOptions,
|
||||||
) => Promise<AddTorrentResponse>;
|
) => Promise<AddTorrentResponse>;
|
||||||
|
|
||||||
pause: (index: number) => Promise<void>;
|
pause: (index: number) => Promise<void>;
|
||||||
|
updateOnlyFiles: (index: number, files: number[]) => Promise<void>;
|
||||||
start: (index: number) => Promise<void>;
|
start: (index: number) => Promise<void>;
|
||||||
forget: (index: number) => Promise<void>;
|
forget: (index: number) => Promise<void>;
|
||||||
delete: (index: number) => Promise<void>;
|
delete: (index: number) => Promise<void>;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { AddTorrentResponse, TorrentFile } from "../api-types";
|
import { TorrentDetails } from "../api-types";
|
||||||
import { FormCheckbox } from "./forms/FormCheckbox";
|
import { FormCheckbox } from "./forms/FormCheckbox";
|
||||||
import { CiSquarePlus, CiSquareMinus } from "react-icons/ci";
|
import { CiSquarePlus, CiSquareMinus } from "react-icons/ci";
|
||||||
import { IconButton } from "./buttons/IconButton";
|
import { IconButton } from "./buttons/IconButton";
|
||||||
|
|
@ -19,12 +19,12 @@ type FileTree = {
|
||||||
files: TorrentFileForCheckbox[];
|
files: TorrentFileForCheckbox[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const newFileTree = (listTorrentResponse: AddTorrentResponse): FileTree => {
|
const newFileTree = (torrentDetails: TorrentDetails): FileTree => {
|
||||||
const newFileTreeInner = (
|
const newFileTreeInner = (
|
||||||
name: string,
|
name: string,
|
||||||
id: string,
|
id: string,
|
||||||
files: TorrentFileForCheckbox[],
|
files: TorrentFileForCheckbox[],
|
||||||
depth: number
|
depth: number,
|
||||||
): FileTree => {
|
): FileTree => {
|
||||||
let directFiles: TorrentFileForCheckbox[] = [];
|
let directFiles: TorrentFileForCheckbox[] = [];
|
||||||
let groups: FileTree[] = [];
|
let groups: FileTree[] = [];
|
||||||
|
|
@ -59,7 +59,7 @@ const newFileTree = (listTorrentResponse: AddTorrentResponse): FileTree => {
|
||||||
return newFileTreeInner(
|
return newFileTreeInner(
|
||||||
"",
|
"",
|
||||||
"filetree-root",
|
"filetree-root",
|
||||||
listTorrentResponse.details.files.map((file, id) => {
|
torrentDetails.files.map((file, id) => {
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
filename: file.components[file.components.length - 1],
|
filename: file.components[file.components.length - 1],
|
||||||
|
|
@ -67,13 +67,13 @@ const newFileTree = (listTorrentResponse: AddTorrentResponse): FileTree => {
|
||||||
length: file.length,
|
length: file.length,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileTreeComponent: React.FC<{
|
const FileTreeComponent: React.FC<{
|
||||||
tree: FileTree;
|
tree: FileTree;
|
||||||
listTorrentResponse: AddTorrentResponse;
|
torrentDetails: TorrentDetails;
|
||||||
selectedFiles: Set<number>;
|
selectedFiles: Set<number>;
|
||||||
setSelectedFiles: React.Dispatch<React.SetStateAction<Set<number>>>;
|
setSelectedFiles: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||||
initialExpanded: boolean;
|
initialExpanded: boolean;
|
||||||
|
|
@ -82,7 +82,7 @@ const FileTreeComponent: React.FC<{
|
||||||
selectedFiles,
|
selectedFiles,
|
||||||
setSelectedFiles,
|
setSelectedFiles,
|
||||||
initialExpanded,
|
initialExpanded,
|
||||||
listTorrentResponse,
|
torrentDetails,
|
||||||
}) => {
|
}) => {
|
||||||
let [expanded, setExpanded] = useState(initialExpanded);
|
let [expanded, setExpanded] = useState(initialExpanded);
|
||||||
let children = useMemo(() => {
|
let children = useMemo(() => {
|
||||||
|
|
@ -125,7 +125,7 @@ const FileTreeComponent: React.FC<{
|
||||||
const getTotalSelectedBytes = () => {
|
const getTotalSelectedBytes = () => {
|
||||||
return children
|
return children
|
||||||
.filter((c) => selectedFiles.has(c))
|
.filter((c) => selectedFiles.has(c))
|
||||||
.map((c) => listTorrentResponse.details.files[c].length)
|
.map((c) => torrentDetails.files[c].length)
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ const FileTreeComponent: React.FC<{
|
||||||
label={`${
|
label={`${
|
||||||
tree.name ? tree.name + ", " : ""
|
tree.name ? tree.name + ", " : ""
|
||||||
} ${getTotalSelectedFiles()} files, ${formatBytes(
|
} ${getTotalSelectedFiles()} files, ${formatBytes(
|
||||||
getTotalSelectedBytes()
|
getTotalSelectedBytes(),
|
||||||
)}`}
|
)}`}
|
||||||
name={tree.id}
|
name={tree.id}
|
||||||
onChange={handleToggleTree}
|
onChange={handleToggleTree}
|
||||||
|
|
@ -150,7 +150,7 @@ const FileTreeComponent: React.FC<{
|
||||||
<div className="pl-5" hidden={!expanded}>
|
<div className="pl-5" hidden={!expanded}>
|
||||||
{tree.dirs.map((dir) => (
|
{tree.dirs.map((dir) => (
|
||||||
<FileTreeComponent
|
<FileTreeComponent
|
||||||
listTorrentResponse={listTorrentResponse}
|
torrentDetails={torrentDetails}
|
||||||
key={dir.name}
|
key={dir.name}
|
||||||
tree={dir}
|
tree={dir}
|
||||||
selectedFiles={selectedFiles}
|
selectedFiles={selectedFiles}
|
||||||
|
|
@ -175,19 +175,16 @@ const FileTreeComponent: React.FC<{
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FileListInput: React.FC<{
|
export const FileListInput: React.FC<{
|
||||||
listTorrentResponse: AddTorrentResponse;
|
torrentDetails: TorrentDetails;
|
||||||
selectedFiles: Set<number>;
|
selectedFiles: Set<number>;
|
||||||
setSelectedFiles: React.Dispatch<React.SetStateAction<Set<number>>>;
|
setSelectedFiles: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||||
}> = ({ listTorrentResponse, selectedFiles, setSelectedFiles }) => {
|
}> = ({ torrentDetails, selectedFiles, setSelectedFiles }) => {
|
||||||
let fileTree = useMemo(
|
let fileTree = useMemo(() => newFileTree(torrentDetails), [torrentDetails]);
|
||||||
() => newFileTree(listTorrentResponse),
|
|
||||||
[listTorrentResponse]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FileTreeComponent
|
<FileTreeComponent
|
||||||
listTorrentResponse={listTorrentResponse}
|
torrentDetails={torrentDetails}
|
||||||
tree={fileTree}
|
tree={fileTree}
|
||||||
selectedFiles={selectedFiles}
|
selectedFiles={selectedFiles}
|
||||||
setSelectedFiles={setSelectedFiles}
|
setSelectedFiles={setSelectedFiles}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export const Torrent: React.FC<{
|
||||||
const [detailsResponse, updateDetailsResponse] =
|
const [detailsResponse, updateDetailsResponse] =
|
||||||
useState<TorrentDetails | null>(null);
|
useState<TorrentDetails | null>(null);
|
||||||
const [statsResponse, updateStatsResponse] = useState<TorrentStats | null>(
|
const [statsResponse, updateStatsResponse] = useState<TorrentStats | null>(
|
||||||
null
|
null,
|
||||||
);
|
);
|
||||||
const [forceStatsRefresh, setForceStatsRefresh] = useState(0);
|
const [forceStatsRefresh, setForceStatsRefresh] = useState(0);
|
||||||
const API = useContext(APIContext);
|
const API = useContext(APIContext);
|
||||||
|
|
@ -27,14 +27,12 @@ export const Torrent: React.FC<{
|
||||||
setForceStatsRefresh(forceStatsRefresh + 1);
|
setForceStatsRefresh(forceStatsRefresh + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update details once.
|
// Update details once then when asked for.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (detailsResponse === null) {
|
return loopUntilSuccess(async () => {
|
||||||
return loopUntilSuccess(async () => {
|
await API.getTorrentDetails(torrent.id).then(updateDetailsResponse);
|
||||||
await API.getTorrentDetails(torrent.id).then(updateDetailsResponse);
|
}, 1000);
|
||||||
}, 1000);
|
}, [forceStatsRefresh]);
|
||||||
}
|
|
||||||
}, [detailsResponse]);
|
|
||||||
|
|
||||||
// Update stats once then forever.
|
// Update stats once then forever.
|
||||||
useEffect(
|
useEffect(
|
||||||
|
|
@ -61,10 +59,10 @@ export const Torrent: React.FC<{
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
return errorInterval;
|
return errorInterval;
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}, 0),
|
}, 0),
|
||||||
[forceStatsRefresh]
|
[forceStatsRefresh],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,11 @@ export const TorrentRow: React.FC<{
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
{statsResponse && (
|
{statsResponse && (
|
||||||
<div className="">
|
<div className="">
|
||||||
<TorrentActions id={id} statsResponse={statsResponse} />
|
<TorrentActions
|
||||||
|
id={id}
|
||||||
|
detailsResponse={detailsResponse}
|
||||||
|
statsResponse={statsResponse}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,28 @@
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { TorrentStats } from "../../api-types";
|
import { TorrentDetails, TorrentStats } from "../../api-types";
|
||||||
import { APIContext, RefreshTorrentStatsContext } from "../../context";
|
import { APIContext, RefreshTorrentStatsContext } from "../../context";
|
||||||
import { IconButton } from "./IconButton";
|
import { IconButton } from "./IconButton";
|
||||||
import { DeleteTorrentModal } from "../modal/DeleteTorrentModal";
|
import { DeleteTorrentModal } from "../modal/DeleteTorrentModal";
|
||||||
import { FaPause, FaPlay, FaTrash } from "react-icons/fa";
|
import { TorrentSettingsModal } from "../modal/TorrentSettingsModal";
|
||||||
|
import { FaCog, FaPause, FaPlay, FaTrash } from "react-icons/fa";
|
||||||
import { useErrorStore } from "../../stores/errorStore";
|
import { useErrorStore } from "../../stores/errorStore";
|
||||||
|
|
||||||
export const TorrentActions: React.FC<{
|
export const TorrentActions: React.FC<{
|
||||||
id: number;
|
id: number;
|
||||||
|
detailsResponse: TorrentDetails | null;
|
||||||
statsResponse: TorrentStats;
|
statsResponse: TorrentStats;
|
||||||
}> = ({ id, statsResponse }) => {
|
}> = ({ id, detailsResponse, statsResponse }) => {
|
||||||
let state = statsResponse.state;
|
let state = statsResponse.state;
|
||||||
|
|
||||||
let [disabled, setDisabled] = useState<boolean>(false);
|
let [disabled, setDisabled] = useState<boolean>(false);
|
||||||
let [deleting, setDeleting] = useState<boolean>(false);
|
let [deleting, setDeleting] = useState<boolean>(false);
|
||||||
|
let [configuring, setConfiguring] = useState<boolean>(false);
|
||||||
|
|
||||||
let refreshCtx = useContext(RefreshTorrentStatsContext);
|
let refreshCtx = useContext(RefreshTorrentStatsContext);
|
||||||
|
|
||||||
const canPause = state == "live";
|
const canPause = state == "live";
|
||||||
const canUnpause = state == "paused" || state == "error";
|
const canUnpause = state == "paused" || state == "error";
|
||||||
|
const canConfigure = state == "paused" || state == "live";
|
||||||
|
|
||||||
const setCloseableError = useErrorStore((state) => state.setCloseableError);
|
const setCloseableError = useErrorStore((state) => state.setCloseableError);
|
||||||
|
|
||||||
|
|
@ -36,7 +40,7 @@ export const TorrentActions: React.FC<{
|
||||||
text: `Error starting torrent id=${id}`,
|
text: `Error starting torrent id=${id}`,
|
||||||
details: e,
|
details: e,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.finally(() => setDisabled(false));
|
.finally(() => setDisabled(false));
|
||||||
};
|
};
|
||||||
|
|
@ -53,11 +57,15 @@ export const TorrentActions: React.FC<{
|
||||||
text: `Error pausing torrent id=${id}`,
|
text: `Error pausing torrent id=${id}`,
|
||||||
details: e,
|
details: e,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.finally(() => setDisabled(false));
|
.finally(() => setDisabled(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openConfigureModal = () => {
|
||||||
|
setConfiguring(true);
|
||||||
|
};
|
||||||
|
|
||||||
const startDeleting = () => {
|
const startDeleting = () => {
|
||||||
setDisabled(true);
|
setDisabled(true);
|
||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
|
|
@ -80,10 +88,23 @@ export const TorrentActions: React.FC<{
|
||||||
<FaPause className="hover:text-amber-500" />
|
<FaPause className="hover:text-amber-500" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
{canConfigure && (
|
||||||
|
<IconButton onClick={openConfigureModal} disabled={disabled}>
|
||||||
|
<FaCog className="hover:text-green-600" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
<IconButton onClick={startDeleting} disabled={disabled}>
|
<IconButton onClick={startDeleting} disabled={disabled}>
|
||||||
<FaTrash className="hover:text-red-500" />
|
<FaTrash className="hover:text-red-500" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<DeleteTorrentModal id={id} show={deleting} onHide={cancelDeleting} />
|
<DeleteTorrentModal id={id} show={deleting} onHide={cancelDeleting} />
|
||||||
|
{detailsResponse && configuring && (
|
||||||
|
<TorrentSettingsModal
|
||||||
|
id={id}
|
||||||
|
show={configuring}
|
||||||
|
details={detailsResponse}
|
||||||
|
onHide={() => setConfiguring(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export const FileSelectionModal = (props: {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedFiles(
|
setSelectedFiles(
|
||||||
new Set(listTorrentResponse?.details.files.map((_, i) => i))
|
new Set(listTorrentResponse?.details.files.map((_, i) => i)),
|
||||||
);
|
);
|
||||||
setOutputFolder(listTorrentResponse?.output_folder || "");
|
setOutputFolder(listTorrentResponse?.output_folder || "");
|
||||||
}, [listTorrentResponse]);
|
}, [listTorrentResponse]);
|
||||||
|
|
@ -79,7 +79,7 @@ export const FileSelectionModal = (props: {
|
||||||
},
|
},
|
||||||
(e) => {
|
(e) => {
|
||||||
setUploadError({ text: "Error starting torrent", details: e });
|
setUploadError({ text: "Error starting torrent", details: e });
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
.finally(() => setUploading(false));
|
.finally(() => setUploading(false));
|
||||||
};
|
};
|
||||||
|
|
@ -104,7 +104,7 @@ export const FileSelectionModal = (props: {
|
||||||
<FileListInput
|
<FileListInput
|
||||||
selectedFiles={selectedFiles}
|
selectedFiles={selectedFiles}
|
||||||
setSelectedFiles={setSelectedFiles}
|
setSelectedFiles={setSelectedFiles}
|
||||||
listTorrentResponse={listTorrentResponse}
|
torrentDetails={listTorrentResponse.details}
|
||||||
/>
|
/>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { useContext, useState } from "react";
|
||||||
|
import {
|
||||||
|
AddTorrentResponse,
|
||||||
|
ErrorDetails,
|
||||||
|
TorrentDetails,
|
||||||
|
} from "../../api-types";
|
||||||
|
import { FileListInput } from "../FileListInput";
|
||||||
|
import { Modal } from "./Modal";
|
||||||
|
import { ModalBody } from "./ModalBody";
|
||||||
|
import { ModalFooter } from "./ModalFooter";
|
||||||
|
import { Button } from "../buttons/Button";
|
||||||
|
import { Spinner } from "../Spinner";
|
||||||
|
import { APIContext, RefreshTorrentStatsContext } from "../../context";
|
||||||
|
import { ErrorComponent } from "../ErrorComponent";
|
||||||
|
import { ErrorWithLabel } from "../../stores/errorStore";
|
||||||
|
|
||||||
|
export const TorrentSettingsModal: React.FC<{
|
||||||
|
id: number;
|
||||||
|
show: boolean;
|
||||||
|
onHide: () => void;
|
||||||
|
details: TorrentDetails;
|
||||||
|
}> = ({ id, show, onHide, details }) => {
|
||||||
|
let initialSelectedFiles = new Set<number>();
|
||||||
|
|
||||||
|
let refreshCtx = useContext(RefreshTorrentStatsContext);
|
||||||
|
|
||||||
|
details.files.forEach((f, i) => {
|
||||||
|
if (f.included) {
|
||||||
|
initialSelectedFiles.add(i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const API = useContext(APIContext);
|
||||||
|
|
||||||
|
const [selectedFiles, setSelectedFiles] =
|
||||||
|
useState<Set<number>>(initialSelectedFiles);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<ErrorWithLabel | null>(null);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
setSelectedFiles(initialSelectedFiles);
|
||||||
|
onHide();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
setSaving(true);
|
||||||
|
API.updateOnlyFiles(id, Array.from(selectedFiles)).then(
|
||||||
|
() => {
|
||||||
|
setSaving(false);
|
||||||
|
refreshCtx.refresh();
|
||||||
|
close();
|
||||||
|
setError(null);
|
||||||
|
},
|
||||||
|
(e) => {
|
||||||
|
setSaving(false);
|
||||||
|
setError({
|
||||||
|
text: "Error configuring torrent",
|
||||||
|
details: e as ErrorDetails,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={show} onClose={close} title="Configure torrent">
|
||||||
|
<ModalBody>
|
||||||
|
<ErrorComponent error={error}></ErrorComponent>
|
||||||
|
<FileListInput
|
||||||
|
torrentDetails={details}
|
||||||
|
selectedFiles={selectedFiles}
|
||||||
|
setSelectedFiles={setSelectedFiles}
|
||||||
|
/>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
{saving && <Spinner />}
|
||||||
|
<Button onClick={close} variant="cancel">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSave}
|
||||||
|
variant="primary"
|
||||||
|
disabled={saving || selectedFiles.size == 0}
|
||||||
|
>
|
||||||
|
OK
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { createContext } from "react";
|
import { createContext } from "react";
|
||||||
import { RqbitAPI } from "./api-types";
|
import { RqbitAPI } from "./api-types";
|
||||||
import { ContextType } from "./rqbit-web";
|
|
||||||
|
|
||||||
export const APIContext = createContext<RqbitAPI>({
|
export const APIContext = createContext<RqbitAPI>({
|
||||||
listTorrents: () => {
|
listTorrents: () => {
|
||||||
|
|
@ -15,6 +14,9 @@ export const APIContext = createContext<RqbitAPI>({
|
||||||
uploadTorrent: () => {
|
uploadTorrent: () => {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
|
updateOnlyFiles: () => {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
},
|
||||||
pause: () => {
|
pause: () => {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,17 +16,26 @@ const apiUrl =
|
||||||
const makeRequest = async (
|
const makeRequest = async (
|
||||||
method: string,
|
method: string,
|
||||||
path: string,
|
path: string,
|
||||||
data?: any
|
data?: any,
|
||||||
|
isJson?: boolean,
|
||||||
): Promise<any> => {
|
): Promise<any> => {
|
||||||
console.log(method, path);
|
console.log(method, path);
|
||||||
const url = apiUrl + path;
|
const url = apiUrl + path;
|
||||||
const options: RequestInit = {
|
let options: RequestInit = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
},
|
||||||
body: data,
|
|
||||||
};
|
};
|
||||||
|
if (isJson) {
|
||||||
|
options.headers = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
options.body = JSON.stringify(data);
|
||||||
|
} else {
|
||||||
|
options.body = data;
|
||||||
|
}
|
||||||
|
|
||||||
let error: ErrorDetails = {
|
let error: ErrorDetails = {
|
||||||
method: method,
|
method: method,
|
||||||
|
|
@ -100,6 +109,18 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
|
||||||
return makeRequest("POST", url, data);
|
return makeRequest("POST", url, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateOnlyFiles: (index: number, files: number[]): Promise<void> => {
|
||||||
|
let url = `/torrents/${index}/update_only_files`;
|
||||||
|
return makeRequest(
|
||||||
|
"POST",
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
only_files: files,
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
pause: (index: number): Promise<void> => {
|
pause: (index: number): Promise<void> => {
|
||||||
return makeRequest("POST", `/torrents/${index}/pause`);
|
return makeRequest("POST", `/torrents/${index}/pause`);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue