Merge branch 'splitShowStore' into 'master'

Split show store

See merge request !75
This commit is contained in:
Lucas 2017-05-31 12:43:34 +00:00
commit 697997c0ae
9 changed files with 185 additions and 222 deletions

View File

@ -24,18 +24,13 @@ 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: {
null,
{
imdbId,
season,
episode,
},
}
)
}
export function fetchShowDetails(imdbId) {

View File

@ -9,21 +9,15 @@ export function refreshSubtitles(type, id, season, episode) {
return request(
'MOVIE_SUBTITLES_UPDATE',
configureAxios().post(`${resourceURL}/subtitles/refresh`),
[
addAlertOk("Subtitles refreshed"),
],
{
imdb_id: id,
},
null,
{ imdb_id: id },
)
case 'episode':
var resourceURL = `/shows/${id}/seasons/${season}/episodes/${episode}`
return request(
'EPISODE_SUBTITLES_UPDATE',
configureAxios().post(`${resourceURL}/subtitles/refresh`),
[
addAlertOk("Subtitles refreshed"),
],
null,
{
imdb_id: id,
season: season,

View File

@ -13,7 +13,7 @@ export default class SubtitlesButton extends React.Component {
return
}
// Refresh the subtitles
this.props.refreshSubtitles(this.props.type, this.props.resourceID, this.props.data.season, this.props.data.episode);
this.props.refreshSubtitles(this.props.type, this.props.resourceID, this.props.season, this.props.episode);
}
render() {
// If there is no URL, the resource is not in polochon, we won't be able to download subtitles

View File

@ -1,26 +1,27 @@
import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { toJS } from 'immutable'
import { addTorrent } from '../../actions/torrents'
import { refreshSubtitles } from '../../actions/subtitles'
import { addShowToWishlist, deleteFromWishlist, getEpisodeDetails,
updateEpisodeDetailsStore, updateShowDetails } from '../../actions/shows'
import { addShowToWishlist, deleteShowFromWishlist, getEpisodeDetails, updateShowDetails } from '../../actions/shows'
import Loader from '../loader/loader'
import DownloadButton from '../buttons/download'
import SubtitlesButton from '../buttons/subtitles'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
function mapStateToProps(state) {
return {
loading: state.showStore.loading,
show: state.showStore.show,
show: state.showStore.get('show'),
};
}
const mapDispatchToProps = (dispatch) =>
bindActionCreators({addTorrent, addShowToWishlist, deleteFromWishlist,
updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails,
bindActionCreators({addTorrent, addShowToWishlist, deleteShowFromWishlist,
updateShowDetails, getEpisodeDetails,
refreshSubtitles }, dispatch)
class ShowDetails extends React.Component {
@ -41,7 +42,6 @@ class ShowDetails extends React.Component {
addTorrent={this.props.addTorrent}
addToWishlist={this.props.addShowToWishlist}
getEpisodeDetails={this.props.getEpisodeDetails}
updateEpisodeDetailsStore={this.props.updateEpisodeDetailsStore}
refreshSubtitles={this.props.refreshSubtitles}
/>
</div>
@ -70,21 +70,21 @@ function Header(props){
function HeaderThumbnail(props){
return (
<div className="col-xs-12 col-sm-2 text-center">
<img src={props.data.poster_url}
<img src={props.data.get('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}`;
const imdbLink = `http://www.imdb.com/title/${props.data.get('imdb_id')}`;
return (
<div className="col-xs-12 col-sm-10">
<dl className="dl-horizontal">
<dt>Title</dt>
<dd>{props.data.title}</dd>
<dd>{props.data.get('title')}</dd>
<dt>Plot</dt>
<dd className="plot">{props.data.plot}</dd>
<dd className="plot">{props.data.get('plot')}</dd>
<dt>IMDB</dt>
<dd>
<a type="button" className="btn btn-warning btn-xs" href={imdbLink}>
@ -92,9 +92,9 @@ function HeaderDetails(props){
</a>
</dd>
<dt>Year</dt>
<dd>{props.data.year}</dd>
<dd>{props.data.get('year')}</dd>
<dt>Rating</dt>
<dd>{props.data.rating}</dd>
<dd>{props.data.get('rating')}</dd>
</dl>
<TrackHeader
data={props.data}
@ -108,19 +108,20 @@ function HeaderDetails(props){
function SeasonsList(props){
return (
<div>
{props.data.seasons.length > 0 && props.data.seasons.map(function(season, index) {
{props.data.get('seasons').entrySeq().map(function([season, data]) {
return (
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1" key={index}>
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1" key={season.toString()}>
<Season
data={season}
data={data}
season={season}
addTorrent={props.addTorrent}
addToWishlist={props.addToWishlist}
getEpisodeDetails={props.getEpisodeDetails}
updateEpisodeDetailsStore={props.updateEpisodeDetailsStore}
refreshSubtitles={props.refreshSubtitles}
/>
</div>
)
);
})}
</div>
)
@ -140,8 +141,8 @@ class Season extends React.Component {
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>
Season {this.props.season}
<small className="text-primary"> ({this.props.data.toList().size} episodes)</small>
<span className="pull-right">
{this.state.colapsed ||
<i className="fa fa-chevron-down"></i>
@ -154,8 +155,8 @@ class Season extends React.Component {
{this.state.colapsed ||
<table className="table table-striped table-hover">
<tbody>
{this.props.data.episodes.map(function(episode, index) {
let key = `${episode.season}-${episode.episode}`;
{this.props.data.toList().map(function(episode) {
let key = `${episode.get('season')}-${episode.get('episode')}`;
return (
<Episode
key={key}
@ -163,7 +164,6 @@ class Season extends React.Component {
addTorrent={this.props.addTorrent}
addToWishlist={this.props.addToWishlist}
getEpisodeDetails={this.props.getEpisodeDetails}
updateEpisodeDetailsStore={this.props.updateEpisodeDetailsStore}
refreshSubtitles={this.props.refreshSubtitles}
/>
)
@ -177,6 +177,12 @@ class Season extends React.Component {
}
function Episode(props) {
// TODO: remove this when everything uses immutable
let subtitles;
if (props.data.has('subtitles') && props.data.get('subtitles')) {
subtitles = props.data.get('subtitles').toJS();
}
return (
<tr>
<th scope="row" className="col-xs-2">
@ -184,23 +190,24 @@ function Episode(props) {
data={props.data}
addToWishlist={props.addToWishlist}
/>
{props.data.episode}
{props.data.get('episode')}
</th>
<td className="col-xs-12">
{props.data.title}
{props.data.get('title')}
<span className="pull-right episode-buttons">
<SubtitlesButton
url={props.data.polochon_url}
subtitles={props.data.subtitles}
url={props.data.get('polochon_url')}
subtitles={subtitles}
refreshSubtitles={props.refreshSubtitles}
resourceID={props.data.show_imdb_id}
data={props.data}
resourceID={props.data.get('show_imdb_id')}
season={props.data.get('season')}
episode={props.data.get('episode')}
type="episode"
xs
/>
{props.data.torrents && props.data.torrents.map(function(torrent, index) {
let key = `${props.data.season}-${props.data.episode}-${torrent.source}-${torrent.quality}`;
{props.data.get('torrents') && props.data.get('torrents').toList().map(function(torrent) {
let key = `${props.data.get('season')}-${props.data.get('episode')}-${torrent.get('source')}-${torrent.get('quality')}`;
return (
<Torrent
data={torrent}
@ -210,14 +217,13 @@ function Episode(props) {
)
})}
<DownloadButton
url={props.data.polochon_url}
subtitles={props.data.subtitles}
url={props.data.get('polochon_url')}
subtitles={subtitles}
xs
/>
<GetDetailsButton
data={props.data}
getEpisodeDetails={props.getEpisodeDetails}
updateEpisodeDetailsStore={props.updateEpisodeDetailsStore}
/>
</span>
</td>
@ -239,9 +245,9 @@ class Torrent extends React.Component {
<span>
<a type="button"
className="btn btn-primary btn-xs"
onClick={(e) => this.handleClick(e, this.props.data.url)}
onClick={(e) => this.handleClick(e, this.props.data.get('url'))}
href={this.props.data.url} >
<i className="fa fa-download"></i> {this.props.data.quality}
<i className="fa fa-download"></i> {this.props.data.get('quality')}
</a>
</span>
)
@ -255,28 +261,30 @@ class TrackHeader extends React.Component {
}
handleClick(e, url) {
e.preventDefault();
let wishlisted = (this.props.data.tracked_season !== null && this.props.data.tracked_episode !== null);
const trackedSeason = this.props.data.get('tracked_season');
const trackedEpisode = this.props.data.get('tracked_episode');
const imdbId = this.props.data.get('imdb_id');
const wishlisted = (trackedSeason !== null && trackedEpisode !== null);
if (wishlisted) {
this.props.deleteFromWishlist(this.props.data.imdb_id);
this.props.deleteFromWishlist(imdbId);
} else {
this.props.addToWishlist(this.props.data.imdb_id);
this.props.addToWishlist(imdbId);
}
}
render() {
let wishlisted = (this.props.data.tracked_season !== null && this.props.data.tracked_episode !== null);
if (wishlisted) {
const trackedSeason = this.props.data.get('tracked_season');
const trackedEpisode = this.props.data.get('tracked_episode');
const imdbId = this.props.data.get('imdb_id');
const wishlisted = (trackedSeason !== null && trackedEpisode !== null);
let msg;
if (this.props.data.tracked_season !== 0 && this.props.data.tracked_episode !== 0) {
if (wishlisted) {
if (trackedSeason !== 0 && trackedEpisode !== 0) {
msg = (
<dd>
Show tracked from <strong>season {this.props.data.tracked_season} episode {this.props.data.tracked_episode}</strong>
</dd>
<dd>Show tracked from <strong>season {trackedSeason} episode {trackedEpisode}</strong></dd>
);
} else {
msg = (
<dd>
Whole show tracked
</dd>
<dd>Whole show tracked</dd>
);
}
return (
@ -313,7 +321,10 @@ class TrackButton extends React.Component {
}
handleClick(e, url) {
e.preventDefault();
this.props.addToWishlist(this.props.data.show_imdb_id, this.props.data.season, this.props.data.episode);
const imdbId = this.props.data.get('show_imdb_id');
const season = this.props.data.get('season');
const episode = this.props.data.get('episode');
this.props.addToWishlist(imdbId, season, episode);
}
render() {
const tooltipId = `tooltip-${this.props.data.season}-${this.props.data.episode}`;
@ -337,22 +348,23 @@ class GetDetailsButton extends React.Component {
}
handleClick(e, url) {
e.preventDefault();
if (this.props.data.fetching) {
if (this.props.data.get('fetching')) {
return
}
this.props.updateEpisodeDetailsStore(this.props.data.show_imdb_id, this.props.data.season, this.props.data.episode);
console.log(this.props.data);
this.props.getEpisodeDetails(this.props.data.show_imdb_id, this.props.data.season, this.props.data.episode);
const imdbId = this.props.data.get('show_imdb_id');
const season = this.props.data.get('season');
const episode = this.props.data.get('episode');
this.props.getEpisodeDetails(imdbId, season, episode);
}
render() {
return (
<a type="button" className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}>
{this.props.data.fetching ||
{this.props.data.get('fetching') ||
<span>
<i className="fa fa-refresh"></i> Refresh
</span>
}
{this.props.data.fetching &&
{this.props.data.get('fetching') &&
<span>
<i className="fa fa-spin fa-refresh"></i> Refreshing
</span>

View File

@ -2,23 +2,23 @@ import React from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import { selectShow, addShowToWishlist,
deleteFromWishlist, getShowDetails } from '../../actions/shows'
deleteShowFromWishlist, getShowDetails } from '../../actions/shows'
import ListDetails from '../list/details'
import ListPosters from '../list/posters'
import ShowButtons from './listButtons'
function mapStateToProps(state) {
return { showStore: state.showStore };
return { showsStore: state.showsStore };
}
const mapDispatchToProps = (dispatch) =>
bindActionCreators({ selectShow, addShowToWishlist,
deleteFromWishlist, getShowDetails }, dispatch)
deleteShowFromWishlist, getShowDetails }, dispatch)
class ShowList extends React.Component {
render() {
const shows = this.props.showStore.shows;
const selectedShowId = this.props.showStore.selectedImdbId;
const shows = this.props.showsStore.shows;
const selectedShowId = this.props.showsStore.selectedImdbId;
let index = shows.map((el) => el.imdb_id).indexOf(selectedShowId);
if (index === -1) {
index = 0;
@ -30,17 +30,17 @@ class ShowList extends React.Component {
<ListPosters
data={shows}
type="shows"
formModel="showStore"
filterControlModel="showStore.filter"
formModel="showsStore"
filterControlModel="showsStore.filter"
filterControlPlaceHolder="Filter shows..."
exploreOptions={this.props.showStore.exploreOptions}
exploreOptions={this.props.showsStore.exploreOptions}
selectedImdbId={selectedShowId}
filter={this.props.showStore.filter}
perPage={this.props.showStore.perPage}
filter={this.props.showsStore.filter}
perPage={this.props.showsStore.perPage}
onClick={this.props.selectShow}
router={this.props.router}
params={this.props.params}
loading={this.props.showStore.loading}
loading={this.props.showsStore.loading}
/>
{selectedShow &&
<ListDetails data={selectedShow} >
@ -49,7 +49,7 @@ class ShowList extends React.Component {
deleteFromWishlist={this.props.deleteShowFromWishlist}
addToWishlist={this.props.addShowToWishlist}
getDetails={this.props.getShowDetails}
fetching={this.props.showStore.getDetails}
fetching={this.props.showsStore.getDetails}
/>
</ListDetails>
}

View File

@ -2,7 +2,8 @@ import { combineForms } from 'react-redux-form'
import { routerReducer } from 'react-router-redux'
import movieStore from './movies'
import showStore from './shows'
import showsStore from './shows'
import showStore from './show'
import userStore from './users'
import alerts from './alerts'
import torrentStore from './torrents'
@ -13,6 +14,7 @@ import torrentStore from './torrents'
const rootReducer = combineForms({
routing: routerReducer,
movieStore,
showsStore,
showStore,
userStore,
alerts,

View File

@ -0,0 +1,94 @@
import { OrderedMap, Map, List, fromJS } from 'immutable'
const defaultState = Map({
loading: false,
show: Map({
seasons: OrderedMap(),
}),
});
export default function showStore(state = defaultState, action) {
switch (action.type) {
case 'SHOW_FETCH_DETAILS_PENDING':
return state.set('loading', true)
case 'SHOW_FETCH_DETAILS_FULFILLED':
return sortEpisodes(state, action.payload.response.data);
case 'SHOW_UPDATE_STORE_WISHLIST':
let season = action.payload.season;
let episode = action.payload.episode;
if (action.payload.wishlisted && season === null) {
season = 0;
episode = 0;
}
return state.mergeDeep(fromJS({
'show': {
'tracked_season': season,
'tracked_episode': episode,
}
}));
case 'EPISODE_GET_DETAILS_PENDING':
return state.setIn(['show', 'seasons', action.payload.main.season, action.payload.main.episode, 'fetching'], true);
case 'EPISODE_GET_DETAILS_FULFILLED':
let data = action.payload.response.data;
if (!data) { return state }
data.fetching = false;
return state.setIn(['show', 'seasons', data.season, data.episode], fromJS(data));
case 'EPISODE_SUBTITLES_UPDATE_PENDING':
return state.setIn(['show', 'seasons', action.payload.main.season, action.payload.main.episode, 'fetchingSubtitles'], true);
case 'EPISODE_SUBTITLES_UPDATE_FULFILLED':
let epId = ['show', 'seasons', action.payload.main.season, action.payload.main.episode];
let ep = state.getIn(epId);
ep = ep.set('subtitles', fromJS(action.payload.response.data)).set('fetchingSubtitles', false);
return state.setIn(epId, ep);
default:
return state
}
}
function updateEpisode(state, fetching, data = null) {
if (data === null) {
return state;
}
if (!state.hasIn(['show', 'season', data.season, data.episode])) {
return show;
}
data.fetching = fetching
return show.updateIn(['show', 'seasons', data.season, data.episode], fromJS(data));
}
function sortEpisodes(state, show) {
let episodes = show.episodes;
delete show["episodes"];
let ret = state.set('loading', false);
if (episodes.length == 0) {
return ret;
}
// Set the show data
ret = ret.set('show', fromJS(show));
// Set the show episodes
for (let ep of episodes) {
ep.fetching = false;
ret = ret.setIn(['show', 'seasons', ep.season, ep.episode], fromJS(ep));
}
// Sort the episodes
ret = ret.updateIn(['show', 'seasons'], function(seasons) {
return seasons.map(function(episodes) {
return episodes.sort((a,b) => a.get('episode') - b.get('episode'));
});
});
// Sort the seasons
ret = ret.updateIn(['show', 'seasons'], function(seasons) {
return seasons.sort(function(a,b) {
return a.first().get('season') - b.first().get('season');
});
});
return ret
}

View File

@ -4,15 +4,12 @@ const defaultState = {
filter: "",
perPage: 30,
selectedImdbId: "",
show: {
seasons: [],
},
getDetails: false,
lastShowsFetchUrl: "",
exploreOptions: {},
};
export default function showStore(state = defaultState, action) {
export default function showsStore(state = defaultState, action) {
switch (action.type) {
case 'SHOW_LIST_FETCH_PENDING':
return Object.assign({}, state, {
@ -29,7 +26,6 @@ export default function showStore(state = defaultState, action) {
shows: action.payload.response.data,
selectedImdbId: selectedImdbId,
filter: defaultState.filter,
perPage: defaultState.perPage,
loading: false,
})
case 'SHOW_GET_DETAILS_PENDING':
@ -41,23 +37,6 @@ export default function showStore(state = defaultState, action) {
shows: updateShowDetails(state.shows.slice(), action.payload.response.data),
getDetails: false,
})
case 'SHOW_FETCH_DETAILS_PENDING':
return Object.assign({}, state, {
loading: true,
})
case 'SHOW_FETCH_DETAILS_FULFILLED':
return Object.assign({}, state, {
show: sortEpisodes(action.payload.response.data),
loading: false,
})
case 'EPISODE_GET_DETAILS':
return Object.assign({}, state, {
show: updateEpisode(Object.assign({}, state.show), true, action.payload),
})
case 'EPISODE_GET_DETAILS_FULFILLED':
return Object.assign({}, state, {
show: updateEpisode(Object.assign({}, state.show), false, action.payload.response.data),
})
case 'EXPLORE_SHOWS_PENDING':
return Object.assign({}, state, {
loading: true,
@ -74,21 +53,11 @@ export default function showStore(state = defaultState, action) {
case 'SHOW_UPDATE_STORE_WISHLIST':
return Object.assign({}, state, {
shows: updateShowsStoreWishlist(state.shows.slice(), action.payload),
show: updateShowStoreWishlist(Object.assign({}, state.show), action.payload),
})
case 'UPDATE_LAST_SHOWS_FETCH_URL':
return Object.assign({}, state, {
lastShowsFetchUrl: action.payload.url,
})
case 'EPISODE_SUBTITLES_UPDATE_PENDING':
return Object.assign({}, state, {
show: updateEpisodeSubtitles(Object.assign({}, state.show), action.payload.main.season, action.payload.main.episode, true),
})
case 'EPISODE_SUBTITLES_UPDATE_FULFILLED':
console.log("payload :", action.payload);
return Object.assign({}, state, {
show: updateEpisodeSubtitles(Object.assign({}, state.show), action.payload.main.season, action.payload.main.episode, false, action.payload.response.data),
})
case 'SELECT_SHOW':
// Don't select the show if we're fetching another show's details
if (state.fetchingDetails) {
@ -103,82 +72,6 @@ export default function showStore(state = defaultState, action) {
}
}
function updateEpisode(show, fetching, data = null) {
// Error handling for PouuleT
if (data === null) {
for (let seasonIndex of Object.keys(show.seasons)) {
for (let episodeIndex of Object.keys(show.seasons[seasonIndex].episodes)) {
show.seasons[seasonIndex].episodes[episodeIndex].fetching = false;
}
}
return show
}
let seasonIndex = show.seasons.map((el) => el.season).indexOf(data.season.toString());
let episodeIndex = show.seasons[seasonIndex].episodes.map((el) => el.episode).indexOf(data.episode);
if ('imdb_id' in data) {
show.seasons[seasonIndex].episodes[episodeIndex] = data;
}
show.seasons[seasonIndex].episodes[episodeIndex].fetching = fetching;
return show
}
function updateEpisodeSubtitles(show, season, episode, fetching, data = null) {
let seasonIndex = show.seasons.map((el) => el.season).indexOf(season.toString());
let episodeIndex = show.seasons[seasonIndex].episodes.map((el) => el.episode).indexOf(episode);
if (data) {
show.seasons[seasonIndex].episodes[episodeIndex].subtitles = data;
}
show.seasons[seasonIndex].episodes[episodeIndex].fetchingSubtitles = fetching;
return show
}
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) {
ep.fetching = false;
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;
}
// Update the store containing all the shows
function updateShowsStoreWishlist(shows, payload) {
if (shows.length === 0) {
@ -201,26 +94,6 @@ function updateShowsStoreWishlist(shows, payload) {
return shows
}
// Update the store containing the current detailed show
function updateShowStoreWishlist(show, payload) {
if (show.seasons.length === 0) {
return show;
}
let season = payload.season;
let episode = payload.episode;
if (payload.wishlisted) {
if (season === null) {
season = 0;
}
if (episode === null) {
episode = 0;
}
}
show.tracked_season = season;
show.tracked_episode = episode;
return show
}
function updateShowDetails(shows, data) {
let index = shows.map((el) => el.imdb_id).indexOf(data.imdb_id);
shows[index] = data;

View File

@ -15,13 +15,6 @@ import { fetchShows, fetchShowDetails, getShowExploreOptions } from './actions/s
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();
@ -198,7 +191,7 @@ export default function getRoutes(App) {
loginCheck(nextState, replace, next, function() {
var state = store.getState();
// Fetch the explore options
if (Object.keys(state.showStore.exploreOptions).length === 0) {
if (Object.keys(state.showsStore.exploreOptions).length === 0) {
store.dispatch(getShowExploreOptions());
}
store.dispatch(fetchShows(