diff --git a/src/public/js/actions/torrents.js b/src/public/js/actions/torrents.js index c554d8d..f6c7f0b 100644 --- a/src/public/js/actions/torrents.js +++ b/src/public/js/actions/torrents.js @@ -30,3 +30,10 @@ export function fetchTorrents() { configureAxios().get("/torrents") ) } + +export function searchTorrents(url) { + return request( + "TORRENTS_SEARCH", + configureAxios().get(url) + ) +} diff --git a/src/public/js/components/navbar.js b/src/public/js/components/navbar.js index 5c38db6..19ae1f9 100644 --- a/src/public/js/components/navbar.js +++ b/src/public/js/components/navbar.js @@ -64,7 +64,7 @@ export default class AppNavBar extends React.PureComponent { } {loggedAndActivated && - + } ) return( ); } + +function TorrentsDropdownTitle(props) { + return ( + + Torrents + {props.torrentsCount > 0 && + +   {props.torrentsCount} + + } + + ); +} diff --git a/src/public/js/components/torrents/search.js b/src/public/js/components/torrents/search.js new file mode 100644 index 0000000..dd2a2e9 --- /dev/null +++ b/src/public/js/components/torrents/search.js @@ -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 ( +
+
+
e.preventDefault()}> +
+ +
+
+
+
+ this.handleClick("movies")} + /> + this.handleClick("shows")} + /> +
+
+
+ +
+
+ ); + } +} + + +function SearchButton(props) { + const color = (props.type === props.typeFromURL) ? "primary" : "default"; + return ( +
+ +
+ + ); +} + +function TorrentList(props) { + if (props.searching) { + return (); + } + + if (props.searchFromURL === "") { + return null; + } + + if (props.results.size === 0) { + return ( +
+
+

No results

+
+
+ ); + } + + return ( +
+ {props.results.map(function(el, index) { + return ( + ); + })} +
+ ); +} + +function Torrent(props) { + return ( +
+
+ + + + + + + + + + + + + + +
+

+ +

+
+ {props.data.get("name")} + +

props.addTorrent(props.data.get("url"))}> + +

+
+ {props.data.get("quality")} + + {props.data.get("source")} + + {props.data.get("upload_user")} +
+
+
+ ); +} + +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 = ( + +

Health: {health}

+

Seeders: {seeders}

+

Leechers: {props.leechers}

+
+ ); + + return ( + + + + + + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch); diff --git a/src/public/js/reducers/torrents.js b/src/public/js/reducers/torrents.js index 6e219ec..d00f985 100644 --- a/src/public/js/reducers/torrents.js +++ b/src/public/js/reducers/torrents.js @@ -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) => diff --git a/src/public/js/routes.js b/src/public/js/routes.js index 3f1fcd1..20a0b91 100644 --- a/src/public/js/routes.js +++ b/src/public/js/routes.js @@ -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, diff --git a/src/public/less/app.less b/src/public/less/app.less index c6fbf3f..0fecbf3 100644 --- a/src/public/less/app.less +++ b/src/public/less/app.less @@ -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; +}