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 count = useSelector((state) => state.torrents.torrents.length);
const count = useSelector((state) => state.torrents.count);
if (count === 0) {
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,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>
);
};

View File

@ -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>
);

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 { 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>
);

View File

@ -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":

View File

@ -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)}`;