Add explore options

This commit is contained in:
Grégoire Delattre 2017-04-14 22:36:35 +02:00
parent 2811c4fa16
commit 04ef01995e
10 changed files with 290 additions and 52 deletions

View File

@ -92,6 +92,13 @@ export function selectMovie(imdbId) {
} }
} }
export function getMovieExploreOptions() {
return request(
'MOVIE_GET_EXPLORE_OPTIONS',
configureAxios().get('/movies/explore/options')
)
}
export function deleteMovieFromStore(imdbId) { export function deleteMovieFromStore(imdbId) {
return { return {
type: 'DELETE_MOVIE', type: 'DELETE_MOVIE',
@ -106,6 +113,13 @@ export function searchMovies(search) {
) )
} }
export function exploreMovies(source, category) {
return request(
'EXPLORE_MOVIES',
configureAxios().get(`/movies/explore?source=${encodeURI(source)}&category=${encodeURI(category)}`)
)
}
export function getMovieDetails(imdbId) { export function getMovieDetails(imdbId) {
return request( return request(
'MOVIE_GET_DETAILS', 'MOVIE_GET_DETAILS',
@ -251,6 +265,20 @@ export function updateShowWishlistStore(imdbId, wishlisted, season = null, episo
} }
} }
export function exploreShows(source, category) {
return request(
'EXPLORE_SHOWS',
configureAxios().get(`/shows/explore?source=${encodeURI(source)}&category=${encodeURI(category)}`)
)
}
export function getShowExploreOptions() {
return request(
'SHOW_GET_EXPLORE_OPTIONS',
configureAxios().get('/shows/explore/options')
)
}
export function selectShow(imdbId) { export function selectShow(imdbId) {
return { return {
type: 'SELECT_SHOW', type: 'SELECT_SHOW',

View File

@ -37,7 +37,7 @@ import UserSignUp from './components/users/signup'
class Main extends React.Component { class Main extends React.Component {
componentWillMount() { componentWillMount() {
this.props.isUserLoggedIn() this.props.isUserLoggedIn();
} }
render() { render() {
return ( return (
@ -77,52 +77,37 @@ const UserIsAuthenticated = UserAuthWrapper({
}) })
// TODO find a better way // TODO find a better way
const MovieListPopular = (props) => (
<MovieList {...props} moviesUrl='/movies/explore'/>
)
const MovieListPolochon = (props) => ( const MovieListPolochon = (props) => (
<MovieList {...props} moviesUrl='/movies/polochon'/> <MovieList {...props} moviesUrl='/movies/polochon'/>
) )
const MovieListWishlisted = (props) => ( const MovieListWishlisted = (props) => (
<MovieList {...props} moviesUrl='/wishlist/movies'/> <MovieList {...props} moviesUrl='/wishlist/movies'/>
) )
const MovieListSearch = (props) => (
<MovieList {...props} />
)
const ShowListPopular = (props) => (
<ShowList {...props} showsUrl='/shows/explore'/>
)
const ShowListPolochon = (props) => ( const ShowListPolochon = (props) => (
<ShowList {...props} showsUrl='/shows/polochon'/> <ShowList {...props} showsUrl='/shows/polochon'/>
) )
const ShowListWishlisted = (props) => ( const ShowListWishlisted = (props) => (
<MovieList {...props} moviesUrl='/wishlist/shows'/> <MovieList {...props} moviesUrl='/wishlist/shows'/>
) )
const ShowDetailsView = (props) => (
<ShowDetails {...props} />
)
const ShowListSearch = (props) => (
<ShowList {...props} />
)
ReactDOM.render(( ReactDOM.render((
<Provider store={store}> <Provider store={store}>
<Router history={history}> <Router history={history}>
<Route path="/" component={App}> <Route path="/" component={App}>
<IndexRedirect to="/movies/polochon" /> <IndexRedirect to="/movies/explore/yts/seeds" />
<Route path="/users/login" component={UserLoginForm} /> <Route path="/users/login" component={UserLoginForm} />
<Route path="/users/signup" component={UserSignUp} /> <Route path="/users/signup" component={UserSignUp} />
<Route path="/users/edit" component={UserIsAuthenticated(UserEdit)} /> <Route path="/users/edit" component={UserIsAuthenticated(UserEdit)} />
<Route path="/movies/search/:search" component={UserIsAuthenticated(MovieListSearch)} /> <Route path="/movies/search/:search" component={UserIsAuthenticated(MovieList)} />
<Route path="/movies/popular" component={UserIsAuthenticated(MovieListPopular)} />
<Route path="/movies/polochon" component={UserIsAuthenticated(MovieListPolochon)} /> <Route path="/movies/polochon" component={UserIsAuthenticated(MovieListPolochon)} />
<Route path="/movies/explore/:source/:category" component={UserIsAuthenticated(MovieList)} />
<Route path="/movies/wishlist" component={UserIsAuthenticated(MovieListWishlisted)} /> <Route path="/movies/wishlist" component={UserIsAuthenticated(MovieListWishlisted)} />
<Route path="/shows/search/:search" component={UserIsAuthenticated(ShowListSearch)} /> <Route path="/shows/search/:search" component={UserIsAuthenticated(ShowList)} />
<Route path="/shows/popular" component={UserIsAuthenticated(ShowListPopular)} />
<Route path="/shows/polochon" component={UserIsAuthenticated(ShowListPolochon)} /> <Route path="/shows/polochon" component={UserIsAuthenticated(ShowListPolochon)} />
<Route path="/shows/wishlist" component={UserIsAuthenticated(ShowListWishlisted)} /> <Route path="/shows/wishlist" component={UserIsAuthenticated(ShowListWishlisted)} />
<Route path="/shows/details/:imdbId" component={UserIsAuthenticated(ShowDetailsView)} /> <Route path="/shows/details/:imdbId" component={UserIsAuthenticated(ShowDetails)} />
<Route path="/shows/explore/:source/:category" component={UserIsAuthenticated(ShowList)} />
</Route> </Route>
</Router> </Router>
</Provider> </Provider>

View File

@ -0,0 +1,127 @@
import React from 'react'
import { Form, FormGroup, FormControl, ControlLabel } from 'react-bootstrap'
export default class ExplorerOptions extends React.Component {
constructor(props) {
super(props);
let source = null;
let category = null;
let categories = [];
// Check if the options are present
if (Object.keys(this.props.options).length === 0) {
// Fetch options
this.props.fetchOptions();
// Explore
this.props.explore(this.props.params.source, this.props.params.category);
} else {
source = this.props.params.source;
category = this.props.params.category;
categories = this.props.options[this.props.params.source];
}
this.state = {
selectedSource: source,
selectedCategory: category,
categories: categories,
};
this.handleSourceChange = this.handleSourceChange.bind(this);
this.handleCategoryChange = this.handleCategoryChange.bind(this);
}
handleSourceChange(event) {
let source = event.target.value;
let category = this.props.options[event.target.value][0];
this.setState({
selectedSource: source,
selectedCategory: category,
categories: this.props.options[source],
});
this.props.router.push(`/${this.props.type}/explore/${source}/${category}`);
}
handleCategoryChange(event) {
this.setState({ selectedCategory: event.target.value });
this.props.router.push(`/${this.props.type}/explore/${this.state.selectedSource}/${event.target.value}`);
}
componentWillUpdate(nextProps, nextState) {
// Check props
if (!nextProps.params.source
|| !nextProps.params.category
|| (nextProps.params.source === "")
|| (nextProps.params.category === "")) {
return
}
// Explore params changed
if ((this.props.params.source !== nextProps.params.source)
|| (this.props.params.category !== nextProps.params.category)) {
this.props.explore(nextProps.params.source, nextProps.params.category);
}
// State must be updated
if ((this.state.selectedSource !== nextProps.params.source)
|| (this.state.selectedCategory !== nextProps.params.category)) {
this.setState({
selectedSource: nextProps.params.source,
selectedCategory: nextProps.params.category,
categories: nextProps.options[nextProps.params.source],
});
}
}
render() {
// Should this componennt be displayed
if (!this.props.display) {
return null;
}
// Options are not yet fetched
if (Object.keys(this.props.options).length === 0) {
return null;
}
// State is not yet set
if (!this.state.selectedSource && !this.state.selectedCategory) {
return null;
}
return (
<div className="row">
<div className="col-xs-12 col-md-12">
<Form>
<div className="row">
<div className="col-xs-12 col-md-6">
<FormGroup>
<ControlLabel>Source</ControlLabel>
<FormControl
bsClass="form-control input-sm"
componentClass="select"
onChange={this.handleSourceChange}
value={this.state.selectedSource}
>
{Object.keys(this.props.options).map(function(source) {
return (<option key={source} value={source}>{source}</option>)
})}
</FormControl>
</FormGroup>
</div>
<div className="col-xs-12 col-md-6">
<FormGroup>
<ControlLabel>Category</ControlLabel>
<FormControl
bsClass="form-control input-sm"
componentClass="select"
onChange={this.handleCategoryChange}
value={this.state.selectedCategory}
>
{this.state.categories.map(function(category) {
return (<option key={category} value={category}>{category}</option>)
}, this)}
</FormControl>
</FormGroup>
</div>
</div>
</Form>
</div>
</div>
);
}
}

View File

@ -2,6 +2,10 @@ import React from 'react'
import { Control, Form } from 'react-redux-form'; import { Control, Form } from 'react-redux-form';
export default function ListFilter(props) { export default function ListFilter(props) {
if (!props.display) {
return null;
}
if (props.listSize === 0) { if (props.listSize === 0) {
return null; return null;
} }

View File

@ -4,6 +4,7 @@ import fuzzy from 'fuzzy';
import InfiniteScroll from 'react-infinite-scroller'; import InfiniteScroll from 'react-infinite-scroller';
import ListFilter from './filter' import ListFilter from './filter'
import ExplorerOptions from './explorerOptions'
import ListPoster from './poster' import ListPoster from './poster'
export default class ListPosters extends React.Component { export default class ListPosters extends React.Component {
@ -49,13 +50,33 @@ export default class ListPosters extends React.Component {
elmts = elmts.slice(0, this.state.items); elmts = elmts.slice(0, this.state.items);
} }
// Chose when to display filter / explore options
let displayFilter = true;
if (this.props.params
&& this.props.params.category
&& this.props.params.category !== ""
&& this.props.params.source
&& this.props.params.source !== "") {
displayFilter = false;
}
return ( return (
<div className={colSize}> <div className={colSize}>
<ListFilter <ListFilter
listSize={listSize} listSize={listSize}
display={displayFilter}
formModel={this.props.formModel} formModel={this.props.formModel}
controlModel={this.props.controlModel} controlModel={this.props.filterControlModel}
controlPlaceHolder={this.props.controlPlaceHolder} controlPlaceHolder={this.props.filterControlPlaceHolder}
/>
<ExplorerOptions
type={this.props.type}
display={!displayFilter}
params={this.props.params}
router={this.props.router}
fetchOptions={this.props.fetchExploreOptions}
options={this.props.exploreOptions}
explore={this.props.explore}
/> />
{elmts.length === 0 && {elmts.length === 0 &&
<div className="jumbotron"> <div className="jumbotron">

View File

@ -41,25 +41,42 @@ function MovieButtons(props) {
} }
export default class MovieList extends React.Component { export default class MovieList extends React.Component {
componentWillMount() { handleParams(props = this.props) {
if (this.props.moviesUrl) { // Check if the URL to fetch is in the props
this.props.fetchMovies(this.props.moviesUrl); if (props.moviesUrl) {
} else if (this.props.params && this.props.params.search != "") { this.props.fetchMovies(props.moviesUrl);
this.props.searchMovies({ return
key: this.props.params.search
});
} }
// Check for URL parameters
if (!props.params) {
return
}
// Search param
if (props.params.search && props.params.search !== "") {
props.searchMovies({
key: props.params.search
});
return
}
}
componentWillMount() {
this.handleParams();
} }
componentWillUpdate(nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
if (!nextProps.params || nextProps.params.search === "") { // No params
if (!nextProps.params) {
return return
} }
if (this.props.params.search === nextProps.params.search) {
// Search field changed
if (this.props.params.search
&& (this.props.params.search !== nextProps.params.search)
&& (nextProps.params.search !== "")) {
this.handleParams(nextProps);
return return
} }
this.props.searchMovies({
key: nextProps.params.search
});
} }
render() { render() {
const movies = this.props.movieStore.movies; const movies = this.props.movieStore.movies;
@ -79,14 +96,19 @@ export default class MovieList extends React.Component {
<div className="row" id="container"> <div className="row" id="container">
<ListPosters <ListPosters
data={movies} data={movies}
type="movies"
formModel="movieStore" formModel="movieStore"
controlModel="movieStore.filter" filterControlModel="movieStore.filter"
controlPlaceHolder="Filter movies..." filterControlPlaceHolder="Filter movies..."
fetchExploreOptions={this.props.getMovieExploreOptions}
exploreOptions={this.props.movieStore.exploreOptions}
explore={this.props.exploreMovies}
selectedImdbId={selectedMovieId} selectedImdbId={selectedMovieId}
filter={this.props.movieStore.filter} filter={this.props.movieStore.filter}
perPage={this.props.movieStore.perPage} perPage={this.props.movieStore.perPage}
onClick={this.props.selectMovie} onClick={this.props.selectMovie}
params={this.props.params} params={this.props.params}
router={this.props.router}
/> />
{selectedMovie && {selectedMovie &&
<ListDetails data={selectedMovie}> <ListDetails data={selectedMovie}>

View File

@ -85,8 +85,8 @@ function MoviesDropdown(props) {
return( return(
<Nav> <Nav>
<NavDropdown title="Movies" id="navbar-movies-dropdown"> <NavDropdown title="Movies" id="navbar-movies-dropdown">
<LinkContainer to="/movies/popular"> <LinkContainer to="/movies/explore/yts/seeds">
<MenuItem>Popular</MenuItem> <MenuItem>Explore</MenuItem>
</LinkContainer> </LinkContainer>
<LinkContainer to="/movies/polochon"> <LinkContainer to="/movies/polochon">
<MenuItem>Polochon</MenuItem> <MenuItem>Polochon</MenuItem>
@ -103,8 +103,8 @@ function ShowsDropdown(props) {
return( return(
<Nav> <Nav>
<NavDropdown title="Shows" id="navbar-shows-dropdown"> <NavDropdown title="Shows" id="navbar-shows-dropdown">
<LinkContainer to="/shows/popular"> <LinkContainer to="/shows/explore/eztv/popular">
<NavItem>Popular</NavItem> <NavItem>Explore</NavItem>
</LinkContainer> </LinkContainer>
<LinkContainer to="/shows/polochon"> <LinkContainer to="/shows/polochon">
<NavItem>Polochon</NavItem> <NavItem>Polochon</NavItem>

View File

@ -6,25 +6,42 @@ import Loader from '../loader/loader'
import ShowButtons from './listButtons' import ShowButtons from './listButtons'
export default class ShowList extends React.Component { export default class ShowList extends React.Component {
componentWillMount() { handleParams(props = this.props) {
if (this.props.showsUrl) { // Check if the URL to fetch is in the props
this.props.fetchShows(this.props.showsUrl); if (props.showsUrl) {
} else if (this.props.params && this.props.params.search != "") { this.props.fetchShows(props.showsUrl);
return
}
// Check for URL parameters
if (!props.params) {
return
}
// Search param
if (props.params.search && props.params.search !== "") {
this.props.searchShows({ this.props.searchShows({
key: this.props.params.search key: this.props.params.search
}); });
return
} }
} }
componentWillMount() {
this.handleParams();
}
componentWillUpdate(nextProps, nextState) { componentWillUpdate(nextProps, nextState) {
if (!nextProps.params || nextProps.params.search === "") { // No params
if (!nextProps.params) {
return return
} }
if (this.props.params.search === nextProps.params.search) {
// Search field changed
if (this.props.params.search
&& (this.props.params.search !== nextProps.params.search)
&& (nextProps.params.search !== "")) {
this.handleParams(nextProps);
return return
} }
this.props.searchShows({
key: nextProps.params.search
});
} }
render() { render() {
const shows = this.props.showStore.shows; const shows = this.props.showStore.shows;
@ -44,13 +61,19 @@ export default class ShowList extends React.Component {
<div className="row" id="container"> <div className="row" id="container">
<ListPosters <ListPosters
data={shows} data={shows}
type="shows"
formModel="showStore" formModel="showStore"
controlModel="showStore.filter" filterControlModel="showStore.filter"
controlPlaceHolder="Filter shows..." filterControlPlaceHolder="Filter shows..."
fetchExploreOptions={this.props.getShowExploreOptions}
exploreOptions={this.props.showStore.exploreOptions}
explore={this.props.exploreShows}
selectedImdbId={selectedShowId} selectedImdbId={selectedShowId}
filter={this.props.showStore.filter} filter={this.props.showStore.filter}
perPage={this.props.showStore.perPage} perPage={this.props.showStore.perPage}
onClick={this.props.selectShow} onClick={this.props.selectShow}
router={this.props.router}
params={this.props.params}
/> />
{selectedShow && {selectedShow &&
<ListDetails data={selectedShow} > <ListDetails data={selectedShow} >

View File

@ -5,6 +5,7 @@ const defaultState = {
perPage: 30, perPage: 30,
selectedImdbId: "", selectedImdbId: "",
fetchingDetails: false, fetchingDetails: false,
exploreOptions: {},
search: "", search: "",
}; };
@ -27,6 +28,15 @@ export default function movieStore(state = defaultState, action) {
selectedImdbId: selectedImdbId, selectedImdbId: selectedImdbId,
loading: false, loading: false,
}) })
case 'EXPLORE_MOVIES_PENDING':
return Object.assign({}, state, {
loading: true,
})
case 'EXPLORE_MOVIES_FULFILLED':
return Object.assign({}, state, {
movies: action.payload.data,
loading: false,
})
case 'SEARCH_MOVIES_PENDING': case 'SEARCH_MOVIES_PENDING':
return Object.assign({}, state, { return Object.assign({}, state, {
loading: true, loading: true,
@ -49,6 +59,10 @@ export default function movieStore(state = defaultState, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
movies: updateStoreWishlist(state.movies.slice(), action.payload.imdbId, action.payload.wishlisted), movies: updateStoreWishlist(state.movies.slice(), action.payload.imdbId, action.payload.wishlisted),
}) })
case 'MOVIE_GET_EXPLORE_OPTIONS_FULFILLED':
return Object.assign({}, state, {
exploreOptions: action.payload.data,
})
case 'DELETE_MOVIE': case 'DELETE_MOVIE':
return Object.assign({}, state, { return Object.assign({}, state, {
movies: state.movies.filter((e) => (e.imdb_id !== action.imdbId)), movies: state.movies.filter((e) => (e.imdb_id !== action.imdbId)),

View File

@ -9,6 +9,7 @@ const defaultState = {
}, },
search: "", search: "",
getDetails: false, getDetails: false,
exploreOptions: {},
}; };
export default function showStore(state = defaultState, action) { export default function showStore(state = defaultState, action) {
@ -63,6 +64,19 @@ export default function showStore(state = defaultState, action) {
shows: action.payload.data, shows: action.payload.data,
loading: false, loading: false,
}) })
case 'EXPLORE_SHOWS_PENDING':
return Object.assign({}, state, {
loading: true,
})
case 'EXPLORE_SHOWS_FULFILLED':
return Object.assign({}, state, {
shows: action.payload.data,
loading: false,
})
case 'SHOW_GET_EXPLORE_OPTIONS_FULFILLED':
return Object.assign({}, state, {
exploreOptions: action.payload.data,
})
case 'SHOW_UPDATE_STORE_WISHLIST': case 'SHOW_UPDATE_STORE_WISHLIST':
return Object.assign({}, state, { return Object.assign({}, state, {
shows: updateShowsStoreWishlist(state.shows.slice(), action.payload), shows: updateShowsStoreWishlist(state.shows.slice(), action.payload),