From 908f930081a2a062cbc440f1c84345df254252c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Sun, 21 May 2017 17:24:10 +0200 Subject: [PATCH 01/12] Improve performance while rendering the navbar Only re-render the navbar if needed --- src/public/js/app.js | 6 +- src/public/js/components/navbar.js | 149 ++++++++++++++++++----------- 2 files changed, 99 insertions(+), 56 deletions(-) diff --git a/src/public/js/app.js b/src/public/js/app.js index 366c3b8..cea0025 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -50,7 +50,11 @@ import TorrentList from './components/torrents/list' function Main(props) { return (
- +
{React.cloneElement(props.children, props)} diff --git a/src/public/js/components/navbar.js b/src/public/js/components/navbar.js index 1c6e8bc..8a7193e 100644 --- a/src/public/js/components/navbar.js +++ b/src/public/js/components/navbar.js @@ -2,41 +2,100 @@ import React from 'react' import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from 'react-bootstrap' import { LinkContainer } from 'react-router-bootstrap' -export default function NavBar(props) { - return ( -
- - - - Canapé - - - - - - - - - - - - - -
- ); +export default class NavBar extends React.Component { + constructor(props) { + super(props); + this.state = { + userLoggedIn: (props.username !== ""), + displayMoviesSearch: this.shouldDisplayMoviesSearch(props.router), + displayShowsSearch: this.shouldDisplayShowsSearch(props.router), + }; + } + shouldComponentUpdate(nextProps, nextState) { + if (nextProps.username !== this.props.username) { + return true; + } + if (nextProps.torrentsCount !== this.props.torrentsCount) { + return true; + } + if (nextState.displayMoviesSearch !== this.state.displayMoviesSearch) { + return true; + } + if (nextState.displayShowsSearch !== this.state.displayShowsSearch) { + return true; + } + if (nextState.userLoggedIn !== this.state.userLoggedIn) { + return true; + } + + return false; + } + componentWillReceiveProps(nextProps) { + // Update the state based on the next props + const shouldDisplayMoviesSearch = this.shouldDisplayMoviesSearch(nextProps.router); + const shouldDisplayShowsSearch = this.shouldDisplayShowsSearch(nextProps.router); + if ((this.state.displayMoviesSearch !== shouldDisplayMoviesSearch) + || (this.state.displayShowsSearch !== shouldDisplayShowsSearch)) { + this.setState({ + userLoggedIn: (nextProps.username !== ""), + displayMoviesSearch: shouldDisplayMoviesSearch, + displayShowsSearch: shouldDisplayShowsSearch, + }); + } + } + shouldDisplayMoviesSearch(router) { + return this.matchPath(router, 'movies'); + } + shouldDisplayShowsSearch(router) { + return this.matchPath(router, 'shows'); + } + matchPath(router, keyword) { + const location = router.getCurrentLocation().pathname; + return (location.indexOf(keyword) !== -1) + } + render() { + return ( +
+ + + + Canapé + + + + + {this.state.userLoggedIn && + + } + {this.state.userLoggedIn && + + } + {this.state.userLoggedIn && + + } + {this.state.userLoggedIn && + + } + + {(this.state.displayMoviesSearch && this.state.userLoggedIn) && + + } + {(this.state.displayShowsSearch && this.state.userLoggedIn) && + + } + + +
+ ); + } } class Search extends React.Component { @@ -48,15 +107,7 @@ class Search extends React.Component { ev.preventDefault(); this.props.router.push(`${this.props.path}/${encodeURI(this.input.value)}`); } - isActive() { - const location = this.props.router.getCurrentLocation().pathname; - return (location.indexOf(this.props.pathMatch) !== -1) - } render() { - if (!this.isActive()) { - return null; - } - return(
this.handleSearch(ev)}> @@ -72,9 +123,6 @@ class Search extends React.Component { } function MoviesDropdown(props) { - if (props.username === "") { - return null; - } return(
); } - -function mapStateToProps(state) { - return { - movieStore: state.movieStore, - showStore: state.showStore, - userStore: state.userStore, - torrentStore: state.torrentStore, - alerts: state.alerts, - } -} - -function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); -} - -const App = connect(mapStateToProps, mapDispatchToProps)(Main); -export function startPollingTorrents() { - return request( - 'TORRENTS_FETCH', - configureAxios().get('/torrents') - ) -} - -// This function returns true if the user is logged in, false otherwise -function isLoggedIn() { - const state = store.getState(); - const isLogged = state.userStore.isLogged; - let token = localStorage.getItem('token'); - - // Let's check if the user has a token, if he does let's assume he's logged - // in. If that's not the case he will be logged out on the fisrt query - if (token && token !== "") { - store.dispatch({ - type: 'USER_SET_TOKEN', - payload: { - token: token, - }, - }); - } - - if (isLogged || (token && token !== "")) { - return true - } - - return false -} - -var pollingTorrentsId; -const loginCheck = function(nextState, replace, next, f = null) { - const loggedIn = isLoggedIn(); - if (!loggedIn) { - replace('/users/login'); - } else { - if (f) { - f(); - } - - // Poll torrents once logged - if (!pollingTorrentsId) { - // Fetch the torrents every 10s - pollingTorrentsId = setInterval(function() { - store.dispatch(actionCreators.fetchTorrents()); - }, 10000); - } - } - - next(); -} - -const defaultRoute = '/movies/explore/yts/seeds'; -const routes = { - path: '/', - component: App, - indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)}, - childRoutes: [ - { - path: '/users/signup', - component: UserSignUp - }, - { - path: '/users/login', - component: UserLoginForm, - onEnter: function(nextState, replace, next) { - if (isLoggedIn()) { - // User is already logged in, redirect him to the default route - replace(defaultRoute); - } - next(); - }, - }, - { - path: '/users/edit', - component: UserEdit, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next); - }, - }, - { - path: '/users/logout', - onEnter: function(nextState, replace, next) { - // Stop polling - if (pollingTorrentsId !== null) { - clearInterval(pollingTorrentsId); - pollingTorrentsId = null; - } - store.dispatch(actionCreators.userLogout()); - replace('/users/login'); - next(); - }, - }, - { - path: '/movies/search/:search', - component: MovieList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchMovies(`/movies/search/${nextState.params.search}`)); - }); - }, - }, - { - path: '/movies/polochon', - component: MovieList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchMovies('/movies/polochon')); - }); - }, - }, - { - path: '/movies/explore/:source/:category', - component: MovieList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - var state = store.getState(); - // Fetch the explore options - if (Object.keys(state.movieStore.exploreOptions).length === 0) { - store.dispatch(actionCreators.getMovieExploreOptions()); - } - store.dispatch(actionCreators.fetchMovies( - `/movies/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}` - )); - }); - }, - }, - { - path: '/movies/wishlist', - component: MovieList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchMovies('/wishlist/movies')); - }); - }, - }, - { - path: '/shows/search/:search', - component: ShowList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.search}`)); - }); - }, - }, - { - path: '/shows/polochon', - component: ShowList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows('/shows/polochon')); - }); - }, - }, - { - path: '/shows/wishlist', - component: ShowList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows('/wishlist/shows')); - }); - }, - }, - { - path: '/shows/details/:imdbId', - component: ShowDetails, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.imdbId}`)); - }); - }, - }, - { - path: '/shows/explore/:source/:category', - component: ShowList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - var state = store.getState(); - // Fetch the explore options - if (Object.keys(state.showStore.exploreOptions).length === 0) { - store.dispatch(actionCreators.getShowExploreOptions()); - } - store.dispatch(actionCreators.fetchShows( - `/shows/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}` - )); - }); - }, - }, - { - path: '/torrents', - component: TorrentList, - onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchTorrents()); - }); - }, - }, - ], -} +export const App = connect(mapStateToProps, mapDispatchToProps)(Main); ReactDOM.render(( - + ),document.getElementById('app')); diff --git a/src/public/js/routes.js b/src/public/js/routes.js new file mode 100644 index 0000000..7e329dd --- /dev/null +++ b/src/public/js/routes.js @@ -0,0 +1,215 @@ +import React from 'react' + +import MovieList from './components/movies/list' +import ShowList from './components/shows/list' +import ShowDetails from './components/shows/details' +import UserLoginForm from './components/users/login' +import UserEdit from './components/users/edit' +import UserSignUp from './components/users/signup' +import TorrentList from './components/torrents/list' + +import * as actionCreators from './actions/actionCreators' +import store from './store' + +function startPollingTorrents() { + return request( + 'TORRENTS_FETCH', + configureAxios().get('/torrents') + ) +} + +// This function returns true if the user is logged in, false otherwise +function isLoggedIn() { + const state = store.getState(); + const isLogged = state.userStore.isLogged; + let token = localStorage.getItem('token'); + + // Let's check if the user has a token, if he does let's assume he's logged + // in. If that's not the case he will be logged out on the fisrt query + if (token && token !== "") { + store.dispatch({ + type: 'USER_SET_TOKEN', + payload: { + token: token, + }, + }); + } + + if (isLogged || (token && token !== "")) { + return true + } + + return false +} + +var pollingTorrentsId; +const loginCheck = function(nextState, replace, next, f = null) { + const loggedIn = isLoggedIn(); + if (!loggedIn) { + replace('/users/login'); + } else { + if (f) { + f(); + } + + // Poll torrents once logged + if (!pollingTorrentsId) { + // Fetch the torrents every 10s + pollingTorrentsId = setInterval(function() { + store.dispatch(actionCreators.fetchTorrents()); + }, 10000); + } + } + + next(); +} + +const defaultRoute = '/movies/explore/yts/seeds'; +export default function getRoutes(App) { + return { + path: '/', + component: App, + indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)}, + childRoutes: [ + { + path: '/users/signup', + component: UserSignUp + }, + { + path: '/users/login', + component: UserLoginForm, + onEnter: function(nextState, replace, next) { + if (isLoggedIn()) { + // User is already logged in, redirect him to the default route + replace(defaultRoute); + } + next(); + }, + }, + { + path: '/users/edit', + component: UserEdit, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next); + }, + }, + { + path: '/users/logout', + onEnter: function(nextState, replace, next) { + // Stop polling + if (pollingTorrentsId !== null) { + clearInterval(pollingTorrentsId); + pollingTorrentsId = null; + } + store.dispatch(actionCreators.userLogout()); + replace('/users/login'); + next(); + }, + }, + { + path: '/movies/search/:search', + component: MovieList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchMovies(`/movies/search/${nextState.params.search}`)); + }); + }, + }, + { + path: '/movies/polochon', + component: MovieList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchMovies('/movies/polochon')); + }); + }, + }, + { + path: '/movies/explore/:source/:category', + component: MovieList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + var state = store.getState(); + // Fetch the explore options + if (Object.keys(state.movieStore.exploreOptions).length === 0) { + store.dispatch(actionCreators.getMovieExploreOptions()); + } + store.dispatch(actionCreators.fetchMovies( + `/movies/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}` + )); + }); + }, + }, + { + path: '/movies/wishlist', + component: MovieList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchMovies('/wishlist/movies')); + }); + }, + }, + { + path: '/shows/search/:search', + component: ShowList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.search}`)); + }); + }, + }, + { + path: '/shows/polochon', + component: ShowList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchShows('/shows/polochon')); + }); + }, + }, + { + path: '/shows/wishlist', + component: ShowList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchShows('/wishlist/shows')); + }); + }, + }, + { + path: '/shows/details/:imdbId', + component: ShowDetails, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.imdbId}`)); + }); + }, + }, + { + path: '/shows/explore/:source/:category', + component: ShowList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + var state = store.getState(); + // Fetch the explore options + if (Object.keys(state.showStore.exploreOptions).length === 0) { + store.dispatch(actionCreators.getShowExploreOptions()); + } + store.dispatch(actionCreators.fetchShows( + `/shows/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}` + )); + }); + }, + }, + { + path: '/torrents', + component: TorrentList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchTorrents()); + }); + }, + }, + ], + }; +}; From e3849d5fd3d96d3c20a5e99139218eff1a898b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Sun, 21 May 2017 19:32:01 +0200 Subject: [PATCH 04/12] Remove the torrents store from the global scope --- src/public/js/app.js | 4 +- src/public/js/components/torrents/list.js | 131 ++++++++++++---------- 2 files changed, 75 insertions(+), 60 deletions(-) diff --git a/src/public/js/app.js b/src/public/js/app.js index bec00f3..cd70a6b 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -45,7 +45,7 @@ function mapStateToProps(state) { movieStore: state.movieStore, showStore: state.showStore, userStore: state.userStore, - torrentStore: state.torrentStore, + torrentCount: state.torrentStore.torrents.length, alerts: state.alerts, } } @@ -60,7 +60,7 @@ function Main(props) { - - -
- ); +function mapStateToProps(state) { + return { torrents: state.torrentStore.torrents }; } +const mapDispatchToProps = (dipatch) => + bindActionCreators({ addTorrent }, dipatch) -class AddTorrent extends React.Component { +class TorrentList extends React.PureComponent { + render() { + return ( +
+ + +
+ ); + } +} +export default connect(mapStateToProps, mapDispatchToProps)(TorrentList); + +class AddTorrent extends React.PureComponent { constructor(props) { super(props); this.state = { url: '' }; @@ -53,72 +65,75 @@ class AddTorrent extends React.Component { } } -function List(props){ - if (props.torrents.length === 0) { +class List extends React.PureComponent { + render() { + if (this.props.torrents.length === 0) { + return ( +
+
+

Torrents

+
+
No torrents
+
+
+
+ ); + } return (

Torrents

-
-
No torrents
-
-
-
- ); - } - return ( -
-
-

Torrents

- {props.torrents.map(function(el, index) { + {this.props.torrents.map(function(el, index) { return ( ); })}
- ); + ); + } } +class Torrent extends React.PureComponent { + render() { + const done = this.props.data.is_finished; + var progressStyle = 'progress-bar progress-bar-warning'; + if (done) { + progressStyle = 'progress-bar progress-bar-success'; + } + var percentDone = this.props.data.percent_done; + const started = (percentDone !== 0); + if (started) { + percentDone = Number(percentDone).toFixed(1) + '%'; + } -function Torrent(props){ - const done = props.data.is_finished; - var progressStyle = 'progress-bar progress-bar-warning'; - if (done) { - progressStyle = 'progress-bar progress-bar-success'; - } - var percentDone = props.data.percent_done; - const started = (percentDone !== 0); - if (started) { - percentDone = Number(percentDone).toFixed(1) + '%'; - } - - var downloadedSize = prettySize(props.data.downloaded_size); - var totalSize = prettySize(props.data.total_size); - var downloadRate = prettySize(props.data.download_rate) + "/s"; - return ( -
-
{props.data.name}
-
- {started && -
-
+ var downloadedSize = prettySize(this.props.data.downloaded_size); + var totalSize = prettySize(this.props.data.total_size); + var downloadRate = prettySize(this.props.data.download_rate) + "/s"; + return ( +
+
{this.props.data.name}
+
+ {started && +
+
+
-
- } - {!started && -

Download not yet started

- } - {started && -
-

{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}

+ } + {!started && +

Download not yet started

+ } + {started && +
+

{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}

+
+ }
- }
-
- ); + ); + } } function prettySize(fileSizeInBytes) { From 94468167cba55c32a861093080d6c620191b2141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Sun, 21 May 2017 19:55:32 +0200 Subject: [PATCH 05/12] Remove the global state containing everything --- src/public/js/app.js | 12 +++++------- src/public/js/components/movies/list.js | 14 +++++++++++--- src/public/js/components/shows/details.js | 12 +++++++++++- src/public/js/components/shows/list.js | 12 +++++++++++- src/public/js/components/torrents/list.js | 4 ++-- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/public/js/app.js b/src/public/js/app.js index cd70a6b..6fb6f4b 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -28,7 +28,7 @@ import { Router } from 'react-router' import { routerActions } from 'react-router-redux' // Action creators -import * as actionCreators from './actions/actionCreators' +import { dismissAlert } from './actions/actionCreators' // Store import store, { history } from './store' @@ -42,23 +42,21 @@ import getRoutes from './routes' function mapStateToProps(state) { return { - movieStore: state.movieStore, - showStore: state.showStore, - userStore: state.userStore, + username: state.userStore.username, torrentCount: state.torrentStore.torrents.length, alerts: state.alerts, } } function mapDispatchToProps(dispatch) { - return bindActionCreators(actionCreators, dispatch); + return bindActionCreators({ dismissAlert }, dispatch); } function Main(props) { return (
@@ -67,7 +65,7 @@ function Main(props) { dismissAlert={props.dismissAlert} />
- {React.cloneElement(props.children, props)} + {props.children}
); diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 6014cb2..3056dd8 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -1,4 +1,7 @@ import React from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import * as actionCreators from '../../actions/actionCreators' import DownloadButton from '../buttons/download' import TorrentsButton from './torrents' @@ -6,6 +9,12 @@ import ActionsButton from './actions' import ListPosters from '../list/posters' import ListDetails from '../list/details' +function mapStateToProps(state) { + return { movieStore: state.movieStore }; +} +const mapDispatchToProps = (dipatch) => + bindActionCreators(actionCreators, dipatch) + function MovieButtons(props) { const imdb_link = `http://www.imdb.com/title/${props.movie.imdb_id}`; const hasMovie = (props.movie.polochon_url !== ""); @@ -16,7 +25,6 @@ function MovieButtons(props) { movieId={props.movie.imdb_id} getDetails={props.getMovieDetails} deleteMovie={props.deleteMovie} - isUserAdmin={props.isUserAdmin} hasMovie={hasMovie} wishlisted={props.movie.wishlisted} addToWishlist={props.addToWishlist} @@ -44,7 +52,7 @@ function MovieButtons(props) { ); } -export default class MovieList extends React.Component { +class MovieList extends React.Component { constructor(props) { super(props); } @@ -84,7 +92,6 @@ export default class MovieList extends React.Component { getMovieDetails={this.props.getMovieDetails} addTorrent={this.props.addTorrent} deleteMovie={this.props.deleteMovie} - isUserAdmin={this.props.userStore.isAdmin} addToWishlist={this.props.addMovieToWishlist} deleteFromWishlist={this.props.deleteMovieFromWishlist} fetchMovies={this.props.fetchMovies} @@ -96,3 +103,4 @@ export default class MovieList extends React.Component { ); } } +export default connect(mapStateToProps, mapDispatchToProps)(MovieList); diff --git a/src/public/js/components/shows/details.js b/src/public/js/components/shows/details.js index 81f0332..bb68de9 100644 --- a/src/public/js/components/shows/details.js +++ b/src/public/js/components/shows/details.js @@ -1,11 +1,20 @@ import React from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import * as actionCreators from '../../actions/actionCreators' import Loader from '../loader/loader' import DownloadButton from '../buttons/download' import { OverlayTrigger, Tooltip } from 'react-bootstrap' -export default class ShowDetails extends React.Component { +function mapStateToProps(state) { + return { showStore: state.showStore }; +} +const mapDispatchToProps = (dispatch) => + bindActionCreators(actionCreators, dispatch) + +class ShowDetails extends React.Component { componentWillMount() { this.props.fetchShowDetails(this.props.params.imdbId); } @@ -32,6 +41,7 @@ export default class ShowDetails extends React.Component { ); } } +export default connect(mapStateToProps, mapDispatchToProps)(ShowDetails); function Header(props){ return ( diff --git a/src/public/js/components/shows/list.js b/src/public/js/components/shows/list.js index 7434765..1323f58 100644 --- a/src/public/js/components/shows/list.js +++ b/src/public/js/components/shows/list.js @@ -1,10 +1,19 @@ import React from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import * as actionCreators from '../../actions/actionCreators' import ListDetails from '../list/details' import ListPosters from '../list/posters' import ShowButtons from './listButtons' -export default class ShowList extends React.Component { +function mapStateToProps(state) { + return { showStore: state.showStore }; +} +const mapDispatchToProps = (dispatch) => + bindActionCreators(actionCreators, dispatch) + +class ShowList extends React.Component { componentWillMount() { if (this.props.showsUrl) { this.props.fetchShows(this.props.showsUrl); @@ -54,3 +63,4 @@ export default class ShowList extends React.Component { ); } } +export default connect(mapStateToProps, mapDispatchToProps)(ShowList); diff --git a/src/public/js/components/torrents/list.js b/src/public/js/components/torrents/list.js index 38a7998..cc791c0 100644 --- a/src/public/js/components/torrents/list.js +++ b/src/public/js/components/torrents/list.js @@ -6,8 +6,8 @@ import { addTorrent } from '../../actions/actionCreators' function mapStateToProps(state) { return { torrents: state.torrentStore.torrents }; } -const mapDispatchToProps = (dipatch) => - bindActionCreators({ addTorrent }, dipatch) +const mapDispatchToProps = (dispatch) => + bindActionCreators({ addTorrent }, dispatch) class TorrentList extends React.PureComponent { render() { From 7ff88c531f55eab1c1d8a291dc9b52f9fe150982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Sun, 21 May 2017 20:35:40 +0200 Subject: [PATCH 06/12] Update the navbar to a pure component --- src/public/js/components/navbar.js | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/public/js/components/navbar.js b/src/public/js/components/navbar.js index 8a7193e..56c1398 100644 --- a/src/public/js/components/navbar.js +++ b/src/public/js/components/navbar.js @@ -2,7 +2,7 @@ import React from 'react' import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from 'react-bootstrap' import { LinkContainer } from 'react-router-bootstrap' -export default class NavBar extends React.Component { +export default class NavBar extends React.PureComponent { constructor(props) { super(props); this.state = { @@ -11,25 +11,6 @@ export default class NavBar extends React.Component { displayShowsSearch: this.shouldDisplayShowsSearch(props.router), }; } - shouldComponentUpdate(nextProps, nextState) { - if (nextProps.username !== this.props.username) { - return true; - } - if (nextProps.torrentsCount !== this.props.torrentsCount) { - return true; - } - if (nextState.displayMoviesSearch !== this.state.displayMoviesSearch) { - return true; - } - if (nextState.displayShowsSearch !== this.state.displayShowsSearch) { - return true; - } - if (nextState.userLoggedIn !== this.state.userLoggedIn) { - return true; - } - - return false; - } componentWillReceiveProps(nextProps) { // Update the state based on the next props const shouldDisplayMoviesSearch = this.shouldDisplayMoviesSearch(nextProps.router); From d16c2742eec0b849ab0f929db37a5c0c3c8ab934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Mon, 22 May 2017 14:11:04 +0200 Subject: [PATCH 07/12] Add immutable as dependency --- package.json | 1 + yarn.lock | 119 +++++++++++++++++++++++++-------------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index cbcf306..867633a 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "font-awesome": "^4.7.0", "fuzzy": "^0.1.3", "history": "^4.4.0", + "immutable": "^3.8.1", "jquery": "^2.2.4", "jwt-decode": "^2.1.0", "react": "^15.3.2", diff --git a/yarn.lock b/yarn.lock index 4574179..a71f73c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,11 +1,5 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 - - -Base64@~0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028" - abbrev@1: version "1.0.9" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" @@ -741,6 +735,10 @@ base64-js@^1.0.2: version "1.2.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" +Base64@~0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028" + bcrypt-pbkdf@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4" @@ -1541,12 +1539,6 @@ glob-watcher@^0.0.6: dependencies: gaze "^0.5.1" -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - dependencies: - find-index "^0.1.1" - glob@^4.3.1: version "4.5.3" resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" @@ -1575,6 +1567,12 @@ glob@~3.1.21: inherits "1" minimatch "~0.2.11" +glob2base@^0.0.12: + version "0.0.12" + resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" + dependencies: + find-index "^0.1.1" + global-modules@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" @@ -1825,7 +1823,7 @@ image-size@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.0.tgz#be7aed1c37b5ac3d9ba1d66a24b4c47ff8397651" -immutable@^3.7.6: +immutable, immutable@^3.7.6: version "3.8.1" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" @@ -1850,14 +1848,14 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" +inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1, inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + inherits@1: version "1.0.2" resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" -inherits@2, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" @@ -2032,14 +2030,14 @@ is-windows@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c" +isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - isexe@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" @@ -2182,7 +2180,7 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@~0.2.5: +loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@~0.2.5, loader-utils@0.2.x: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -2419,14 +2417,14 @@ mime-types@^2.1.12, mime-types@~2.1.7: dependencies: mime-db "~1.24.0" -mime@1.2.x: - version "1.2.11" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" - mime@^1.2.11: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +mime@1.2.x: + version "1.2.11" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" + minimatch@^2.0.1: version "2.0.10" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" @@ -2446,15 +2444,15 @@ minimatch@~0.2.11: lru-cache "2" sigmund "~1.0.0" -minimist@0.0.8, minimist@~0.0.1: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +minimist@~0.0.1, minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3008,14 +3006,14 @@ prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + q@^1.1.2: version "1.4.1" resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" @@ -3160,15 +3158,6 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@>=1.0.33-1 <1.1.0-0": - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@^1.0.27-1, readable-stream@^1.1.13, readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -3201,6 +3190,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@~2.0.0: string_decoder "~0.10.x" util-deprecate "~1.0.1" +"readable-stream@>=1.0.33-1 <1.1.0-0": + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readdirp@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" @@ -3361,7 +3359,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@~2.5.1, rimraf@~2.5.4: +rimraf@^2.2.8, rimraf@~2.5.1, rimraf@~2.5.4, rimraf@2: version "2.5.4" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" dependencies: @@ -3375,7 +3373,7 @@ sax@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" -"semver@2 || 3 || 4 || 5", semver@^4.1.0: +semver@^4.1.0, "semver@2 || 3 || 4 || 5": version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" @@ -3495,6 +3493,10 @@ strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" +string_decoder@~0.10.25, string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -3503,10 +3505,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string_decoder@~0.10.25, string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -3597,6 +3595,10 @@ tar@~2.2.1: fstream "^1.0.2" inherits "2" +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + through2@^0.6.1: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -3611,10 +3613,6 @@ through2@^2.0.0: readable-stream "~2.0.0" xtend "~4.0.0" -through@^2.3.8: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - tildify@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" @@ -3736,7 +3734,7 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3, util@~0.10.3: +util@~0.10.3, util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: @@ -3894,19 +3892,19 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0: +xtend@^4.0.0, "xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -3918,3 +3916,4 @@ yargs@~3.10.0: cliui "^2.1.0" decamelize "^1.0.0" window-size "0.1.0" + From c8b65f8da965c3ddfb65706f0cf0465afd18e83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Mon, 22 May 2017 14:11:50 +0200 Subject: [PATCH 08/12] Use an immutable store for torrents --- src/public/js/app.js | 2 +- src/public/js/components/torrents/list.js | 16 ++++++++-------- src/public/js/reducers/torrents.js | 20 ++++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/public/js/app.js b/src/public/js/app.js index 6fb6f4b..2948267 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -43,7 +43,7 @@ import getRoutes from './routes' function mapStateToProps(state) { return { username: state.userStore.username, - torrentCount: state.torrentStore.torrents.length, + torrentCount: state.torrentStore.get('torrents').size, alerts: state.alerts, } } diff --git a/src/public/js/components/torrents/list.js b/src/public/js/components/torrents/list.js index cc791c0..443fe9a 100644 --- a/src/public/js/components/torrents/list.js +++ b/src/public/js/components/torrents/list.js @@ -4,7 +4,7 @@ import { bindActionCreators } from 'redux' import { addTorrent } from '../../actions/actionCreators' function mapStateToProps(state) { - return { torrents: state.torrentStore.torrents }; + return { torrents: state.torrentStore.get('torrents') }; } const mapDispatchToProps = (dispatch) => bindActionCreators({ addTorrent }, dispatch) @@ -67,7 +67,7 @@ class AddTorrent extends React.PureComponent { class List extends React.PureComponent { render() { - if (this.props.torrents.length === 0) { + if (this.props.torrents.size === 0) { return (
@@ -96,23 +96,23 @@ class List extends React.PureComponent { class Torrent extends React.PureComponent { render() { - const done = this.props.data.is_finished; + const done = this.props.data.get('is_finished'); var progressStyle = 'progress-bar progress-bar-warning'; if (done) { progressStyle = 'progress-bar progress-bar-success'; } - var percentDone = this.props.data.percent_done; + var percentDone = this.props.data.get('percent_done'); const started = (percentDone !== 0); if (started) { percentDone = Number(percentDone).toFixed(1) + '%'; } - var downloadedSize = prettySize(this.props.data.downloaded_size); - var totalSize = prettySize(this.props.data.total_size); - var downloadRate = prettySize(this.props.data.download_rate) + "/s"; + var downloadedSize = prettySize(this.props.data.get('downloaded_size')); + var totalSize = prettySize(this.props.data.get('total_size')); + var downloadRate = prettySize(this.props.data.get('download_rate')) + "/s"; return (
-
{this.props.data.name}
+
{this.props.data.get('name')}
{started &&
diff --git a/src/public/js/reducers/torrents.js b/src/public/js/reducers/torrents.js index f085bbd..fa5586e 100644 --- a/src/public/js/reducers/torrents.js +++ b/src/public/js/reducers/torrents.js @@ -1,19 +1,19 @@ -const defaultState = { - fetching: false, - torrents: [], -}; +import { Map, List, fromJS } from 'immutable' -export default function showStore(state = defaultState, action) { +const defaultState = Map({ + 'fetching': false, + 'torrents': List(), +}); + +export default function torrentStore(state = defaultState, action) { switch (action.type) { case 'TORRENTS_FETCH_PENDING': - return Object.assign({}, state, { - fetching: true, - }) + return state.set('fetching', false); case 'TORRENTS_FETCH_FULFILLED': - return Object.assign({}, state, { + return state.merge(fromJS({ fetching: false, torrents: action.payload.data, - }) + })); default: return state } From 1abea1e0c222fe066e3a878abaed2dbe42918856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Tue, 23 May 2017 20:16:29 +0200 Subject: [PATCH 09/12] Only map the required props/funcs to the show view --- src/public/js/components/shows/details.js | 20 +++++++++++--------- src/public/js/routes.js | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/public/js/components/shows/details.js b/src/public/js/components/shows/details.js index bb68de9..e1faea5 100644 --- a/src/public/js/components/shows/details.js +++ b/src/public/js/components/shows/details.js @@ -1,7 +1,8 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import * as actionCreators from '../../actions/actionCreators' +import { addTorrent, addShowToWishlist, deleteFromWishlist, + updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails } from '../../actions/actionCreators' import Loader from '../loader/loader' import DownloadButton from '../buttons/download' @@ -9,29 +10,30 @@ import DownloadButton from '../buttons/download' import { OverlayTrigger, Tooltip } from 'react-bootstrap' function mapStateToProps(state) { - return { showStore: state.showStore }; + return { + loading: state.showStore.loading, + show: state.showStore.show, + }; } const mapDispatchToProps = (dispatch) => - bindActionCreators(actionCreators, dispatch) + bindActionCreators({addTorrent, addShowToWishlist, deleteFromWishlist, + updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails }, dispatch) class ShowDetails extends React.Component { - componentWillMount() { - this.props.fetchShowDetails(this.props.params.imdbId); - } render() { // Loading - if (this.props.showStore.loading) { + if (this.props.loading) { return (); } return (
Date: Tue, 23 May 2017 21:12:23 +0200 Subject: [PATCH 10/12] Only map the required props/funcs to the movie list view --- src/public/js/components/movies/list.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 3056dd8..6609a88 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -1,7 +1,8 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import * as actionCreators from '../../actions/actionCreators' +import { selectMovie, getMovieDetails, addTorrent, + addMovieToWishlist, deleteMovieFromWishlist } from '../../actions/actionCreators' import DownloadButton from '../buttons/download' import TorrentsButton from './torrents' @@ -13,7 +14,8 @@ function mapStateToProps(state) { return { movieStore: state.movieStore }; } const mapDispatchToProps = (dipatch) => - bindActionCreators(actionCreators, dipatch) + bindActionCreators({ selectMovie, getMovieDetails, addTorrent, + addMovieToWishlist, deleteMovieFromWishlist }, dipatch) function MovieButtons(props) { const imdb_link = `http://www.imdb.com/title/${props.movie.imdb_id}`; @@ -73,9 +75,7 @@ class MovieList extends React.Component { formModel="movieStore" filterControlModel="movieStore.filter" filterControlPlaceHolder="Filter movies..." - fetchExploreOptions={this.props.getMovieExploreOptions} exploreOptions={this.props.movieStore.exploreOptions} - explore={this.props.exploreMovies} selectedImdbId={selectedMovieId} filter={this.props.movieStore.filter} perPage={this.props.movieStore.perPage} @@ -94,7 +94,6 @@ class MovieList extends React.Component { deleteMovie={this.props.deleteMovie} addToWishlist={this.props.addMovieToWishlist} deleteFromWishlist={this.props.deleteMovieFromWishlist} - fetchMovies={this.props.fetchMovies} lastFetchUrl={this.props.movieStore.lastFetchUrl} /> From 1d1e239709a802db1d724978603163fdcce4d1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Tue, 23 May 2017 21:16:10 +0200 Subject: [PATCH 11/12] Only map the required props/funcs to the show list view --- src/public/js/components/shows/list.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/public/js/components/shows/list.js b/src/public/js/components/shows/list.js index 1323f58..bca5c00 100644 --- a/src/public/js/components/shows/list.js +++ b/src/public/js/components/shows/list.js @@ -1,7 +1,8 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import * as actionCreators from '../../actions/actionCreators' +import { selectShow, addShowToWishlist, + deleteFromWishlist, getShowDetails } from '../../actions/actionCreators' import ListDetails from '../list/details' import ListPosters from '../list/posters' @@ -11,15 +12,10 @@ function mapStateToProps(state) { return { showStore: state.showStore }; } const mapDispatchToProps = (dispatch) => - bindActionCreators(actionCreators, dispatch) + bindActionCreators({ selectShow, addShowToWishlist, + deleteFromWishlist, getShowDetails }, dispatch) class ShowList extends React.Component { - componentWillMount() { - if (this.props.showsUrl) { - this.props.fetchShows(this.props.showsUrl); - return - } - } render() { const shows = this.props.showStore.shows; const selectedShowId = this.props.showStore.selectedImdbId; @@ -37,9 +33,7 @@ class ShowList extends React.Component { formModel="showStore" filterControlModel="showStore.filter" filterControlPlaceHolder="Filter shows..." - fetchExploreOptions={this.props.getShowExploreOptions} exploreOptions={this.props.showStore.exploreOptions} - explore={this.props.exploreShows} selectedImdbId={selectedShowId} filter={this.props.showStore.filter} perPage={this.props.showStore.perPage} From 9ba4af9d7d3e1064245588269d037b36f9ac2378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Tue, 23 May 2017 22:07:19 +0200 Subject: [PATCH 12/12] Split actions to separate file Only use the necessary actions for each component --- src/public/js/actions/actionCreators.js | 293 ---------------------- src/public/js/actions/alerts.js | 23 ++ src/public/js/actions/movies.js | 86 +++++++ src/public/js/actions/shows.js | 106 ++++++++ src/public/js/actions/torrents.js | 22 ++ src/public/js/actions/users.js | 46 ++++ src/public/js/app.js | 8 +- src/public/js/components/movies/list.js | 5 +- src/public/js/components/shows/details.js | 5 +- src/public/js/components/shows/list.js | 2 +- src/public/js/components/torrents/list.js | 2 +- src/public/js/components/users/edit.js | 22 +- src/public/js/components/users/login.js | 21 +- src/public/js/components/users/signup.js | 10 +- src/public/js/routes.js | 38 +-- 15 files changed, 359 insertions(+), 330 deletions(-) delete mode 100644 src/public/js/actions/actionCreators.js create mode 100644 src/public/js/actions/alerts.js create mode 100644 src/public/js/actions/movies.js create mode 100644 src/public/js/actions/shows.js create mode 100644 src/public/js/actions/torrents.js create mode 100644 src/public/js/actions/users.js diff --git a/src/public/js/actions/actionCreators.js b/src/public/js/actions/actionCreators.js deleted file mode 100644 index b0eef51..0000000 --- a/src/public/js/actions/actionCreators.js +++ /dev/null @@ -1,293 +0,0 @@ -import { configureAxios, request } from '../requests' - -// ====================== -// Alerts -// ====================== - -export function addAlertError(message) { - return { - type: 'ADD_ALERT_ERROR', - payload: { - message, - } - } -} - -export function addAlertOk(message) { - return { - type: 'ADD_ALERT_OK', - payload: { - message, - } - } -} - -export function dismissAlert() { - return { - type: 'DISMISS_ALERT', - } -} - -// ====================== -// Users -// ====================== - -export function userLogout() { - return { - type: 'USER_LOGOUT', - } -} - -export function loginUser(username, password) { - return request( - 'USER_LOGIN', - configureAxios().post( - '/users/login', - { - username: username, - password: password, - }, - ), - ) -} - -export function updateUser(config) { - return request( - 'USER_UPDATE', - configureAxios().post('/users/edit', config), - [ - addAlertOk("User updated"), - ], - ) -} - -export function userSignUp(config) { - return request( - 'USER_SIGNUP', - configureAxios().post('/users/signup', config) - ) -} - -export function getUserInfos() { - return request( - 'GET_USER', - configureAxios().get('/users/details') - ) -} - -// ====================== -// Movies -// ====================== - -export function updateLastMovieFetchUrl(url) { - return { - type: 'UPDATE_LAST_MOVIE_FETCH_URL', - payload: { - url: url, - }, - } -} - -export function selectMovie(imdbId) { - return { - type: 'SELECT_MOVIE', - imdbId - } -} - -export function getMovieExploreOptions() { - return request( - 'MOVIE_GET_EXPLORE_OPTIONS', - configureAxios().get('/movies/explore/options') - ) -} - -export function getMovieDetails(imdbId) { - return request( - 'MOVIE_GET_DETAILS', - configureAxios().post(`/movies/${imdbId}/refresh`) - ) -} - -export function deleteMovie(imdbId, lastFetchUrl) { - return request( - 'MOVIE_DELETE', - configureAxios().delete(`/movies/${imdbId}`), - [ - fetchMovies(lastFetchUrl), - addAlertOk("Movie deleted"), - ], - ) -} - -export function addMovieToWishlist(imdbId) { - return request( - 'MOVIE_ADD_TO_WISHLIST', - configureAxios().post(`/wishlist/movies/${imdbId}`), - [ - addAlertOk("Movie added to the wishlist"), - updateMovieWishlistStore(imdbId, true), - ], - ) -} - -export function deleteMovieFromWishlist(imdbId) { - return request( - 'MOVIE_DELETE_FROM_WISHLIST', - configureAxios().delete(`/wishlist/movies/${imdbId}`), - [ - addAlertOk("Movie deleted from the wishlist"), - updateMovieWishlistStore(imdbId, false), - ], - ) -} - -export function updateMovieWishlistStore(imdbId, wishlisted) { - return { - type: 'MOVIE_UPDATE_STORE_WISHLIST', - payload: { - imdbId, - wishlisted, - } - } -} - -export function fetchMovies(url) { - return request( - 'MOVIE_LIST_FETCH', - configureAxios().get(url), - [ - updateLastMovieFetchUrl(url), - ] - ) -} - -// ====================== -// Shows -// ====================== - -export function fetchShows(url) { - return request( - 'SHOW_LIST_FETCH', - configureAxios().get(url), - [ - updateLastShowsFetchUrl(url), - ] - ) -} - -export function getShowDetails(imdbId) { - return request( - 'SHOW_GET_DETAILS', - configureAxios().post(`/shows/${imdbId}/refresh`) - ) -} - - -export function getEpisodeDetails(imdbId, season, episode) { - return request( - 'EPISODE_GET_DETAILS', - configureAxios().post(`/shows/${imdbId}/seasons/${season}/episodes/${episode}`), - ) -} - -export function updateEpisodeDetailsStore(imdbId, season, episode) { - return { - type: 'EPISODE_GET_DETAILS', - payload: { - imdbId, - season, - episode, - }, - } -} - -export function fetchShowDetails(imdbId) { - return request( - 'SHOW_FETCH_DETAILS', - configureAxios().get(`/shows/${imdbId}`) - ) -} - -export function addShowToWishlist(imdbId, season = null, episode = null) { - return request( - 'SHOW_ADD_TO_WISHLIST', - configureAxios().post(`/wishlist/shows/${imdbId}`, { - season: season, - episode: episode, - }), - [ - addAlertOk("Show added to the wishlist"), - updateShowWishlistStore(imdbId, true, season, episode), - ], - ) -} - -export function deleteShowFromWishlist(imdbId) { - return request( - 'SHOW_DELETE_FROM_WISHLIST', - configureAxios().delete(`/wishlist/shows/${imdbId}`), - [ - addAlertOk("Show deleted from the wishlist"), - updateShowWishlistStore(imdbId, false), - ], - ) -} - -export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) { - return { - type: 'SHOW_UPDATE_STORE_WISHLIST', - payload: { - wishlisted: wishlisted, - imdbId, - season, - episode, - } - } -} - -export function getShowExploreOptions() { - return request( - 'SHOW_GET_EXPLORE_OPTIONS', - configureAxios().get('/shows/explore/options') - ) -} - -export function selectShow(imdbId) { - return { - type: 'SELECT_SHOW', - imdbId - } -} - -export function updateLastShowsFetchUrl(url) { - return { - type: 'UPDATE_LAST_SHOWS_FETCH_URL', - payload: { - url: url, - }, - } -} - -// ====================== -// AddTorrent -// ====================== - -export function addTorrent(url) { - return request( - 'ADD_TORRENT', - configureAxios().post('/torrents', { - url: url, - }), - [ - addAlertOk("Torrent added"), - ], - ) -} - -export function fetchTorrents() { - return request( - 'TORRENTS_FETCH', - configureAxios().get('/torrents') - ) -} diff --git a/src/public/js/actions/alerts.js b/src/public/js/actions/alerts.js new file mode 100644 index 0000000..83c1198 --- /dev/null +++ b/src/public/js/actions/alerts.js @@ -0,0 +1,23 @@ +export function addAlertError(message) { + return { + type: 'ADD_ALERT_ERROR', + payload: { + message, + } + } +} + +export function addAlertOk(message) { + return { + type: 'ADD_ALERT_OK', + payload: { + message, + } + } +} + +export function dismissAlert() { + return { + type: 'DISMISS_ALERT', + } +} diff --git a/src/public/js/actions/movies.js b/src/public/js/actions/movies.js new file mode 100644 index 0000000..e5e0d36 --- /dev/null +++ b/src/public/js/actions/movies.js @@ -0,0 +1,86 @@ +import { configureAxios, request } from '../requests' + +import { addAlertOk } from './alerts' + +export function updateLastMovieFetchUrl(url) { + return { + type: 'UPDATE_LAST_MOVIE_FETCH_URL', + payload: { + url: url, + }, + } +} + +export function selectMovie(imdbId) { + return { + type: 'SELECT_MOVIE', + imdbId + } +} + +export function getMovieExploreOptions() { + return request( + 'MOVIE_GET_EXPLORE_OPTIONS', + configureAxios().get('/movies/explore/options') + ) +} + +export function getMovieDetails(imdbId) { + return request( + 'MOVIE_GET_DETAILS', + configureAxios().post(`/movies/${imdbId}/refresh`) + ) +} + +export function deleteMovie(imdbId, lastFetchUrl) { + return request( + 'MOVIE_DELETE', + configureAxios().delete(`/movies/${imdbId}`), + [ + fetchMovies(lastFetchUrl), + addAlertOk("Movie deleted"), + ], + ) +} + +export function addMovieToWishlist(imdbId) { + return request( + 'MOVIE_ADD_TO_WISHLIST', + configureAxios().post(`/wishlist/movies/${imdbId}`), + [ + addAlertOk("Movie added to the wishlist"), + updateMovieWishlistStore(imdbId, true), + ], + ) +} + +export function deleteMovieFromWishlist(imdbId) { + return request( + 'MOVIE_DELETE_FROM_WISHLIST', + configureAxios().delete(`/wishlist/movies/${imdbId}`), + [ + addAlertOk("Movie deleted from the wishlist"), + updateMovieWishlistStore(imdbId, false), + ], + ) +} + +export function updateMovieWishlistStore(imdbId, wishlisted) { + return { + type: 'MOVIE_UPDATE_STORE_WISHLIST', + payload: { + imdbId, + wishlisted, + } + } +} + +export function fetchMovies(url) { + return request( + 'MOVIE_LIST_FETCH', + configureAxios().get(url), + [ + updateLastMovieFetchUrl(url), + ] + ) +} diff --git a/src/public/js/actions/shows.js b/src/public/js/actions/shows.js new file mode 100644 index 0000000..fc84fae --- /dev/null +++ b/src/public/js/actions/shows.js @@ -0,0 +1,106 @@ +import { configureAxios, request } from '../requests' + +import { addAlertOk } from './alerts' + +export function fetchShows(url) { + return request( + 'SHOW_LIST_FETCH', + configureAxios().get(url), + [ + updateLastShowsFetchUrl(url), + ] + ) +} + +export function getShowDetails(imdbId) { + return request( + 'SHOW_GET_DETAILS', + configureAxios().post(`/shows/${imdbId}/refresh`) + ) +} + + +export function getEpisodeDetails(imdbId, season, episode) { + return request( + 'EPISODE_GET_DETAILS', + configureAxios().post(`/shows/${imdbId}/seasons/${season}/episodes/${episode}`), + ) +} + +export function updateEpisodeDetailsStore(imdbId, season, episode) { + return { + type: 'EPISODE_GET_DETAILS', + payload: { + imdbId, + season, + episode, + }, + } +} + +export function fetchShowDetails(imdbId) { + return request( + 'SHOW_FETCH_DETAILS', + configureAxios().get(`/shows/${imdbId}`) + ) +} + +export function addShowToWishlist(imdbId, season = null, episode = null) { + return request( + 'SHOW_ADD_TO_WISHLIST', + configureAxios().post(`/wishlist/shows/${imdbId}`, { + season: season, + episode: episode, + }), + [ + addAlertOk("Show added to the wishlist"), + updateShowWishlistStore(imdbId, true, season, episode), + ], + ) +} + +export function deleteShowFromWishlist(imdbId) { + return request( + 'SHOW_DELETE_FROM_WISHLIST', + configureAxios().delete(`/wishlist/shows/${imdbId}`), + [ + addAlertOk("Show deleted from the wishlist"), + updateShowWishlistStore(imdbId, false), + ], + ) +} + +export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) { + return { + type: 'SHOW_UPDATE_STORE_WISHLIST', + payload: { + wishlisted: wishlisted, + imdbId, + season, + episode, + } + } +} + +export function getShowExploreOptions() { + return request( + 'SHOW_GET_EXPLORE_OPTIONS', + configureAxios().get('/shows/explore/options') + ) +} + +export function selectShow(imdbId) { + return { + type: 'SELECT_SHOW', + imdbId + } +} + +export function updateLastShowsFetchUrl(url) { + return { + type: 'UPDATE_LAST_SHOWS_FETCH_URL', + payload: { + url: url, + }, + } +} diff --git a/src/public/js/actions/torrents.js b/src/public/js/actions/torrents.js new file mode 100644 index 0000000..e92880c --- /dev/null +++ b/src/public/js/actions/torrents.js @@ -0,0 +1,22 @@ +import { configureAxios, request } from '../requests' + +import { addAlertOk } from './alerts' + +export function addTorrent(url) { + return request( + 'ADD_TORRENT', + configureAxios().post('/torrents', { + url: url, + }), + [ + addAlertOk("Torrent added"), + ], + ) +} + +export function fetchTorrents() { + return request( + 'TORRENTS_FETCH', + configureAxios().get('/torrents') + ) +} diff --git a/src/public/js/actions/users.js b/src/public/js/actions/users.js new file mode 100644 index 0000000..76734e4 --- /dev/null +++ b/src/public/js/actions/users.js @@ -0,0 +1,46 @@ +import { configureAxios, request } from '../requests' + +import { addAlertOk } from './alerts' + +export function userLogout() { + return { + type: 'USER_LOGOUT', + } +} + +export function loginUser(username, password) { + return request( + 'USER_LOGIN', + configureAxios().post( + '/users/login', + { + username: username, + password: password, + }, + ), + ) +} + +export function updateUser(config) { + return request( + 'USER_UPDATE', + configureAxios().post('/users/edit', config), + [ + addAlertOk("User updated"), + ], + ) +} + +export function userSignUp(config) { + return request( + 'USER_SIGNUP', + configureAxios().post('/users/signup', config) + ) +} + +export function getUserInfos() { + return request( + 'GET_USER', + configureAxios().get('/users/details') + ) +} diff --git a/src/public/js/app.js b/src/public/js/app.js index 2948267..b7e7ac8 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -28,7 +28,7 @@ import { Router } from 'react-router' import { routerActions } from 'react-router-redux' // Action creators -import { dismissAlert } from './actions/actionCreators' +import { dismissAlert } from './actions/alerts' // Store import store, { history } from './store' @@ -41,9 +41,13 @@ import Alert from './components/alerts/alert' import getRoutes from './routes' function mapStateToProps(state) { + let torrentCount = 0; + if (state.torrentStore.has('torrents')) { + torrentCount = state.torrentStore.has('torrents').size; + } return { username: state.userStore.username, - torrentCount: state.torrentStore.get('torrents').size, + torrentCount: torrentCount, alerts: state.alerts, } } diff --git a/src/public/js/components/movies/list.js b/src/public/js/components/movies/list.js index 6609a88..91237c3 100644 --- a/src/public/js/components/movies/list.js +++ b/src/public/js/components/movies/list.js @@ -1,8 +1,9 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import { selectMovie, getMovieDetails, addTorrent, - addMovieToWishlist, deleteMovieFromWishlist } from '../../actions/actionCreators' +import { addTorrent } from '../../actions/torrents' +import { addMovieToWishlist, deleteMovieFromWishlist, + getMovieDetails, selectMovie } from '../../actions/movies' import DownloadButton from '../buttons/download' import TorrentsButton from './torrents' diff --git a/src/public/js/components/shows/details.js b/src/public/js/components/shows/details.js index e1faea5..bb39410 100644 --- a/src/public/js/components/shows/details.js +++ b/src/public/js/components/shows/details.js @@ -1,8 +1,9 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import { addTorrent, addShowToWishlist, deleteFromWishlist, - updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails } from '../../actions/actionCreators' +import { addTorrent } from '../../actions/torrents' +import { addShowToWishlist, deleteFromWishlist, getEpisodeDetails, + updateEpisodeDetailsStore, updateShowDetails } from '../../actions/shows' import Loader from '../loader/loader' import DownloadButton from '../buttons/download' diff --git a/src/public/js/components/shows/list.js b/src/public/js/components/shows/list.js index bca5c00..fb7452f 100644 --- a/src/public/js/components/shows/list.js +++ b/src/public/js/components/shows/list.js @@ -2,7 +2,7 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import { selectShow, addShowToWishlist, - deleteFromWishlist, getShowDetails } from '../../actions/actionCreators' + deleteFromWishlist, getShowDetails } from '../../actions/shows' import ListDetails from '../list/details' import ListPosters from '../list/posters' diff --git a/src/public/js/components/torrents/list.js b/src/public/js/components/torrents/list.js index 443fe9a..197c905 100644 --- a/src/public/js/components/torrents/list.js +++ b/src/public/js/components/torrents/list.js @@ -1,7 +1,7 @@ import React from 'react' import { connect } from 'react-redux' import { bindActionCreators } from 'redux' -import { addTorrent } from '../../actions/actionCreators' +import { addTorrent } from '../../actions/torrents' function mapStateToProps(state) { return { torrents: state.torrentStore.get('torrents') }; diff --git a/src/public/js/components/users/edit.js b/src/public/js/components/users/edit.js index 4e28e8b..4b6af6a 100644 --- a/src/public/js/components/users/edit.js +++ b/src/public/js/components/users/edit.js @@ -1,18 +1,25 @@ import React from 'react' -import { Control, Form } from 'react-redux-form'; +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' -export default class UserEdit extends React.Component { - componentWillMount() { - this.props.getUserInfos(); - } +import { Control, Form } from 'react-redux-form'; +import { updateUser } from '../../actions/users' + +function mapStateToProps(state) { + return { user: state.userStore }; +} +const mapDispatchToProps = (dispatch) => + bindActionCreators({ updateUser }, dispatch) + +class UserEdit extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit() { this.props.updateUser({ - 'polochon_url': this.props.userStore.polochonUrl, - 'polochon_token': this.props.userStore.polochonToken, + 'polochon_url': this.props.user.polochonUrl, + 'polochon_token': this.props.user.polochonToken, 'password': this.refs.newPassword.value, 'password_confirm': this.refs.newPasswordConfirm.value, }); @@ -58,3 +65,4 @@ export default class UserEdit extends React.Component { ); } } +export default connect(mapStateToProps, mapDispatchToProps)(UserEdit); diff --git a/src/public/js/components/users/login.js b/src/public/js/components/users/login.js index 4b36812..be24d46 100644 --- a/src/public/js/components/users/login.js +++ b/src/public/js/components/users/login.js @@ -1,12 +1,22 @@ import React from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' -export default class UserLoginForm extends React.Component { +import { loginUser } from '../../actions/users' + +function mapStateToProps(state) { + return { user: state.userStore }; +} +const mapDispatchToProps = (dispatch) => + bindActionCreators({ loginUser }, dispatch) + +class UserLoginForm extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } componentWillReceiveProps(nextProps) { - if (!nextProps.userStore.isLogged) { + if (!nextProps.user.isLogged) { return } if (!nextProps.location.query.redirect) { @@ -19,7 +29,7 @@ export default class UserLoginForm extends React.Component { } handleSubmit(e) { e.preventDefault(); - if (this.props.userStore.userLoading) { + if (this.props.user.userLoading) { return; } const username = this.refs.username.value; @@ -47,12 +57,12 @@ export default class UserLoginForm extends React.Component {

- {this.props.userStore.userLoading && + {this.props.user.userLoading && } - {this.props.userStore.userLoading || + {this.props.user.userLoading || }
@@ -64,3 +74,4 @@ export default class UserLoginForm extends React.Component { ); } } +export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm); diff --git a/src/public/js/components/users/signup.js b/src/public/js/components/users/signup.js index 9a781a2..ad8b1c6 100644 --- a/src/public/js/components/users/signup.js +++ b/src/public/js/components/users/signup.js @@ -1,6 +1,13 @@ import React from 'react' +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' -export default class UserSignUp extends React.Component { +import { userSignUp } from '../../actions/users' + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ userSignUp }, dispatch) + +class UserSignUp extends React.Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); @@ -46,3 +53,4 @@ export default class UserSignUp extends React.Component { ); } } +export default connect(null, mapDispatchToProps)(UserSignUp); diff --git a/src/public/js/routes.js b/src/public/js/routes.js index d9f903a..9355f9a 100644 --- a/src/public/js/routes.js +++ b/src/public/js/routes.js @@ -8,7 +8,11 @@ import UserEdit from './components/users/edit' import UserSignUp from './components/users/signup' import TorrentList from './components/torrents/list' -import * as actionCreators from './actions/actionCreators' +import { fetchTorrents } from './actions/torrents' +import { userLogout, getUserInfos } from './actions/users' +import { fetchMovies, getMovieExploreOptions } from './actions/movies' +import { fetchShows, fetchShowDetails, getShowExploreOptions } from './actions/shows' + import store from './store' function startPollingTorrents() { @@ -56,7 +60,7 @@ const loginCheck = function(nextState, replace, next, f = null) { if (!pollingTorrentsId) { // Fetch the torrents every 10s pollingTorrentsId = setInterval(function() { - store.dispatch(actionCreators.fetchTorrents()); + store.dispatch(fetchTorrents()); }, 10000); } } @@ -90,7 +94,9 @@ export default function getRoutes(App) { path: '/users/edit', component: UserEdit, onEnter: function(nextState, replace, next) { - loginCheck(nextState, replace, next); + loginCheck(nextState, replace, next, function() { + store.dispatch(getUserInfos()); + }); }, }, { @@ -101,7 +107,7 @@ export default function getRoutes(App) { clearInterval(pollingTorrentsId); pollingTorrentsId = null; } - store.dispatch(actionCreators.userLogout()); + store.dispatch(userLogout()); replace('/users/login'); next(); }, @@ -111,7 +117,7 @@ export default function getRoutes(App) { component: MovieList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchMovies(`/movies/search/${nextState.params.search}`)); + store.dispatch(fetchMovies(`/movies/search/${nextState.params.search}`)); }); }, }, @@ -120,7 +126,7 @@ export default function getRoutes(App) { component: MovieList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchMovies('/movies/polochon')); + store.dispatch(fetchMovies('/movies/polochon')); }); }, }, @@ -132,9 +138,9 @@ export default function getRoutes(App) { var state = store.getState(); // Fetch the explore options if (Object.keys(state.movieStore.exploreOptions).length === 0) { - store.dispatch(actionCreators.getMovieExploreOptions()); + store.dispatch(getMovieExploreOptions()); } - store.dispatch(actionCreators.fetchMovies( + store.dispatch(fetchMovies( `/movies/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}` )); }); @@ -145,7 +151,7 @@ export default function getRoutes(App) { component: MovieList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchMovies('/wishlist/movies')); + store.dispatch(fetchMovies('/wishlist/movies')); }); }, }, @@ -154,7 +160,7 @@ export default function getRoutes(App) { component: ShowList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.search}`)); + store.dispatch(fetchShows(`/shows/search/${nextState.params.search}`)); }); }, }, @@ -163,7 +169,7 @@ export default function getRoutes(App) { component: ShowList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows('/shows/polochon')); + store.dispatch(fetchShows('/shows/polochon')); }); }, }, @@ -172,7 +178,7 @@ export default function getRoutes(App) { component: ShowList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShows('/wishlist/shows')); + store.dispatch(fetchShows('/wishlist/shows')); }); }, }, @@ -181,7 +187,7 @@ export default function getRoutes(App) { component: ShowDetails, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchShowDetails(nextState.params.imdbId)); + store.dispatch(fetchShowDetails(nextState.params.imdbId)); }); }, }, @@ -193,9 +199,9 @@ export default function getRoutes(App) { var state = store.getState(); // Fetch the explore options if (Object.keys(state.showStore.exploreOptions).length === 0) { - store.dispatch(actionCreators.getShowExploreOptions()); + store.dispatch(getShowExploreOptions()); } - store.dispatch(actionCreators.fetchShows( + store.dispatch(fetchShows( `/shows/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}` )); }); @@ -206,7 +212,7 @@ export default function getRoutes(App) { component: TorrentList, onEnter: function(nextState, replace, next) { loginCheck(nextState, replace, next, function() { - store.dispatch(actionCreators.fetchTorrents()); + store.dispatch(fetchTorrents()); }); }, },