Rich torrent group #26
@ -170,7 +170,7 @@ const TorrentsDropdown = () => {
|
||||
};
|
||||
|
||||
const TorrentsDropdownTitle = () => {
|
||||
const count = useSelector((state) => state.torrents.torrents.length);
|
||||
const count = useSelector((state) => state.torrents.count);
|
||||
if (count === 0) {
|
||||
return <span>Torrents</span>;
|
||||
}
|
||||
|
17
frontend/js/components/torrents/list/poster.js
Normal file
17
frontend/js/components/torrents/list/poster.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
export const Poster = ({ url }) => {
|
||||
if (!url || url === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-md-2 d-none d-md-block">
|
||||
<img className="card-img" src={url} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Poster.propTypes = {
|
||||
url: PropTypes.string,
|
||||
};
|
@ -20,9 +20,7 @@ export const Progress = ({ torrent }) => {
|
||||
const totalSize = prettySize(torrent.status.total_size);
|
||||
const downloadRate = prettySize(torrent.status.download_rate) + "/s";
|
||||
return (
|
||||
<div className="card-body pb-0">
|
||||
{started && (
|
||||
<>
|
||||
<div>
|
||||
<div className="progress bg-light">
|
||||
<div
|
||||
className={progressBarClass}
|
||||
@ -33,12 +31,16 @@ export const Progress = ({ torrent }) => {
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
{started && (
|
||||
<p>
|
||||
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{!started && <p>Download not yet started</p>}
|
||||
{!started && (
|
||||
<p>
|
||||
<small>Not yet started</small>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { prettyEpisodeName } from "../../../utils";
|
||||
import { prettyEpisodeNameWithoutShow } from "../../../utils";
|
||||
import { removeTorrent } from "../../../actions/torrents";
|
||||
|
||||
import { Progress } from "./progress";
|
||||
@ -10,32 +10,31 @@ import { Progress } from "./progress";
|
||||
export const Torrent = ({ torrent }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const torrentTitle = (torrent) => {
|
||||
switch (torrent.type) {
|
||||
case "movie":
|
||||
return torrent.video ? torrent.video.title : torrent.status.name;
|
||||
case "episode":
|
||||
return torrent.video
|
||||
? prettyEpisodeName(
|
||||
torrent.video.show_title,
|
||||
const title = (torrent) => {
|
||||
if (torrent.type !== "episode" || !torrent.video) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return (
|
||||
prettyEpisodeNameWithoutShow(
|
||||
torrent.video.season,
|
||||
torrent.video.episode
|
||||
)
|
||||
: torrent.status.name;
|
||||
default:
|
||||
return torrent.status.name;
|
||||
}
|
||||
) + " - "
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card w-100 mb-3">
|
||||
<h5 className="card-header">
|
||||
<span className="text text-break">{torrentTitle(torrent)}</span>
|
||||
<span
|
||||
className="fa fa-trash clickable pull-right"
|
||||
<div className="border-top">
|
||||
<div className="card-text d-flex flex-row">
|
||||
<div className="mt-1 flex-fill">
|
||||
<span className="text text-break">{title(torrent)}</span>
|
||||
<small className="text-muted text-break">{torrent.status.name}</small>
|
||||
</div>
|
||||
<div
|
||||
className="fa fa-trash btn text-right"
|
||||
onClick={() => dispatch(removeTorrent(torrent.status.id))}
|
||||
></span>
|
||||
</h5>
|
||||
/>
|
||||
</div>
|
||||
<Progress torrent={torrent} />
|
||||
</div>
|
||||
);
|
||||
|
49
frontend/js/components/torrents/list/torrentGroup.js
Normal file
49
frontend/js/components/torrents/list/torrentGroup.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Torrent } from "./torrent";
|
||||
import { Poster } from "./poster";
|
||||
|
||||
export const TorrentGroup = ({ torrentKey }) => {
|
||||
const torrents = useSelector((state) =>
|
||||
state.torrents.torrents.get(torrentKey)
|
||||
);
|
||||
|
||||
if (torrents.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = (torrent) => {
|
||||
switch (torrent.type) {
|
||||
case "movie":
|
||||
return torrent.video.title;
|
||||
case "episode":
|
||||
return torrent.video.show_title;
|
||||
default:
|
||||
return "Files";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-100 mb-3 card">
|
||||
<div className="row no-gutters">
|
||||
<Poster url={torrents[0].img} />
|
||||
<div className="col-sm">
|
||||
<div className="card-body">
|
||||
<h4 className="card-title">{title(torrents[0])}</h4>
|
||||
{torrents.map((torrent, i) => (
|
||||
<Torrent
|
||||
key={torrent.video ? torrent.video.imdb_id : i}
|
||||
torrent={torrent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
TorrentGroup.propTypes = {
|
||||
torrentKey: PropTypes.string.isRequired,
|
||||
};
|
@ -1,12 +1,14 @@
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Torrent } from "./torrent";
|
||||
import { TorrentGroup } from "./torrentGroup";
|
||||
|
||||
export const Torrents = () => {
|
||||
const torrents = useSelector((state) => state.torrents.torrents);
|
||||
const torrentsKeys = useSelector((state) =>
|
||||
Array.from(state.torrents.torrents.keys())
|
||||
);
|
||||
|
||||
if (torrents.length === 0) {
|
||||
if (torrentsKeys.length === 0) {
|
||||
return (
|
||||
<div className="jumbotron">
|
||||
<h2>No torrents</h2>
|
||||
@ -16,8 +18,8 @@ export const Torrents = () => {
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-wrap">
|
||||
{torrents.map((torrent, index) => (
|
||||
<Torrent key={index} torrent={torrent} />
|
||||
{torrentsKeys.map((key) => (
|
||||
<TorrentGroup key={key} torrentKey={key} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
@ -3,10 +3,42 @@ import { produce } from "immer";
|
||||
const defaultState = {
|
||||
fetching: false,
|
||||
searching: false,
|
||||
torrents: [],
|
||||
torrents: new Map(),
|
||||
count: 0,
|
||||
searchResults: [],
|
||||
};
|
||||
|
||||
// Group the torrents by imdb id
|
||||
const formatTorrents = (input) => {
|
||||
let torrents = new Map();
|
||||
if (!input) {
|
||||
return torrents;
|
||||
}
|
||||
|
||||
input.forEach((t) => {
|
||||
let key;
|
||||
switch (t.type) {
|
||||
case "movie":
|
||||
key = t.video ? t.video.imdb_id : "unknown";
|
||||
break;
|
||||
case "episode":
|
||||
key = t.video ? t.video.show_imdb_id : "unknown";
|
||||
break;
|
||||
default:
|
||||
key = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
if (!torrents.has(key)) {
|
||||
torrents.set(key, []);
|
||||
}
|
||||
|
||||
torrents.get(key).push(t);
|
||||
});
|
||||
|
||||
return torrents;
|
||||
};
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
produce(state, (draft) => {
|
||||
switch (action.type) {
|
||||
@ -16,7 +48,8 @@ export default (state = defaultState, action) =>
|
||||
|
||||
case "TORRENTS_FETCH_FULFILLED":
|
||||
draft.fetching = false;
|
||||
draft.torrents = action.payload.response.data;
|
||||
draft.torrents = formatTorrents(action.payload.response.data);
|
||||
draft.count = action.payload.response.data.length;
|
||||
break;
|
||||
|
||||
case "TORRENTS_SEARCH_PENDING":
|
||||
|
@ -18,6 +18,9 @@ export const prettyDurationFromMinutes = (runtime) => {
|
||||
|
||||
const pad = (d) => (d < 10 ? "0" + d.toString() : d.toString());
|
||||
|
||||
export const prettyEpisodeNameWithoutShow = (season, episode) =>
|
||||
`S${pad(season)}E${pad(episode)}`;
|
||||
|
||||
export const prettyEpisodeName = (showName, season, episode) =>
|
||||
`${showName} S${pad(season)}E${pad(episode)}`;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user