208 lines
5.5 KiB
JavaScript
208 lines
5.5 KiB
JavaScript
import React, { useState, useEffect } from "react"
|
|
import PropTypes from "prop-types"
|
|
import { connect } from "react-redux"
|
|
import { addTorrent, searchTorrents } from "../../actions/torrents"
|
|
import { Map, List } from "immutable"
|
|
import Loader from "../loader/loader"
|
|
|
|
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
|
|
|
const mapStateToProps = (state) => ({
|
|
searching: state.torrentStore.get("searching"),
|
|
results: state.torrentStore.get("searchResults"),
|
|
});
|
|
const mapDispatchToProps = { addTorrent, searchTorrents };
|
|
|
|
const TorrentSearch = (props) => {
|
|
const [search, setSearch] = useState(props.match.params.search || "");
|
|
const [type, setType] = useState(props.match.params.type || "");
|
|
const [url, setUrl] = useState("");
|
|
|
|
const getUrl = () =>
|
|
`/torrents/search/${type}/${encodeURI(search)}`;
|
|
|
|
useEffect(() => {
|
|
if (search === "") { return }
|
|
if (type === "") { return }
|
|
|
|
const url = getUrl();
|
|
props.searchTorrents(url)
|
|
props.history.push(url);
|
|
}, [url]);
|
|
|
|
return (
|
|
<div className="row">
|
|
<div className="col-12 d-flex flex-row flex-wrap">
|
|
<input
|
|
type="text"
|
|
className="form-control mb-1 w-100 form-control-lg"
|
|
placeholder="Search torrents"
|
|
value={search}
|
|
onChange={(e) => setSearch(e.target.value)}
|
|
/>
|
|
<div className="mb-3 w-100 d-flex">
|
|
<SearchButton
|
|
text="Search movies"
|
|
type="movies"
|
|
typeFromURL={type}
|
|
handleClick={() => {
|
|
setType("movies");
|
|
setUrl(getUrl());
|
|
}}/>
|
|
<SearchButton
|
|
text="Search shows"
|
|
type="shows"
|
|
typeFromURL={type}
|
|
handleClick={() => {
|
|
setType("shows");
|
|
setUrl(getUrl());
|
|
}}/>
|
|
</div>
|
|
</div>
|
|
<div className="col-12">
|
|
<TorrentList
|
|
searching={props.searching}
|
|
results={props.results}
|
|
addTorrent={props.addTorrent}
|
|
searchFromURL={search}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
TorrentSearch.propTypes = {
|
|
searching: PropTypes.bool.isRequired,
|
|
results: PropTypes.instanceOf(List),
|
|
searchFromURL: PropTypes.string,
|
|
match: PropTypes.object,
|
|
history: PropTypes.object,
|
|
addTorrent: PropTypes.func.isRequired,
|
|
searchTorrents: PropTypes.func.isRequired,
|
|
};
|
|
|
|
|
|
const SearchButton = (props) => {
|
|
const variant = (props.type === props.typeFromURL) ? "primary" : "secondary";
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={`w-50 btn m-1 btn-lg btn-${variant}`}
|
|
onClick={props.handleClick}
|
|
>
|
|
<i className="fa fa-search" aria-hidden="true"></i> {props.text}
|
|
</button>
|
|
);
|
|
}
|
|
SearchButton.propTypes = {
|
|
type: PropTypes.string,
|
|
typeFromURL: PropTypes.string,
|
|
text: PropTypes.string,
|
|
handleClick: PropTypes.func.isRequired,
|
|
};
|
|
|
|
const TorrentList = (props) => {
|
|
if (props.searching) {
|
|
return (<Loader />);
|
|
}
|
|
|
|
if (props.searchFromURL === "") {
|
|
return null;
|
|
}
|
|
|
|
if (props.results.size === 0) {
|
|
return (
|
|
<div className="jumbotron">
|
|
<h2>No results</h2>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<React.Fragment>
|
|
{props.results.map(function(el, index) {
|
|
return (
|
|
<Torrent
|
|
key={index}
|
|
data={el}
|
|
addTorrent={props.addTorrent}
|
|
/>);
|
|
})}
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
TorrentList.propTypes = {
|
|
searching: PropTypes.bool.isRequired,
|
|
results: PropTypes.instanceOf(List),
|
|
searchFromURL: PropTypes.string,
|
|
addTorrent: PropTypes.func.isRequired,
|
|
};
|
|
|
|
const Torrent = (props) => (
|
|
<div className="alert d-flex border-bottom border-secondary align-items-center">
|
|
<TorrentHealth
|
|
url={props.data.get("url")}
|
|
seeders={props.data.get("seeders")}
|
|
leechers={props.data.get("leechers")}
|
|
/>
|
|
<span className="mx-3 text text-start text-break flex-fill">{props.data.get("name")}</span>
|
|
<div>
|
|
<span className="mx-1 badge badge-pill badge-warning">{props.data.get("quality")}</span>
|
|
<span className="mx-1 badge badge-pill badge-success">{props.data.get("source")}</span>
|
|
<span className="mx-1 badge badge-pill badge-info">{props.data.get("upload_user")}</span>
|
|
</div>
|
|
<div className="align-self-end ml-3">
|
|
<i className="fa fa-cloud-download clickable" onClick={() => props.addTorrent(props.data.get("url"))}></i>
|
|
</div>
|
|
</div>
|
|
);
|
|
Torrent.propTypes = {
|
|
data: PropTypes.instanceOf(Map),
|
|
addTorrent: PropTypes.func.isRequired,
|
|
};
|
|
|
|
const TorrentHealth = (props) => {
|
|
const seeders = props.seeders || 0;
|
|
const leechers = props.leechers || 1;
|
|
|
|
let color;
|
|
let health;
|
|
let ratio = seeders/leechers;
|
|
|
|
if (seeders > 20) {
|
|
health = "good";
|
|
color = "success";
|
|
} else {
|
|
if (ratio > 1) {
|
|
health = "medium";
|
|
color = "warning";
|
|
} else {
|
|
health = "bad";
|
|
color = "danger";
|
|
}
|
|
}
|
|
|
|
const className = `align-self-start text text-center text-${color}`;
|
|
const tooltip = (
|
|
<Tooltip id={`tooltip-health-${props.url}`}>
|
|
<p><span className={className}>Health: {health}</span></p>
|
|
<p>Seeders: {seeders}</p>
|
|
<p>Leechers: {props.leechers}</p>
|
|
</Tooltip>
|
|
);
|
|
|
|
return (
|
|
<OverlayTrigger placement="right" overlay={tooltip}>
|
|
<span className={className}>
|
|
<i className="fa fa-circle" aria-hidden="true"></i>
|
|
</span>
|
|
</OverlayTrigger>
|
|
);
|
|
}
|
|
TorrentHealth.propTypes = {
|
|
url: PropTypes.string,
|
|
seeders: PropTypes.number,
|
|
leechers: PropTypes.number,
|
|
};
|
|
|
|
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|