UI link to video files
This commit is contained in:
parent
f0788f2c4a
commit
94589a21fe
11 changed files with 64 additions and 19 deletions
|
|
@ -176,7 +176,6 @@ impl HttpApi {
|
||||||
.and_then(|s| s.parse().ok());
|
.and_then(|s| s.parse().ok());
|
||||||
if let Some(offset) = offset {
|
if let Some(offset) = offset {
|
||||||
status = StatusCode::PARTIAL_CONTENT;
|
status = StatusCode::PARTIAL_CONTENT;
|
||||||
info!(offset, "range request offset");
|
|
||||||
stream
|
stream
|
||||||
.seek(SeekFrom::Start(offset))
|
.seek(SeekFrom::Start(offset))
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
2
crates/librqbit/webui/dist/assets/index.css
vendored
2
crates/librqbit/webui/dist/assets/index.css
vendored
File diff suppressed because one or more lines are too long
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
6
crates/librqbit/webui/dist/manifest.json
vendored
6
crates/librqbit/webui/dist/manifest.json
vendored
|
|
@ -4,14 +4,14 @@
|
||||||
"src": "assets/logo.svg"
|
"src": "assets/logo.svg"
|
||||||
},
|
},
|
||||||
"index.css": {
|
"index.css": {
|
||||||
"file": "assets/index-144bafe5.css",
|
"file": "assets/index-74bd798d.css",
|
||||||
"src": "index.css"
|
"src": "index.css"
|
||||||
},
|
},
|
||||||
"index.html": {
|
"index.html": {
|
||||||
"css": [
|
"css": [
|
||||||
"assets/index-144bafe5.css"
|
"assets/index-74bd798d.css"
|
||||||
],
|
],
|
||||||
"file": "assets/index-838acac9.js",
|
"file": "assets/index-80d6e7c6.js",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "index.html"
|
"src": "index.html"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ export interface RqbitAPI {
|
||||||
listTorrents: () => Promise<ListTorrentsResponse>;
|
listTorrents: () => Promise<ListTorrentsResponse>;
|
||||||
getTorrentDetails: (index: number) => Promise<TorrentDetails>;
|
getTorrentDetails: (index: number) => Promise<TorrentDetails>;
|
||||||
getTorrentStats: (index: number) => Promise<TorrentStats>;
|
getTorrentStats: (index: number) => Promise<TorrentStats>;
|
||||||
|
getTorrentStreamUrl: (index: number, file_id: number) => string | null;
|
||||||
uploadTorrent: (
|
uploadTorrent: (
|
||||||
data: string | File,
|
data: string | File,
|
||||||
opts?: AddTorrentOptions,
|
opts?: AddTorrentOptions,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useMemo, useState } from "react";
|
import { useContext, useMemo, useState } from "react";
|
||||||
import { TorrentDetails, TorrentStats } from "../api-types";
|
import { TorrentDetails, TorrentStats } 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";
|
||||||
|
|
@ -6,6 +6,7 @@ import { IconButton } from "./buttons/IconButton";
|
||||||
import { formatBytes } from "../helper/formatBytes";
|
import { formatBytes } from "../helper/formatBytes";
|
||||||
import { ProgressBar } from "./ProgressBar";
|
import { ProgressBar } from "./ProgressBar";
|
||||||
import sortBy from "lodash.sortby";
|
import sortBy from "lodash.sortby";
|
||||||
|
import { APIContext } from "../context";
|
||||||
|
|
||||||
type TorrentFileForCheckbox = {
|
type TorrentFileForCheckbox = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -86,6 +87,7 @@ const newFileTree = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileTreeComponent: React.FC<{
|
const FileTreeComponent: React.FC<{
|
||||||
|
torrentId?: number;
|
||||||
tree: FileTree;
|
tree: FileTree;
|
||||||
torrentDetails: TorrentDetails;
|
torrentDetails: TorrentDetails;
|
||||||
torrentStats: TorrentStats | null;
|
torrentStats: TorrentStats | null;
|
||||||
|
|
@ -94,7 +96,9 @@ const FileTreeComponent: React.FC<{
|
||||||
initialExpanded: boolean;
|
initialExpanded: boolean;
|
||||||
showProgressBar?: boolean;
|
showProgressBar?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
allowStream?: boolean;
|
||||||
}> = ({
|
}> = ({
|
||||||
|
torrentId,
|
||||||
tree,
|
tree,
|
||||||
selectedFiles,
|
selectedFiles,
|
||||||
setSelectedFiles,
|
setSelectedFiles,
|
||||||
|
|
@ -103,7 +107,9 @@ const FileTreeComponent: React.FC<{
|
||||||
torrentStats,
|
torrentStats,
|
||||||
showProgressBar,
|
showProgressBar,
|
||||||
disabled,
|
disabled,
|
||||||
|
allowStream,
|
||||||
}) => {
|
}) => {
|
||||||
|
const API = useContext(APIContext);
|
||||||
let [expanded, setExpanded] = useState(initialExpanded);
|
let [expanded, setExpanded] = useState(initialExpanded);
|
||||||
let children = useMemo(() => {
|
let children = useMemo(() => {
|
||||||
let getAllChildren = (tree: FileTree): number[] => {
|
let getAllChildren = (tree: FileTree): number[] => {
|
||||||
|
|
@ -149,6 +155,16 @@ const FileTreeComponent: React.FC<{
|
||||||
.reduce((a, b) => a + b, 0);
|
.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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
|
|
@ -170,6 +186,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
|
||||||
|
torrentId={torrentId}
|
||||||
torrentDetails={torrentDetails}
|
torrentDetails={torrentDetails}
|
||||||
torrentStats={torrentStats}
|
torrentStats={torrentStats}
|
||||||
key={dir.name}
|
key={dir.name}
|
||||||
|
|
@ -179,6 +196,7 @@ const FileTreeComponent: React.FC<{
|
||||||
initialExpanded={false}
|
initialExpanded={false}
|
||||||
showProgressBar={showProgressBar}
|
showProgressBar={showProgressBar}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
allowStream={allowStream}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className="pl-1">
|
<div className="pl-1">
|
||||||
|
|
@ -197,6 +215,7 @@ const FileTreeComponent: React.FC<{
|
||||||
name={`file-${file.id}`}
|
name={`file-${file.id}`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={() => handleToggleFile(file.id)}
|
onChange={() => handleToggleFile(file.id)}
|
||||||
|
labelLink={fileLink(file)}
|
||||||
></FormCheckbox>
|
></FormCheckbox>
|
||||||
{showProgressBar && (
|
{showProgressBar && (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
|
|
@ -213,19 +232,23 @@ const FileTreeComponent: React.FC<{
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FileListInput: React.FC<{
|
export const FileListInput: React.FC<{
|
||||||
|
torrentId?: number;
|
||||||
torrentDetails: TorrentDetails;
|
torrentDetails: TorrentDetails;
|
||||||
torrentStats: TorrentStats | null;
|
torrentStats: TorrentStats | null;
|
||||||
selectedFiles: Set<number>;
|
selectedFiles: Set<number>;
|
||||||
setSelectedFiles: (_: Set<number>) => void;
|
setSelectedFiles: (_: Set<number>) => void;
|
||||||
showProgressBar?: boolean;
|
showProgressBar?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
allowStream?: boolean;
|
||||||
}> = ({
|
}> = ({
|
||||||
|
torrentId,
|
||||||
torrentDetails,
|
torrentDetails,
|
||||||
selectedFiles,
|
selectedFiles,
|
||||||
setSelectedFiles,
|
setSelectedFiles,
|
||||||
torrentStats,
|
torrentStats,
|
||||||
showProgressBar,
|
showProgressBar,
|
||||||
disabled,
|
disabled,
|
||||||
|
allowStream,
|
||||||
}) => {
|
}) => {
|
||||||
let fileTree = useMemo(
|
let fileTree = useMemo(
|
||||||
() => newFileTree(torrentDetails, torrentStats),
|
() => newFileTree(torrentDetails, torrentStats),
|
||||||
|
|
@ -234,6 +257,7 @@ export const FileListInput: React.FC<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileTreeComponent
|
<FileTreeComponent
|
||||||
|
torrentId={torrentId}
|
||||||
torrentDetails={torrentDetails}
|
torrentDetails={torrentDetails}
|
||||||
torrentStats={torrentStats}
|
torrentStats={torrentStats}
|
||||||
tree={fileTree}
|
tree={fileTree}
|
||||||
|
|
@ -242,6 +266,7 @@ export const FileListInput: React.FC<{
|
||||||
initialExpanded={true}
|
initialExpanded={true}
|
||||||
showProgressBar={showProgressBar}
|
showProgressBar={showProgressBar}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
allowStream={allowStream}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -170,11 +170,13 @@ export const TorrentRow: React.FC<{
|
||||||
{detailsResponse && extendedView && (
|
{detailsResponse && extendedView && (
|
||||||
<div className="">
|
<div className="">
|
||||||
<FileListInput
|
<FileListInput
|
||||||
|
torrentId={id}
|
||||||
torrentDetails={detailsResponse}
|
torrentDetails={detailsResponse}
|
||||||
torrentStats={statsResponse}
|
torrentStats={statsResponse}
|
||||||
selectedFiles={selectedFiles}
|
selectedFiles={selectedFiles}
|
||||||
setSelectedFiles={updateSelectedFiles}
|
setSelectedFiles={updateSelectedFiles}
|
||||||
disabled={savingSelectedFiles}
|
disabled={savingSelectedFiles}
|
||||||
|
allowStream
|
||||||
showProgressBar
|
showProgressBar
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const FormCheckbox: React.FC<{
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
classNames?: string;
|
classNames?: string;
|
||||||
|
labelLink?: string | null;
|
||||||
}> = ({
|
}> = ({
|
||||||
checked,
|
checked,
|
||||||
name,
|
name,
|
||||||
|
|
@ -19,6 +20,7 @@ export const FormCheckbox: React.FC<{
|
||||||
help,
|
help,
|
||||||
inputType,
|
inputType,
|
||||||
children,
|
children,
|
||||||
|
labelLink,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className={`flex gap-3 items-start`}>
|
<div className={`flex gap-3 items-start`}>
|
||||||
|
|
@ -34,7 +36,17 @@ export const FormCheckbox: React.FC<{
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm flex flex-col gap-1">
|
<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 && (
|
{help && (
|
||||||
<div className="text-xs text-slate-500 dark:text-slate-300 mb-3">
|
<div className="text-xs text-slate-500 dark:text-slate-300 mb-3">
|
||||||
{help}
|
{help}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ export const APIContext = createContext<RqbitAPI>({
|
||||||
delete: () => {
|
delete: () => {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
getStreamLogsUrl: () => {
|
getTorrentStreamUrl: () => {
|
||||||
return null;
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const RefreshTorrentStatsContext = createContext({ refresh: () => {} });
|
export const RefreshTorrentStatsContext = createContext({ refresh: () => {} });
|
||||||
|
|
|
||||||
|
|
@ -140,4 +140,7 @@ export const API: RqbitAPI & { getVersion: () => Promise<string> } = {
|
||||||
const r = await makeRequest("GET", "/");
|
const r = await makeRequest("GET", "/");
|
||||||
return r.version;
|
return r.version;
|
||||||
},
|
},
|
||||||
|
getTorrentStreamUrl: (index: number, file_id: number) => {
|
||||||
|
return apiUrl + `/torrents/${index}/stream/${file_id}`;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -122,5 +122,8 @@ export const makeAPI = (configuration: RqbitDesktopConfig): RqbitAPI => {
|
||||||
delete: function (id: number): Promise<void> {
|
delete: function (id: number): Promise<void> {
|
||||||
return invokeAPI<void>("torrent_action_delete", { id });
|
return invokeAPI<void>("torrent_action_delete", { id });
|
||||||
},
|
},
|
||||||
|
getTorrentStreamUrl: () => {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue