Add torrent search in the UI
This commit is contained in:
parent
0dda37de50
commit
5d3fc58176
@ -30,3 +30,10 @@ export function fetchTorrents() {
|
||||
configureAxios().get("/torrents")
|
||||
)
|
||||
}
|
||||
|
||||
export function searchTorrents(url) {
|
||||
return request(
|
||||
"TORRENTS_SEARCH",
|
||||
configureAxios().get(url)
|
||||
)
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export default class AppNavBar extends React.PureComponent {
|
||||
<WishlistDropdown />
|
||||
}
|
||||
{loggedAndActivated &&
|
||||
<Torrents torrentsCount={this.props.torrentCount} />
|
||||
<TorrentsDropdown torrentsCount={this.props.torrentCount} />
|
||||
}
|
||||
<UserDropdown
|
||||
username={this.props.username}
|
||||
@ -199,20 +199,31 @@ function WishlistDropdown() {
|
||||
);
|
||||
}
|
||||
|
||||
function Torrents(props) {
|
||||
function TorrentsDropdown(props) {
|
||||
const title = (<TorrentsDropdownTitle torrentsCount={props.torrentsCount} />)
|
||||
return(
|
||||
<Nav>
|
||||
<LinkContainer to="/torrents">
|
||||
<NavItem>
|
||||
Torrents
|
||||
{props.torrentsCount > 0 &&
|
||||
<span>
|
||||
|
||||
<span className="label label-info">{props.torrentsCount}</span>
|
||||
</span>
|
||||
}
|
||||
</NavItem>
|
||||
</LinkContainer>
|
||||
<NavDropdown title={title} id="navbar-wishlit-dropdown">
|
||||
<LinkContainer to="/torrents/list">
|
||||
<MenuItem>Downloads</MenuItem>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/torrents/search">
|
||||
<MenuItem>Search</MenuItem>
|
||||
</LinkContainer>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
|
||||
function TorrentsDropdownTitle(props) {
|
||||
return (
|
||||
<span>
|
||||
Torrents
|
||||
{props.torrentsCount > 0 &&
|
||||
<span>
|
||||
<span className="label label-info">{props.torrentsCount}</span>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
212
src/public/js/components/torrents/search.js
Normal file
212
src/public/js/components/torrents/search.js
Normal file
@ -0,0 +1,212 @@
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent, searchTorrents } from "../../actions/torrents"
|
||||
import Loader from "../loader/loader"
|
||||
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
searching: state.torrentStore.get("searching"),
|
||||
results: state.torrentStore.get("searchResults"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ addTorrent, searchTorrents }, dispatch)
|
||||
|
||||
class TorrentSearch extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSearchInput = this.handleSearchInput.bind(this);
|
||||
this.state = { search: (this.props.router.params.search || "") };
|
||||
}
|
||||
handleSearchInput() {
|
||||
this.setState({ search: this.refs.search.value });
|
||||
}
|
||||
handleClick(type) {
|
||||
if (this.state.search === "") { return }
|
||||
const url = `/torrents/search/${type}/${encodeURI(this.state.search)}`;
|
||||
this.props.router.push(url);
|
||||
}
|
||||
render() {
|
||||
const searchFromURL = this.props.router.params.search || "";
|
||||
const typeFromURL = this.props.router.params.type || "";
|
||||
return (
|
||||
<div>
|
||||
<div className="col-xs-12">
|
||||
<form className="form-horizontal" onSubmit={(e) => e.preventDefault()}>
|
||||
<div className="form-group">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Search torrents"
|
||||
value={this.state.search}
|
||||
onChange={this.handleSearchInput}
|
||||
ref="search"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div className="row">
|
||||
<SearchButton
|
||||
text="Search movies"
|
||||
type="movies"
|
||||
typeFromURL={typeFromURL}
|
||||
handleClick={() => this.handleClick("movies")}
|
||||
/>
|
||||
<SearchButton
|
||||
text="Search shows"
|
||||
type="shows"
|
||||
typeFromURL={typeFromURL}
|
||||
handleClick={() => this.handleClick("shows")}
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<TorrentList
|
||||
searching={this.props.searching}
|
||||
results={this.props.results}
|
||||
addTorrent={this.props.addTorrent}
|
||||
searchFromURL={searchFromURL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function SearchButton(props) {
|
||||
const color = (props.type === props.typeFromURL) ? "primary" : "default";
|
||||
return (
|
||||
<div className="col-xs-6">
|
||||
<button
|
||||
className={`btn btn-${color} full-width`}
|
||||
type="button"
|
||||
onClick={props.handleClick}
|
||||
>
|
||||
<i className="fa fa-search" aria-hidden="true"></i> {props.text}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
function TorrentList(props) {
|
||||
if (props.searching) {
|
||||
return (<Loader />);
|
||||
}
|
||||
|
||||
if (props.searchFromURL === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (props.results.size === 0) {
|
||||
return (
|
||||
<div className="col-xs-12">
|
||||
<div className="well well-lg">
|
||||
<h2>No results</h2>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-xs-12">
|
||||
{props.results.map(function(el, index) {
|
||||
return (
|
||||
<Torrent
|
||||
key={index}
|
||||
data={el}
|
||||
addTorrent={props.addTorrent}
|
||||
/>);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Torrent(props) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12">
|
||||
<table className="table responsive table-align-middle torrent-search-result">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowSpan="2" className="col-xs-1 torrent-small-width">
|
||||
<h4>
|
||||
<TorrentHealth
|
||||
url={props.data.get("url")}
|
||||
seeders={props.data.get("seeders")}
|
||||
leechers={props.data.get("leechers")}
|
||||
/>
|
||||
</h4>
|
||||
</td>
|
||||
<td colSpan="4" className="col-xs-9 title">
|
||||
<span className="torrent-title">{props.data.get("name")}</span>
|
||||
</td>
|
||||
<td rowSpan="2" className="col-xs-1 torrent-small-width">
|
||||
<h4 className="pull-right clickable" onClick={() => props.addTorrent(props.data.get("url"))}>
|
||||
<i className="fa fa-cloud-download" aria-hidden="true"></i>
|
||||
</h4>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="col-xs-1 torrent-label">
|
||||
<span className="label label-warning">{props.data.get("quality")}</span>
|
||||
</td>
|
||||
<td className="col-xs-1 torrent-label">
|
||||
<span className="label label-success">{props.data.get("source")}</span>
|
||||
</td>
|
||||
<td className="col-xs-1 torrent-label">
|
||||
<span className="label label-info">{props.data.get("upload_user")}</span>
|
||||
</td>
|
||||
<td className="col-xs-7 torrent-label"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function 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 = `text 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>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|
@ -2,15 +2,22 @@ import { Map, List, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
"fetching": false,
|
||||
"searching": false,
|
||||
"torrents": List(),
|
||||
"searchResults": List(),
|
||||
});
|
||||
|
||||
const handlers = {
|
||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", false),
|
||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", true),
|
||||
"TORRENTS_FETCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||
fetching: false,
|
||||
torrents: action.payload.response.data,
|
||||
})),
|
||||
"TORRENTS_SEARCH_PENDING": state => state.set("searching", true),
|
||||
"TORRENTS_SEARCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||
searching: false,
|
||||
searchResults: action.payload.response.data,
|
||||
})),
|
||||
}
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
|
@ -6,9 +6,10 @@ import UserEdit from "./components/users/edit"
|
||||
import UserActivation from "./components/users/activation"
|
||||
import UserSignUp from "./components/users/signup"
|
||||
import TorrentList from "./components/torrents/list"
|
||||
import TorrentSearch from "./components/torrents/search"
|
||||
import AdminPanel from "./components/admins/panel"
|
||||
|
||||
import { fetchTorrents } from "./actions/torrents"
|
||||
import { fetchTorrents, searchTorrents } from "./actions/torrents"
|
||||
import { userLogout, getUserInfos } from "./actions/users"
|
||||
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
||||
@ -240,7 +241,7 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents",
|
||||
path: "/torrents/list",
|
||||
component: TorrentList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
@ -248,6 +249,22 @@ export default function getRoutes(App) {
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/search",
|
||||
component: TorrentSearch,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/torrents/search/:type/:search",
|
||||
component: TorrentSearch,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(searchTorrents(`/torrents/search/${nextState.params.type}/${encodeURI(nextState.params.search)}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
component: AdminPanel,
|
||||
|
@ -81,3 +81,25 @@ div.sweet-alert > h2 {
|
||||
margin-left: 3px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
button.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table.torrent-search-result {
|
||||
font-size: 15px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 2px solid @gray-light;
|
||||
td.torrent-small-width {
|
||||
width: 1%;
|
||||
}
|
||||
td.torrent-label {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
table.table-align-middle > tbody > tr > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user