From dd3ab77f2c5528c472b34724b820b071d120df21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Delattre?= Date: Wed, 31 May 2017 13:55:03 +0200 Subject: [PATCH] Make the show store immutable --- src/public/js/actions/shows.js | 13 +- src/public/js/actions/subtitles.js | 12 +- src/public/js/components/buttons/subtitles.js | 2 +- src/public/js/components/shows/details.js | 118 +++++++------ src/public/js/reducers/show.js | 165 +++++++----------- src/public/js/routes.js | 9 +- 6 files changed, 134 insertions(+), 185 deletions(-) diff --git a/src/public/js/actions/shows.js b/src/public/js/actions/shows.js index fc84fae..20c5c28 100644 --- a/src/public/js/actions/shows.js +++ b/src/public/js/actions/shows.js @@ -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) { diff --git a/src/public/js/actions/subtitles.js b/src/public/js/actions/subtitles.js index 843ed43..0f950f9 100644 --- a/src/public/js/actions/subtitles.js +++ b/src/public/js/actions/subtitles.js @@ -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, diff --git a/src/public/js/components/buttons/subtitles.js b/src/public/js/components/buttons/subtitles.js index 6a96c63..083ac23 100644 --- a/src/public/js/components/buttons/subtitles.js +++ b/src/public/js/components/buttons/subtitles.js @@ -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 diff --git a/src/public/js/components/shows/details.js b/src/public/js/components/shows/details.js index 022d509..46931a3 100644 --- a/src/public/js/components/shows/details.js +++ b/src/public/js/components/shows/details.js @@ -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, deleteShowFromWishlist, 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, deleteShowFromWishlist, - updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails, + 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} /> @@ -70,21 +70,21 @@ function Header(props){ function HeaderThumbnail(props){ return (
-
); } 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 (
Title
-
{props.data.title}
+
{props.data.get('title')}
Plot
-
{props.data.plot}
+
{props.data.get('plot')}
IMDB
@@ -92,9 +92,9 @@ function HeaderDetails(props){
Year
-
{props.data.year}
+
{props.data.get('year')}
Rating
-
{props.data.rating}
+
{props.data.get('rating')}
- {props.data.seasons.length > 0 && props.data.seasons.map(function(season, index) { + {props.data.get('seasons').entrySeq().map(function([season, data]) { return ( -
+ +
- ) + ); })}
) @@ -140,8 +141,8 @@ class Season extends React.Component { return (
this.handleClick(e)}> - Season {this.props.data.season} - — ({this.props.data.episodes.length} episodes) + Season {this.props.season} + — ({this.props.data.toList().size} episodes) {this.state.colapsed || @@ -154,8 +155,8 @@ class Season extends React.Component { {this.state.colapsed || - {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 ( ) @@ -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 ( @@ -239,9 +245,9 @@ class Torrent extends React.Component { this.handleClick(e, this.props.data.url)} + onClick={(e) => this.handleClick(e, this.props.data.get('url'))} href={this.props.data.url} > - {this.props.data.quality} + {this.props.data.get('quality')} ) @@ -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); + 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 (wishlisted) { - let msg; - if (this.props.data.tracked_season !== 0 && this.props.data.tracked_episode !== 0) { + if (trackedSeason !== 0 && trackedEpisode !== 0) { msg = ( -
- Show tracked from season {this.props.data.tracked_season} episode {this.props.data.tracked_episode} -
+
Show tracked from season {trackedSeason} episode {trackedEpisode}
); } else { msg = ( -
- Whole show tracked -
+
Whole show tracked
); } 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 ( this.handleClick(e)}> - {this.props.data.fetching || + {this.props.data.get('fetching') || Refresh } - {this.props.data.fetching && + {this.props.data.get('fetching') && Refreshing diff --git a/src/public/js/reducers/show.js b/src/public/js/reducers/show.js index 11685c3..178c5ef 100644 --- a/src/public/js/reducers/show.js +++ b/src/public/js/reducers/show.js @@ -1,139 +1,94 @@ -const defaultState = { +import { OrderedMap, Map, List, fromJS } from 'immutable' + +const defaultState = Map({ loading: false, - show: { - seasons: [], - }, -}; + show: Map({ + seasons: OrderedMap(), + }), +}); export default function showStore(state = defaultState, action) { switch (action.type) { case 'SHOW_FETCH_DETAILS_PENDING': - return Object.assign({}, state, { - loading: true, - }) + return state.set('loading', true) case 'SHOW_FETCH_DETAILS_FULFILLED': - return Object.assign({}, state, { - show: sortEpisodes(action.payload.response.data), - loading: false, - }) + return sortEpisodes(state, action.payload.response.data); case 'SHOW_UPDATE_STORE_WISHLIST': - return Object.assign({}, state, { - show: updateShowStoreWishlist(Object.assign({}, state.show), action.payload), - }) - case 'EPISODE_GET_DETAILS': - return Object.assign({}, state, { - show: updateEpisode(Object.assign({}, state.show), true, action.payload), - }) + 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': - return Object.assign({}, state, { - show: updateEpisode(Object.assign({}, state.show), false, action.payload.response.data), - }) + 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 Object.assign({}, state, { - show: updateEpisodeSubtitles(Object.assign({}, state.show), action.payload.main.season, action.payload.main.episode, true), - }) + return state.setIn(['show', 'seasons', action.payload.main.season, action.payload.main.episode, 'fetchingSubtitles'], 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), - }) + 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(show, fetching, data = null) { - // Error handling for PouuleT +function updateEpisode(state, fetching, data = null) { 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 + return state; } - 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; + if (!state.hasIn(['show', 'season', data.season, data.episode])) { + return show; } - show.seasons[seasonIndex].episodes[episodeIndex].fetching = fetching; - return show + + data.fetching = fetching + return show.updateIn(['show', 'seasons', data.season, data.episode], fromJS(data)); } -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) { +function sortEpisodes(state, show) { let episodes = show.episodes; delete show["episodes"]; + let ret = state.set('loading', false); if (episodes.length == 0) { - return show; + return ret; } - // Extract the seasons - let seasons = {}; + // Set the show data + ret = ret.set('show', fromJS(show)); + + // Set the show episodes for (let ep of episodes) { ep.fetching = false; - if (!seasons[ep.season]) { - seasons[ep.season] = { episodes: [] }; - } - seasons[ep.season].episodes.push(ep); + ret = ret.setIn(['show', 'seasons', ep.season, ep.episode], fromJS(ep)); } - if (seasons.length === 0) { - return show; - } + // 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')); + }); + }); - // 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, - }) - } + // 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'); + }); + }); - // Order the seasons - for (let i=0; i (a.season - b.season)) - } - - show.seasons = sortedSeasons; - - return show; -} - -// 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 + return ret } diff --git a/src/public/js/routes.js b/src/public/js/routes.js index 9355f9a..33e3d03 100644 --- a/src/public/js/routes.js +++ b/src/public/js/routes.js @@ -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(
@@ -184,23 +190,24 @@ function Episode(props) { data={props.data} addToWishlist={props.addToWishlist} /> - {props.data.episode} + {props.data.get('episode')} - {props.data.title} + {props.data.get('title')} - {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 (