Update the shows store to be immutable

This commit is contained in:
Grégoire Delattre 2017-06-02 13:49:58 +02:00
parent 80db4383a3
commit 9e5ae81f4e
6 changed files with 104 additions and 119 deletions

View File

@ -15,7 +15,9 @@ export function fetchShows(url) {
export function getShowDetails(imdbId) { export function getShowDetails(imdbId) {
return request( return request(
'SHOW_GET_DETAILS', 'SHOW_GET_DETAILS',
configureAxios().post(`/shows/${imdbId}/refresh`) configureAxios().post(`/shows/${imdbId}/refresh`),
null,
{ imdbId }
) )
} }
@ -36,7 +38,9 @@ export function getEpisodeDetails(imdbId, season, episode) {
export function fetchShowDetails(imdbId) { export function fetchShowDetails(imdbId) {
return request( return request(
'SHOW_FETCH_DETAILS', 'SHOW_FETCH_DETAILS',
configureAxios().get(`/shows/${imdbId}`) configureAxios().get(`/shows/${imdbId}`),
null,
{ imdbId }
) )
} }
@ -48,7 +52,6 @@ export function addShowToWishlist(imdbId, season = null, episode = null) {
episode: episode, episode: episode,
}), }),
[ [
addAlertOk("Show added to the wishlist"),
updateShowWishlistStore(imdbId, true, season, episode), updateShowWishlistStore(imdbId, true, season, episode),
], ],
) )
@ -59,7 +62,6 @@ export function deleteShowFromWishlist(imdbId) {
'SHOW_DELETE_FROM_WISHLIST', 'SHOW_DELETE_FROM_WISHLIST',
configureAxios().delete(`/wishlist/shows/${imdbId}`), configureAxios().delete(`/wishlist/shows/${imdbId}`),
[ [
addAlertOk("Show deleted from the wishlist"),
updateShowWishlistStore(imdbId, false), updateShowWishlistStore(imdbId, false),
], ],
) )
@ -87,10 +89,22 @@ export function getShowExploreOptions() {
export function selectShow(imdbId) { export function selectShow(imdbId) {
return { return {
type: 'SELECT_SHOW', type: 'SELECT_SHOW',
imdbId payload: {
imdbId,
}
} }
} }
export function updateFilter(filter) {
return {
type: 'SHOWS_UPDATE_FILTER',
payload: {
filter,
},
}
}
export function updateLastShowsFetchUrl(url) { export function updateLastShowsFetchUrl(url) {
return { return {
type: 'UPDATE_LAST_SHOWS_FETCH_URL', type: 'UPDATE_LAST_SHOWS_FETCH_URL',

View File

@ -16,7 +16,7 @@ export default function ListDetails(props) {
const trackedSeason = props.data.get('tracked_season'); const trackedSeason = props.data.get('tracked_season');
const trackedEpisode = props.data.get('tracked_episode'); const trackedEpisode = props.data.get('tracked_episode');
if (trackedEpisode !== undefined && trackedSeason !== undefined) { if (trackedEpisode !== null && trackedSeason !== null) {
if ((trackedSeason === 0) && (trackedEpisode === 0)) { if ((trackedSeason === 0) && (trackedEpisode === 0)) {
wishlistStr = "Whole show tracked"; wishlistStr = "Whole show tracked";
} else { } else {

View File

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

View File

@ -6,7 +6,7 @@ import { DropdownButton } from 'react-bootstrap'
import { WishlistButton, RefreshButton } from '../buttons/actions' import { WishlistButton, RefreshButton } from '../buttons/actions'
export default function ShowButtons(props) { export default function ShowButtons(props) {
const imdbLink = `http://www.imdb.com/title/${props.show.imdb_id}`; const imdbLink = `http://www.imdb.com/title/${props.show.get('imdb_id')}`;
return ( return (
<div className="list-details-buttons btn-toolbar"> <div className="list-details-buttons btn-toolbar">
<ActionsButton <ActionsButton
@ -14,12 +14,11 @@ export default function ShowButtons(props) {
addToWishlist={props.addToWishlist} addToWishlist={props.addToWishlist}
deleteFromWishlist={props.deleteFromWishlist} deleteFromWishlist={props.deleteFromWishlist}
getDetails={props.getDetails} getDetails={props.getDetails}
fetching={props.fetching}
/> />
<a type="button" className="btn btn-warning btn-sm" href={imdbLink}> <a type="button" className="btn btn-warning btn-sm" href={imdbLink}>
<i className="fa fa-external-link"></i> IMDB <i className="fa fa-external-link"></i> IMDB
</a> </a>
<Link type="button" className="btn btn-primary btn-sm" to={"/shows/details/" + props.show.imdb_id}> <Link type="button" className="btn btn-primary btn-sm" to={"/shows/details/" + props.show.get('imdb_id')}>
<i className="fa fa-external-link"></i> Details <i className="fa fa-external-link"></i> Details
</Link> </Link>
</div> </div>
@ -27,16 +26,16 @@ export default function ShowButtons(props) {
} }
function ActionsButton(props) { function ActionsButton(props) {
let wishlisted = (props.show.tracked_season !== null && props.show.tracked_episode !== null); let wishlisted = (props.show.get('tracked_season') !== null && props.show.get('tracked_episode') !== null);
return ( return (
<DropdownButton className="btn btn-default btn-sm" title="Actions" id="actions-button" dropup> <DropdownButton className="btn btn-default btn-sm" title="Actions" id="actions-button" dropup>
<RefreshButton <RefreshButton
fetching={props.fetching} fetching={props.show.get('fetchingDetails')}
resourceId={props.show.imdb_id} resourceId={props.show.get('imdb_id')}
getDetails={props.getDetails} getDetails={props.getDetails}
/> />
<WishlistButton <WishlistButton
resourceId={props.show.imdb_id} resourceId={props.show.get('imdb_id')}
wishlisted={wishlisted} wishlisted={wishlisted}
addToWishlist={props.addToWishlist} addToWishlist={props.addToWishlist}
deleteFromWishlist={props.deleteFromWishlist} deleteFromWishlist={props.deleteFromWishlist}

View File

@ -1,87 +1,52 @@
const defaultState = { import { OrderedMap, Map, fromJS } from 'immutable'
const defaultState = Map({
loading: false, loading: false,
shows: [], shows: OrderedMap(),
filter: "", filter: "",
perPage: 30,
selectedImdbId: "", selectedImdbId: "",
getDetails: false, lastFetchUrl: "",
lastShowsFetchUrl: "", exploreOptions: Map(),
exploreOptions: {}, });
};
export default function showsStore(state = defaultState, action) { export default function showsStore(state = defaultState, action) {
switch (action.type) { switch (action.type) {
case 'SHOW_LIST_FETCH_PENDING': case 'SHOW_LIST_FETCH_PENDING':
return Object.assign({}, state, { return state.set('loading', true);
loading: true,
})
case 'SHOW_LIST_FETCH_FULFILLED': case 'SHOW_LIST_FETCH_FULFILLED':
let shows = Map();
action.payload.response.data.map(function (show) {
show.fetchingDetails = false;
show.fetchingSubtitles = false;
shows = shows.set(show.imdb_id, fromJS(show));
})
let selectedImdbId = ""; let selectedImdbId = "";
// Select the first show if (shows.size > 0) {
console.log("Hey", action.payload); // Sort by year
if (action.payload.response.data.length > 0) { shows = shows.sort((a,b) => b.get('year') - a.get('year'));
selectedImdbId = action.payload.response.data[0].imdb_id; selectedImdbId = shows.first().get('imdb_id');
} }
return Object.assign({}, state, {
shows: action.payload.response.data, return state.delete('shows').merge(Map({
shows: shows,
filter: "",
loading: false,
selectedImdbId: selectedImdbId, selectedImdbId: selectedImdbId,
filter: defaultState.filter, }))
loading: false,
})
case 'SHOW_GET_DETAILS_PENDING': case 'SHOW_GET_DETAILS_PENDING':
return Object.assign({}, state, { return state.setIn(['shows', action.payload.main.imdbId, 'fetchingDetails'], true);
getDetails: true,
})
case 'SHOW_GET_DETAILS_FULFILLED': case 'SHOW_GET_DETAILS_FULFILLED':
return Object.assign({}, state, { let show = action.payload.response.data;
shows: updateShowDetails(state.shows.slice(), action.payload.response.data), show.fetchingDetails = false;
getDetails: false, show.fetchingSubtitles = false;
}) return state.setIn(['shows', show.imdb_id], fromJS(show));
case 'EXPLORE_SHOWS_PENDING':
return Object.assign({}, state, {
loading: true,
})
case 'EXPLORE_SHOWS_FULFILLED':
return Object.assign({}, state, {
shows: action.payload.response.data,
loading: false,
})
case 'SHOW_GET_EXPLORE_OPTIONS_FULFILLED': case 'SHOW_GET_EXPLORE_OPTIONS_FULFILLED':
return Object.assign({}, state, { return state.set('exploreOptions', fromJS(action.payload.response.data));
exploreOptions: action.payload.response.data,
})
case 'SHOW_UPDATE_STORE_WISHLIST': case 'SHOW_UPDATE_STORE_WISHLIST':
return Object.assign({}, state, { let season = action.payload.season;
shows: updateShowsStoreWishlist(state.shows.slice(), action.payload), let episode = action.payload.episode;
}) if (action.payload.wishlisted) {
case 'UPDATE_LAST_SHOWS_FETCH_URL':
return Object.assign({}, state, {
lastShowsFetchUrl: action.payload.url,
})
case 'SELECT_SHOW':
// Don't select the show if we're fetching another show's details
if (state.fetchingDetails) {
return state
}
return Object.assign({}, state, {
selectedImdbId: action.imdbId,
})
default:
return state
}
}
// Update the store containing all the shows
function updateShowsStoreWishlist(shows, payload) {
if (shows.length === 0) {
return shows;
}
let index = shows.map((el) => el.imdb_id).indexOf(payload.imdbId);
let season = payload.season;
let episode = payload.episode;
if (payload.wishlisted) {
if (season === null) { if (season === null) {
season = 0; season = 0;
} }
@ -89,13 +54,18 @@ function updateShowsStoreWishlist(shows, payload) {
episode = 0; episode = 0;
} }
} }
shows[index].tracked_season = season;
shows[index].tracked_episode = episode;
return shows
}
function updateShowDetails(shows, data) { return state.mergeIn(['shows', action.payload.imdbId], Map({
let index = shows.map((el) => el.imdb_id).indexOf(data.imdb_id); tracked_season: season,
shows[index] = data; tracked_episode: episode,
return shows }));
case 'UPDATE_LAST_SHOWS_FETCH_URL':
return state.set('lastFetchUrl', action.payload.url);
case 'SELECT_SHOW':
return state.set('selectedImdbId', action.payload.imdbId);
case 'SHOWS_UPDATE_FILTER':
return state.set('filter', action.payload.filter);
default:
return state
}
} }

View File

@ -191,7 +191,7 @@ export default function getRoutes(App) {
loginCheck(nextState, replace, next, function() { loginCheck(nextState, replace, next, function() {
var state = store.getState(); var state = store.getState();
// Fetch the explore options // Fetch the explore options
if (Object.keys(state.showsStore.exploreOptions).length === 0) { if (state.showsStore.get('exploreOptions').size === 0) {
store.dispatch(getShowExploreOptions()); store.dispatch(getShowExploreOptions());
} }
store.dispatch(fetchShows( store.dispatch(fetchShows(