Add a detailed view of the seasons/episodes of a show
This commit is contained in:
parent
c98117c4f6
commit
421605bb38
@ -105,6 +105,13 @@ export function fetchShows(url) {
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_FETCH_DETAILS',
|
||||
configureAxios().get(`/shows/${imdbId}`)
|
||||
)
|
||||
}
|
||||
|
||||
export function selectShow(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_SHOW',
|
||||
|
@ -30,6 +30,7 @@ import NavBar from './components/navbar'
|
||||
import Error from './components/errors'
|
||||
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'
|
||||
@ -87,6 +88,10 @@ const ShowListPopular = (props) => (
|
||||
<ShowList {...props} showsUrl='/shows/explore'/>
|
||||
)
|
||||
|
||||
const ShowDetailsView = (props) => (
|
||||
<ShowDetails {...props} />
|
||||
)
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
@ -98,6 +103,7 @@ ReactDOM.render((
|
||||
<Route path="/movies/popular" component={UserIsAuthenticated(MovieListPopular)} />
|
||||
<Route path="/movies/polochon(/:page)" component={UserIsAuthenticated(MovieListPolochon)} />
|
||||
<Route path="/shows/popular" component={UserIsAuthenticated(ShowListPopular)} />
|
||||
<Route path="/shows/details/:imdbId" component={UserIsAuthenticated(ShowDetailsView)} />
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
|
@ -16,7 +16,7 @@ export default function ListDetails(props) {
|
||||
<i className="fa fa-star-o"></i>
|
||||
{props.data.rating} <small>({props.data.votes} counts)</small>
|
||||
</p>
|
||||
<p className="list-plot">{props.data.plot}</p>
|
||||
<p className="plot">{props.data.plot}</p>
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
|
145
src/public/js/components/shows/details.js
Normal file
145
src/public/js/components/shows/details.js
Normal file
@ -0,0 +1,145 @@
|
||||
import React from 'react'
|
||||
|
||||
export default class ShowDetails extends React.Component {
|
||||
componentWillMount() {
|
||||
this.props.fetchShowDetails(this.props.params.imdbId);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="row" id="container">
|
||||
<Header data={this.props.showStore.show} />
|
||||
<SeasonsList data={this.props.showStore.show} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Header(props){
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1">
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-body">
|
||||
<HeaderThumbnail data={props.data} />
|
||||
<HeaderDetails data={props.data} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeaderThumbnail(props){
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-2 text-center">
|
||||
<img src={props.data.poster_url} className="show-thumbnail thumbnail-selected img-thumbnail img-responsive"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeaderDetails(props){
|
||||
const imdbLink = `http://www.imdb.com/title/${props.data.imdb_id}`;
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-10">
|
||||
<dl className="dl-horizontal">
|
||||
<dt>Title</dt>
|
||||
<dd>{props.data.title}</dd>
|
||||
<dt>Plot</dt>
|
||||
<dd className="plot">{props.data.plot}</dd>
|
||||
<dt>IMDB</dt>
|
||||
<dd>
|
||||
<a type="button" className="btn btn-warning btn-xs" href={imdbLink}>
|
||||
<i className="fa fa-external-link"></i> Open in IMDB
|
||||
</a>
|
||||
</dd>
|
||||
<dt>Year</dt>
|
||||
<dd>{props.data.year}</dd>
|
||||
<dt>Rating</dt>
|
||||
<dd>{props.data.rating}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SeasonsList(props){
|
||||
return (
|
||||
<div>
|
||||
{props.data.seasons.length > 0 && props.data.seasons.map(function(season, index) {
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1" key={index}>
|
||||
<Season data={season} />
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
class Season extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.state = { colapsed: true };
|
||||
}
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
this.setState({ colapsed: !this.state.colapsed });
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading clickable" onClick={(e) => this.handleClick(e)}>
|
||||
Season {this.props.data.season}
|
||||
<small className="text-primary"> — ({this.props.data.episodes.length} episodes)</small>
|
||||
<span className="pull-right">
|
||||
{this.state.colapsed ||
|
||||
<i className="fa fa-chevron-down"></i>
|
||||
}
|
||||
{this.state.colapsed &&
|
||||
<i className="fa fa-chevron-left"></i>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
{this.state.colapsed ||
|
||||
<table className="table table-striped">
|
||||
<tbody>
|
||||
{this.props.data.episodes.map(function(episode, index) {
|
||||
let key = `${episode.season}-${episode.episode}`;
|
||||
return (
|
||||
<Episode key={key} data={episode} />
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function Episode(props) {
|
||||
return (
|
||||
<tr>
|
||||
<th scope="row" className="col-xs-1">{props.data.episode}</th>
|
||||
<td className="col-xs-8">{props.data.title}</td>
|
||||
<td className="col-xs-3">
|
||||
<span className="pull-right">
|
||||
{props.data.torrents && props.data.torrents.map(function(torrent, index) {
|
||||
let key = `${props.data.season}-${props.data.episode}-${torrent.source}-${torrent.quality}`;
|
||||
return (
|
||||
<Torrent data={torrent} key={key} />
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
function Torrent(props) {
|
||||
return (
|
||||
<span className="episode-button">
|
||||
<a type="button" className="btn btn-primary btn-xs" href={props.data.url}>
|
||||
<i className="fa fa-download"></i> {props.data.quality}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
}
|
@ -1,15 +1,19 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
import ListDetails from '../list/details'
|
||||
import ListPosters from '../list/posters'
|
||||
|
||||
function ShowButtons(props) {
|
||||
const imdb_link = `http://www.imdb.com/title/${props.show.imdb_id}`;
|
||||
const imdbLink = `http://www.imdb.com/title/${props.show.imdb_id}`;
|
||||
return (
|
||||
<div className="list-details-buttons btn-toolbar">
|
||||
<a type="button" className="btn btn-warning btn-sm" href={imdb_link}>
|
||||
<a type="button" className="btn btn-warning btn-sm" href={imdbLink}>
|
||||
<i className="fa fa-external-link"></i> IMDB
|
||||
</a>
|
||||
<Link type="button" className="btn btn-primary btn-sm" to={"/shows/details/" + props.show.imdb_id}>
|
||||
<i className="fa fa-external-link"></i> Details
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,9 @@ const defaultState = {
|
||||
filter: "",
|
||||
perPage: 30,
|
||||
selectedImdbId: "",
|
||||
show: {
|
||||
seasons: [],
|
||||
},
|
||||
};
|
||||
|
||||
export default function showStore(state = defaultState, action) {
|
||||
@ -17,6 +20,11 @@ export default function showStore(state = defaultState, action) {
|
||||
shows: action.payload.data,
|
||||
selectedImdbId: selectedImdbId,
|
||||
})
|
||||
case 'SHOW_FETCH_DETAILS_FULFILLED':
|
||||
return Object.assign({}, state, {
|
||||
show: sortEpisodes(action.payload.data),
|
||||
})
|
||||
return state;
|
||||
case 'SELECT_SHOW':
|
||||
// Don't select the show if we're fetching another show's details
|
||||
if (state.fetchingDetails) {
|
||||
@ -30,3 +38,47 @@ export default function showStore(state = defaultState, action) {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function sortEpisodes(show) {
|
||||
let episodes = show.episodes;
|
||||
delete show["episodes"];
|
||||
|
||||
if (episodes.length == 0) {
|
||||
return show;
|
||||
}
|
||||
|
||||
// Extract the seasons
|
||||
let seasons = {};
|
||||
for (let ep of episodes) {
|
||||
if (!seasons[ep.season]) {
|
||||
seasons[ep.season] = { episodes: [] };
|
||||
}
|
||||
seasons[ep.season].episodes.push(ep);
|
||||
}
|
||||
|
||||
if (seasons.length === 0) {
|
||||
return show;
|
||||
}
|
||||
|
||||
// Put all the season in an array
|
||||
let sortedSeasons = [];
|
||||
for (let season of Object.keys(seasons)) {
|
||||
let seasonEpisodes = seasons[season].episodes;
|
||||
// Order the episodes in each season
|
||||
seasonEpisodes.sort((a,b) => (a.episode - b.episode))
|
||||
// Add the season in the list
|
||||
sortedSeasons.push({
|
||||
season: season,
|
||||
episodes: seasonEpisodes,
|
||||
})
|
||||
}
|
||||
|
||||
// Order the seasons
|
||||
for (let i=0; i<sortedSeasons.length; i++) {
|
||||
sortedSeasons.sort((a,b) => (a.season - b.season))
|
||||
}
|
||||
|
||||
show.seasons = sortedSeasons;
|
||||
|
||||
return show;
|
||||
}
|
||||
|
@ -12,7 +12,11 @@ body {
|
||||
background-color: @brand-primary;
|
||||
}
|
||||
|
||||
.list-plot {
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plot {
|
||||
.text-justify;
|
||||
margin-right: 5%;
|
||||
}
|
||||
@ -27,6 +31,14 @@ body {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.show-thumbnail {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.episode-button {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user