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 (
+
+
+
+ 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 (
+
+ );
+ }
+
+ 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;
+}