Rich torrent group #26

Merged
PouuleT merged 1 commits from sutff into master 2020-04-13 16:29:02 +00:00
8 changed files with 152 additions and 47 deletions

View File

@ -170,7 +170,7 @@ const TorrentsDropdown = () => {
}; };
const TorrentsDropdownTitle = () => { const TorrentsDropdownTitle = () => {
const count = useSelector((state) => state.torrents.torrents.length); const count = useSelector((state) => state.torrents.count);
if (count === 0) { if (count === 0) {
return <span>Torrents</span>; return <span>Torrents</span>;
} }

View 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,
};

View File

@ -20,25 +20,27 @@ export const Progress = ({ torrent }) => {
const totalSize = prettySize(torrent.status.total_size); const totalSize = prettySize(torrent.status.total_size);
const downloadRate = prettySize(torrent.status.download_rate) + "/s"; const downloadRate = prettySize(torrent.status.download_rate) + "/s";
return ( return (
<div className="card-body pb-0"> <div>
<div className="progress bg-light">
<div
className={progressBarClass}
style={{ width: percentDone }}
role="progressbar"
aria-valuenow={percentDone}
aria-valuemin="0"
aria-valuemax="100"
></div>
</div>
{started && ( {started && (
<> <p>
<div className="progress bg-light"> {downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
<div </p>
className={progressBarClass} )}
style={{ width: percentDone }} {!started && (
role="progressbar" <p>
aria-valuenow={percentDone} <small>Not yet started</small>
aria-valuemin="0" </p>
aria-valuemax="100"
></div>
</div>
<p>
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
</p>
</>
)} )}
{!started && <p>Download not yet started</p>}
</div> </div>
); );
}; };

View File

@ -2,7 +2,7 @@ import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { prettyEpisodeName } from "../../../utils"; import { prettyEpisodeNameWithoutShow } from "../../../utils";
import { removeTorrent } from "../../../actions/torrents"; import { removeTorrent } from "../../../actions/torrents";
import { Progress } from "./progress"; import { Progress } from "./progress";
@ -10,32 +10,31 @@ import { Progress } from "./progress";
export const Torrent = ({ torrent }) => { export const Torrent = ({ torrent }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const torrentTitle = (torrent) => { const title = (torrent) => {
switch (torrent.type) { if (torrent.type !== "episode" || !torrent.video) {
case "movie": return "";
return torrent.video ? torrent.video.title : torrent.status.name;
case "episode":
return torrent.video
? prettyEpisodeName(
torrent.video.show_title,
torrent.video.season,
torrent.video.episode
)
: torrent.status.name;
default:
return torrent.status.name;
} }
return (
prettyEpisodeNameWithoutShow(
torrent.video.season,
torrent.video.episode
) + " - "
);
}; };
return ( return (
<div className="card w-100 mb-3"> <div className="border-top">
<h5 className="card-header"> <div className="card-text d-flex flex-row">
<span className="text text-break">{torrentTitle(torrent)}</span> <div className="mt-1 flex-fill">
<span <span className="text text-break">{title(torrent)}</span>
className="fa fa-trash clickable pull-right" <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))} onClick={() => dispatch(removeTorrent(torrent.status.id))}
></span> />
</h5> </div>
<Progress torrent={torrent} /> <Progress torrent={torrent} />
</div> </div>
); );

View 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,
};

View File

@ -1,12 +1,14 @@
import React from "react"; import React from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { Torrent } from "./torrent"; import { TorrentGroup } from "./torrentGroup";
export const Torrents = () => { 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 ( return (
<div className="jumbotron"> <div className="jumbotron">
<h2>No torrents</h2> <h2>No torrents</h2>
@ -16,8 +18,8 @@ export const Torrents = () => {
return ( return (
<div className="d-flex flex-wrap"> <div className="d-flex flex-wrap">
{torrents.map((torrent, index) => ( {torrentsKeys.map((key) => (
<Torrent key={index} torrent={torrent} /> <TorrentGroup key={key} torrentKey={key} />
))} ))}
</div> </div>
); );

View File

@ -3,10 +3,42 @@ import { produce } from "immer";
const defaultState = { const defaultState = {
fetching: false, fetching: false,
searching: false, searching: false,
torrents: [], torrents: new Map(),
count: 0,
searchResults: [], 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) => export default (state = defaultState, action) =>
produce(state, (draft) => { produce(state, (draft) => {
switch (action.type) { switch (action.type) {
@ -16,7 +48,8 @@ export default (state = defaultState, action) =>
case "TORRENTS_FETCH_FULFILLED": case "TORRENTS_FETCH_FULFILLED":
draft.fetching = false; draft.fetching = false;
draft.torrents = action.payload.response.data; draft.torrents = formatTorrents(action.payload.response.data);
draft.count = action.payload.response.data.length;
break; break;
case "TORRENTS_SEARCH_PENDING": case "TORRENTS_SEARCH_PENDING":

View File

@ -18,6 +18,9 @@ export const prettyDurationFromMinutes = (runtime) => {
const pad = (d) => (d < 10 ? "0" + d.toString() : d.toString()); 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) => export const prettyEpisodeName = (showName, season, episode) =>
`${showName} S${pad(season)}E${pad(episode)}`; `${showName} S${pad(season)}E${pad(episode)}`;