UI link to video files

This commit is contained in:
Igor Katson 2024-04-24 22:55:56 +01:00
parent f0788f2c4a
commit 94589a21fe
11 changed files with 64 additions and 19 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,14 +4,14 @@
"src": "assets/logo.svg"
},
"index.css": {
"file": "assets/index-144bafe5.css",
"file": "assets/index-74bd798d.css",
"src": "index.css"
},
"index.html": {
"css": [
"assets/index-144bafe5.css"
"assets/index-74bd798d.css"
],
"file": "assets/index-838acac9.js",
"file": "assets/index-80d6e7c6.js",
"isEntry": true,
"src": "index.html"
}

View file

@ -166,6 +166,7 @@ export interface RqbitAPI {
listTorrents: () => Promise<ListTorrentsResponse>;
getTorrentDetails: (index: number) => Promise<TorrentDetails>;
getTorrentStats: (index: number) => Promise<TorrentStats>;
getTorrentStreamUrl: (index: number, file_id: number) => string | null;
uploadTorrent: (
data: string | File,
opts?: AddTorrentOptions,

View file

@ -1,4 +1,4 @@
import { useMemo, useState } from "react";
import { useContext, useMemo, useState } from "react";
import { TorrentDetails, TorrentStats } from "../api-types";
import { FormCheckbox } from "./forms/FormCheckbox";
import { CiSquarePlus, CiSquareMinus } from "react-icons/ci";
@ -6,6 +6,7 @@ import { IconButton } from "./buttons/IconButton";
import { formatBytes } from "../helper/formatBytes";
import { ProgressBar } from "./ProgressBar";
import sortBy from "lodash.sortby";
import { APIContext } from "../context";
type TorrentFileForCheckbox = {
id: number;
@ -86,6 +87,7 @@ const newFileTree = (
};
const FileTreeComponent: React.FC<{
torrentId?: number;
tree: FileTree;
torrentDetails: TorrentDetails;
torrentStats: TorrentStats | null;
@ -94,7 +96,9 @@ const FileTreeComponent: React.FC<{
initialExpanded: boolean;
showProgressBar?: boolean;
disabled?: boolean;
allowStream?: boolean;
}> = ({
torrentId,
tree,
selectedFiles,
setSelectedFiles,
@ -103,7 +107,9 @@ const FileTreeComponent: React.FC<{
torrentStats,
showProgressBar,
disabled,
allowStream,
}) => {
const API = useContext(APIContext);
let [expanded, setExpanded] = useState(initialExpanded);
let children = useMemo(() => {
let getAllChildren = (tree: FileTree): number[] => {
@ -149,6 +155,16 @@ const FileTreeComponent: React.FC<{
.reduce((a, b) => a + b, 0);
};
const fileLink = (file: TorrentFileForCheckbox) => {
if (
allowStream &&
torrentId != null &&
/\.(mp4|mkv|avi)$/.test(file.filename)
) {
return API.getTorrentStreamUrl(torrentId, file.id);
}
};
return (
<>
<div className="flex items-center">
@ -170,6 +186,7 @@ const FileTreeComponent: React.FC<{
<div className="pl-5" hidden={!expanded}>
{tree.dirs.map((dir) => (
<FileTreeComponent
torrentId={torrentId}
torrentDetails={torrentDetails}
torrentStats={torrentStats}
key={dir.name}
@ -179,6 +196,7 @@ const FileTreeComponent: React.FC<{
initialExpanded={false}
showProgressBar={showProgressBar}
disabled={disabled}
allowStream={allowStream}
/>
))}
<div className="pl-1">
@ -197,6 +215,7 @@ const FileTreeComponent: React.FC<{
name={`file-${file.id}`}
disabled={disabled}
onChange={() => handleToggleFile(file.id)}
labelLink={fileLink(file)}
></FormCheckbox>
{showProgressBar && (
<ProgressBar
@ -213,19 +232,23 @@ const FileTreeComponent: React.FC<{
};
export const FileListInput: React.FC<{
torrentId?: number;
torrentDetails: TorrentDetails;
torrentStats: TorrentStats | null;
selectedFiles: Set<number>;
setSelectedFiles: (_: Set<number>) => void;
showProgressBar?: boolean;
disabled?: boolean;
allowStream?: boolean;
}> = ({
torrentId,
torrentDetails,
selectedFiles,
setSelectedFiles,
torrentStats,
showProgressBar,
disabled,
allowStream,
}) => {
let fileTree = useMemo(
() => newFileTree(torrentDetails, torrentStats),
@ -234,6 +257,7 @@ export const FileListInput: React.FC<{
return (
<FileTreeComponent
torrentId={torrentId}
torrentDetails={torrentDetails}
torrentStats={torrentStats}
tree={fileTree}
@ -242,6 +266,7 @@ export const FileListInput: React.FC<{
initialExpanded={true}
showProgressBar={showProgressBar}
disabled={disabled}
allowStream={allowStream}
/>
);
};

View file

@ -170,11 +170,13 @@ export const TorrentRow: React.FC<{
{detailsResponse && extendedView && (
<div className="">
<FileListInput
torrentId={id}
torrentDetails={detailsResponse}
torrentStats={statsResponse}
selectedFiles={selectedFiles}
setSelectedFiles={updateSelectedFiles}
disabled={savingSelectedFiles}
allowStream
showProgressBar
/>
</div>

View file

@ -10,6 +10,7 @@ export const FormCheckbox: React.FC<{
onChange?: ChangeEventHandler<HTMLInputElement>;
children?: React.ReactNode;
classNames?: string;
labelLink?: string | null;
}> = ({
checked,
name,
@ -19,6 +20,7 @@ export const FormCheckbox: React.FC<{
help,
inputType,
children,
labelLink,
}) => {
return (
<div className={`flex gap-3 items-start`}>
@ -34,7 +36,17 @@ export const FormCheckbox: React.FC<{
/>
</div>
<div className="text-sm flex flex-col gap-1">
<label htmlFor={name}>{label}</label>
{labelLink ? (
<a
href={labelLink}
className="text-blue-600 dark:text-blue-500 hover:underline"
>
{label}
</a>
) : (
<label htmlFor={name}>{label}</label>
)}
{help && (
<div className="text-xs text-slate-500 dark:text-slate-300 mb-3">
{help}

View file

@ -29,8 +29,8 @@ export const APIContext = createContext<RqbitAPI>({
delete: () => {
throw new Error("Function not implemented.");
},
getStreamLogsUrl: () => {
return null;
getTorrentStreamUrl: () => {
throw new Error("Function not implemented.");
},
});
export const RefreshTorrentStatsContext = createContext({ refresh: () => {} });

View file

@ -140,4 +140,7 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
const r = await makeRequest("GET", "/");
return r.version;
},
getTorrentStreamUrl: (index: number, file_id: number) => {
return apiUrl + `/torrents/${index}/stream/${file_id}`;
},
};