Update the movie store to be immutable
This commit is contained in:
parent
697997c0ae
commit
80db4383a3
@ -14,7 +14,18 @@ export function updateLastMovieFetchUrl(url) {
|
|||||||
export function selectMovie(imdbId) {
|
export function selectMovie(imdbId) {
|
||||||
return {
|
return {
|
||||||
type: 'SELECT_MOVIE',
|
type: 'SELECT_MOVIE',
|
||||||
imdbId
|
payload: {
|
||||||
|
imdbId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateFilter(filter) {
|
||||||
|
return {
|
||||||
|
type: 'MOVIE_UPDATE_FILTER',
|
||||||
|
payload: {
|
||||||
|
filter,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +39,11 @@ export function getMovieExploreOptions() {
|
|||||||
export function getMovieDetails(imdbId) {
|
export function getMovieDetails(imdbId) {
|
||||||
return request(
|
return request(
|
||||||
'MOVIE_GET_DETAILS',
|
'MOVIE_GET_DETAILS',
|
||||||
configureAxios().post(`/movies/${imdbId}/refresh`)
|
configureAxios().post(`/movies/${imdbId}/refresh`),
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
imdbId,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +63,6 @@ export function addMovieToWishlist(imdbId) {
|
|||||||
'MOVIE_ADD_TO_WISHLIST',
|
'MOVIE_ADD_TO_WISHLIST',
|
||||||
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
||||||
[
|
[
|
||||||
addAlertOk("Movie added to the wishlist"),
|
|
||||||
updateMovieWishlistStore(imdbId, true),
|
updateMovieWishlistStore(imdbId, true),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -59,7 +73,6 @@ export function deleteMovieFromWishlist(imdbId) {
|
|||||||
'MOVIE_DELETE_FROM_WISHLIST',
|
'MOVIE_DELETE_FROM_WISHLIST',
|
||||||
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
||||||
[
|
[
|
||||||
addAlertOk("Movie deleted from the wishlist"),
|
|
||||||
updateMovieWishlistStore(imdbId, false),
|
updateMovieWishlistStore(imdbId, false),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ export function refreshSubtitles(type, id, season, episode) {
|
|||||||
'MOVIE_SUBTITLES_UPDATE',
|
'MOVIE_SUBTITLES_UPDATE',
|
||||||
configureAxios().post(`${resourceURL}/subtitles/refresh`),
|
configureAxios().post(`${resourceURL}/subtitles/refresh`),
|
||||||
null,
|
null,
|
||||||
{ imdb_id: id },
|
{ imdbId: id },
|
||||||
)
|
)
|
||||||
case 'episode':
|
case 'episode':
|
||||||
var resourceURL = `/shows/${id}/seasons/${season}/episodes/${episode}`
|
var resourceURL = `/shows/${id}/seasons/${season}/episodes/${episode}`
|
||||||
@ -19,12 +19,12 @@ export function refreshSubtitles(type, id, season, episode) {
|
|||||||
configureAxios().post(`${resourceURL}/subtitles/refresh`),
|
configureAxios().post(`${resourceURL}/subtitles/refresh`),
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
imdb_id: id,
|
imdbId: id,
|
||||||
season: season,
|
season: season,
|
||||||
episode: episode,
|
episode: episode,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
console.log("refreshSubtitles - Unknown type " + type)
|
console.warn("refreshSubtitles - Unknown type " + type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ import getRoutes from './routes'
|
|||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
let torrentCount = 0;
|
let torrentCount = 0;
|
||||||
if (state.torrentStore.has('torrents')) {
|
if (state.torrentStore.has('torrents') && state.torrentStore.get('torrents') !== undefined) {
|
||||||
torrentCount = state.torrentStore.get('torrents').size;
|
torrentCount = state.torrentStore.get('torrents').size;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { toJS } from 'immutable'
|
||||||
|
|
||||||
import { Button, Dropdown, MenuItem, Modal } from 'react-bootstrap'
|
import { Button, Dropdown, MenuItem, Modal } from 'react-bootstrap'
|
||||||
|
|
||||||
@ -69,20 +70,13 @@ export default class DownloadButton extends React.Component {
|
|||||||
class Player extends React.Component {
|
class Player extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
var subtitles = [];
|
|
||||||
if (props.subtitles && props.subtitles.length) {
|
|
||||||
subtitles = props.subtitles;
|
|
||||||
}
|
|
||||||
this.state = {
|
|
||||||
subtitles: subtitles,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="embed-responsive embed-responsive-16by9">
|
<div className="embed-responsive embed-responsive-16by9">
|
||||||
<video controls>
|
<video controls>
|
||||||
<source src={this.props.url} type="video/mp4"/>
|
<source src={this.props.url} type="video/mp4"/>
|
||||||
{this.props.subtitles.map(function(el, index) {
|
{this.props.subtitles !== undefined && subtitles.toKeyedSeq().map(function(el, index) {
|
||||||
return (
|
return (
|
||||||
<track
|
<track
|
||||||
key={index}
|
key={index}
|
||||||
|
@ -9,9 +9,8 @@ export default class SubtitlesButton extends React.Component {
|
|||||||
}
|
}
|
||||||
handleClick(e, url) {
|
handleClick(e, url) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.props.fetching) {
|
if (this.props.fetching) { return }
|
||||||
return
|
|
||||||
}
|
|
||||||
// Refresh the subtitles
|
// Refresh the subtitles
|
||||||
this.props.refreshSubtitles(this.props.type, this.props.resourceID, this.props.season, this.props.episode);
|
this.props.refreshSubtitles(this.props.type, this.props.resourceID, this.props.season, this.props.episode);
|
||||||
}
|
}
|
||||||
@ -35,16 +34,16 @@ export default class SubtitlesButton extends React.Component {
|
|||||||
case 'action':
|
case 'action':
|
||||||
return (
|
return (
|
||||||
<MenuItem key={index} onClick={(event) => this.handleClick(event, e.url)}>
|
<MenuItem key={index} onClick={(event) => this.handleClick(event, e.url)}>
|
||||||
{this.props.fetchingSubtitles ||
|
{this.props.fetching ||
|
||||||
<span>
|
<span>
|
||||||
<i className="fa fa-refresh">
|
<i className="fa fa-refresh">
|
||||||
</i> Refresh
|
</i> Refresh
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
{this.props.fetchingSubtitles &&
|
{this.props.fetching &&
|
||||||
<span>
|
<span>
|
||||||
<i className="fa fa-spin fa-refresh">
|
<i className="fa fa-spin fa-refresh">
|
||||||
</i> Refreshing
|
</i> Refreshing
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -77,7 +76,7 @@ function buildMenuItems(subtitles) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// If there is no subtitles, stop here
|
// If there is no subtitles, stop here
|
||||||
if (!subtitles) {
|
if (subtitles == undefined) {
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +87,8 @@ function buildMenuItems(subtitles) {
|
|||||||
entries.push({
|
entries.push({
|
||||||
type: "entry",
|
type: "entry",
|
||||||
// Take only the last part of fr_FR
|
// Take only the last part of fr_FR
|
||||||
lang: sub.language.split("_")[1],
|
lang: sub.get('language').split("_")[1],
|
||||||
url: sub.url,
|
url: sub.get('url'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,46 +1,47 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function ListDetails(props) {
|
export default function ListDetails(props) {
|
||||||
let genres;
|
let genres = props.data.get('genres');
|
||||||
if (props.data.genres) {
|
if (genres !== undefined) {
|
||||||
// Uppercase first genres
|
// Uppercase first genres
|
||||||
genres = props.data.genres.map(
|
genres = genres.toJS().map(
|
||||||
(word) => word[0].toUpperCase() + word.substr(1)
|
(word) => word[0].toUpperCase() + word.substr(1)
|
||||||
).join(', ');
|
).join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
let wishlistStr = "";
|
let wishlistStr = "";
|
||||||
if (props.data.wishlisted === true) {
|
if (props.data.get('wishlisted') === true) {
|
||||||
wishlistStr = "Wishlisted";
|
wishlistStr = "Wishlisted";
|
||||||
}
|
}
|
||||||
if (props.data.tracked_episode !== null && props.data.tracked_season != null) {
|
|
||||||
let season = props.data.tracked_season;
|
const trackedSeason = props.data.get('tracked_season');
|
||||||
let episode = props.data.tracked_episode;
|
const trackedEpisode = props.data.get('tracked_episode');
|
||||||
if ((season === 0) && (episode === 0)) {
|
if (trackedEpisode !== undefined && trackedSeason !== undefined) {
|
||||||
|
if ((trackedSeason === 0) && (trackedEpisode === 0)) {
|
||||||
wishlistStr = "Whole show tracked";
|
wishlistStr = "Whole show tracked";
|
||||||
} else {
|
} else {
|
||||||
wishlistStr = `Tracked from season ${season} episode ${episode}`;
|
wishlistStr = `Tracked from season ${trackedSeason} episode ${trackedEpisode}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-xs-7 col-md-4">
|
<div className="col-xs-7 col-md-4">
|
||||||
<div className="affix">
|
<div className="affix">
|
||||||
<h1 className="hidden-xs">{props.data.title}</h1>
|
<h1 className="hidden-xs">{props.data.get('title')}</h1>
|
||||||
<h3 className="visible-xs">{props.data.title}</h3>
|
<h3 className="visible-xs">{props.data.get('title')}</h3>
|
||||||
{wishlistStr !== "" &&
|
{wishlistStr !== "" &&
|
||||||
<span className="label label-default">
|
<span className="label label-default">
|
||||||
<i className="fa fa-bookmark"></i> {wishlistStr}
|
<i className="fa fa-bookmark"></i> {wishlistStr}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
<h4>{props.data.year}</h4>
|
<h4>{props.data.get('year')}</h4>
|
||||||
{props.data.runtime &&
|
{props.data.get('runtime') !== undefined &&
|
||||||
<p>
|
<p>
|
||||||
<i className="fa fa-clock-o"></i>
|
<i className="fa fa-clock-o"></i>
|
||||||
{props.data.runtime} min
|
{props.data.get('runtime')} min
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
{props.data.genres &&
|
{genres !== undefined &&
|
||||||
<p>
|
<p>
|
||||||
<i className="fa fa-tags"></i>
|
<i className="fa fa-tags"></i>
|
||||||
{genres}
|
{genres}
|
||||||
@ -48,12 +49,12 @@ export default function ListDetails(props) {
|
|||||||
}
|
}
|
||||||
<p>
|
<p>
|
||||||
<i className="fa fa-star-o"></i>
|
<i className="fa fa-star-o"></i>
|
||||||
{Number(props.data.rating).toFixed(1)}
|
{Number(props.data.get('rating')).toFixed(1)}
|
||||||
{props.data.votes &&
|
{props.data.get('votes') !== undefined &&
|
||||||
<small>({props.data.votes} counts)</small>
|
<small>({props.data.get('votes')} counts)</small>
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
<p className="plot">{props.data.plot}</p>
|
<p className="plot">{props.data.get('plot')}</p>
|
||||||
</div>
|
</div>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@ export default class ExplorerOptions extends React.Component {
|
|||||||
}
|
}
|
||||||
handleSourceChange(event) {
|
handleSourceChange(event) {
|
||||||
let source = event.target.value;
|
let source = event.target.value;
|
||||||
let category = this.props.options[event.target.value][0];
|
let category = this.props.options.get(event.target.value).first();
|
||||||
this.props.router.push(`/${this.props.type}/explore/${source}/${category}`);
|
this.props.router.push(`/${this.props.type}/explore/${source}/${category}`);
|
||||||
}
|
}
|
||||||
handleCategoryChange(event) {
|
handleCategoryChange(event) {
|
||||||
@ -40,7 +40,7 @@ export default class ExplorerOptions extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Options are not yet fetched
|
// Options are not yet fetched
|
||||||
if (Object.keys(this.props.options).length === 0) {
|
if (this.props.options.size === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ export default class ExplorerOptions extends React.Component {
|
|||||||
|
|
||||||
let source = this.props.params.source;
|
let source = this.props.params.source;
|
||||||
let category = this.props.params.category;
|
let category = this.props.params.category;
|
||||||
let categories = this.props.options[this.props.params.source];
|
let categories = this.props.options.get(this.props.params.source);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -67,8 +67,9 @@ export default class ExplorerOptions extends React.Component {
|
|||||||
onChange={this.handleSourceChange}
|
onChange={this.handleSourceChange}
|
||||||
value={source}
|
value={source}
|
||||||
>
|
>
|
||||||
{Object.keys(this.props.options).map(function(source) {
|
{this.props.options.entrySeq().map(function([source, categories]) {
|
||||||
return (<option key={source} value={source}>{this.prettyName(source)}</option>)
|
return (
|
||||||
|
<option key={source} value={source}>{this.prettyName(source)}</option>)
|
||||||
}, this)}
|
}, this)}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -1,30 +1,42 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Control, Form } from 'react-redux-form';
|
|
||||||
|
|
||||||
export default function ListFilter(props) {
|
export default class ListFilter extends React.PureComponent {
|
||||||
if (!props.display) {
|
constructor(props) {
|
||||||
return null;
|
super(props);
|
||||||
|
this.state = { filter: '' };
|
||||||
|
this.handleChange = this.handleChange.bind(this);
|
||||||
}
|
}
|
||||||
|
handleChange(ev) {
|
||||||
|
if (ev) { ev.preventDefault(); }
|
||||||
|
const value = this.input.value;
|
||||||
|
if (this.state.filter === value) { return }
|
||||||
|
this.setState({ filter: value });
|
||||||
|
|
||||||
if (props.listSize === 0) {
|
// Start filtering at 3 chars
|
||||||
return null;
|
if (value.length >= 3) {
|
||||||
|
this.props.updateFilter(value);
|
||||||
|
} else {
|
||||||
|
this.props.updateFilter('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xs-12 col-md-12 list-filter">
|
<div className="col-xs-12 col-md-12 list-filter">
|
||||||
<Form model={props.formModel} className="input-group hidebtn-xs" >
|
<form className="input-group" onSubmit={(ev) => this.handleChange(ev)}>
|
||||||
<Control.text
|
<input
|
||||||
model={props.controlModel}
|
className="form-control input-sm"
|
||||||
className="form-control input-sm"
|
placeholder={this.props.placeHolder}
|
||||||
placeholder={props.controlPlaceHolder}
|
onChange={this.handleChange}
|
||||||
updateOn="change"
|
ref={(input) => this.input = input}
|
||||||
/>
|
value={this.state.filter}
|
||||||
<span className="input-group-btn hidden-xs">
|
/>
|
||||||
<button className="btn btn-default btn-sm" type="button">Filter</button>
|
<span className="input-group-btn hidden-xs">
|
||||||
</span>
|
<button className="btn btn-default btn-sm" type="button">Filter</button>
|
||||||
</Form>
|
</span>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,35 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
export default function ListPoster(props) {
|
export default class ListPoster extends React.PureComponent {
|
||||||
const selected = props.selected ? ' thumbnail-selected' : '';
|
constructor(props) {
|
||||||
const imgClass = 'thumbnail' + selected;
|
super(props);
|
||||||
const displayClearFixLg = (props.index % 6) === 0;
|
}
|
||||||
const displayClearFixMd = (props.index % 4) === 0;
|
render() {
|
||||||
const displayClearFixSm = (props.index % 2) === 0;
|
const selected = this.props.selected ? ' thumbnail-selected' : '';
|
||||||
return (
|
const imgClass = 'thumbnail' + selected;
|
||||||
<div>
|
const displayClearFixLg = (this.props.index % 6) === 0;
|
||||||
{displayClearFixLg &&
|
const displayClearFixMd = (this.props.index % 4) === 0;
|
||||||
<div className="clearfix visible-lg"></div>
|
const displayClearFixSm = (this.props.index % 2) === 0;
|
||||||
}
|
return (
|
||||||
{displayClearFixMd &&
|
<div>
|
||||||
<div className="clearfix visible-md"></div>
|
{displayClearFixLg &&
|
||||||
}
|
<div className="clearfix visible-lg"></div>
|
||||||
{displayClearFixSm &&
|
}
|
||||||
<div className="clearfix visible-sm"></div>
|
{displayClearFixMd &&
|
||||||
}
|
<div className="clearfix visible-md"></div>
|
||||||
<div className="col-xs-12 col-sm-6 col-md-3 col-lg-2">
|
}
|
||||||
<a className={imgClass}>
|
{displayClearFixSm &&
|
||||||
<img
|
<div className="clearfix visible-sm"></div>
|
||||||
src={props.data.poster_url}
|
}
|
||||||
onClick={props.onClick}
|
<div className="col-xs-12 col-sm-6 col-md-3 col-lg-2">
|
||||||
/>
|
<a className={imgClass}>
|
||||||
</a>
|
<img
|
||||||
|
src={this.props.data.get('poster_url')}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { Map, List, fromJS } from 'immutable'
|
||||||
|
|
||||||
import fuzzy from 'fuzzy';
|
import fuzzy from 'fuzzy';
|
||||||
import InfiniteScroll from 'react-infinite-scroller';
|
import InfiniteScroll from 'react-infinite-scroller';
|
||||||
@ -24,14 +25,14 @@ export default class ListPosters extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.props.data.length) {
|
if (this.props.data === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState(this.getNextState(this.props));
|
this.setState(this.getNextState(this.props));
|
||||||
}
|
}
|
||||||
getNextState(props) {
|
getNextState(props) {
|
||||||
let totalListSize = props.data.length;
|
let totalListSize = props.data !== undefined ? props.data.size : 0;
|
||||||
let currentListSize = (this.state && this.state.items) ? this.state.items : 0;
|
let currentListSize = (this.state && this.state.items) ? this.state.items : 0;
|
||||||
let nextListSize = currentListSize + DEFAULT_ADD_EXTRA_ITEMS;
|
let nextListSize = currentListSize + DEFAULT_ADD_EXTRA_ITEMS;
|
||||||
let hasMore = true;
|
let hasMore = true;
|
||||||
@ -47,44 +48,44 @@ export default class ListPosters extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
if (this.props.data.length !== nextProps.data.length) {
|
if (this.props.data === undefined) { return }
|
||||||
|
if (nextProps.data === undefined) { return }
|
||||||
|
|
||||||
|
if (this.props.data.size !== nextProps.data.size) {
|
||||||
this.setState(this.getNextState(nextProps));
|
this.setState(this.getNextState(nextProps));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
let elmts = this.props.data.slice();
|
let elmts = this.props.data;
|
||||||
const listSize = elmts.length;
|
const listSize = elmts !== undefined ? elmts.size : 0;
|
||||||
const colSize = (listSize !== 0) ? "col-xs-5 col-md-8" : "col-xs-12";
|
const colSize = (listSize !== 0) ? "col-xs-5 col-md-8" : "col-xs-12";
|
||||||
|
|
||||||
// Filter the list of elements
|
// Filter the list of elements
|
||||||
if (this.props.filter !== "") {
|
if (this.props.filter !== "") {
|
||||||
const filtered = fuzzy.filter(this.props.filter, elmts, {
|
elmts = elmts.filter((v) => fuzzy.test(this.props.filter, v.get('title')), this);
|
||||||
extract: (el) => el.title
|
} else {
|
||||||
});
|
elmts = elmts.slice(0, this.state.items);
|
||||||
elmts = filtered.map((el) => el.original);
|
}
|
||||||
} else {
|
|
||||||
elmts = elmts.slice(0, this.state.items);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chose when to display filter / explore options
|
// Chose when to display filter / explore options
|
||||||
let displayFilter = true;
|
let displayFilter = true;
|
||||||
if (this.props.params
|
if ((this.props.params
|
||||||
&& this.props.params.category
|
&& this.props.params.category
|
||||||
&& this.props.params.category !== ""
|
&& this.props.params.category !== ""
|
||||||
&& this.props.params.source
|
&& this.props.params.source
|
||||||
&& this.props.params.source !== "") {
|
&& this.props.params.source !== "")
|
||||||
|
|| (listSize === 0)) {
|
||||||
displayFilter = false;
|
displayFilter = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={colSize}>
|
<div className={colSize}>
|
||||||
<ListFilter
|
{displayFilter &&
|
||||||
listSize={listSize}
|
<ListFilter
|
||||||
display={displayFilter}
|
updateFilter={this.props.updateFilter}
|
||||||
formModel={this.props.formModel}
|
placeHolder={this.props.placeHolder}
|
||||||
controlModel={this.props.filterControlModel}
|
/>
|
||||||
controlPlaceHolder={this.props.filterControlPlaceHolder}
|
}
|
||||||
/>
|
|
||||||
<ExplorerOptions
|
<ExplorerOptions
|
||||||
type={this.props.type}
|
type={this.props.type}
|
||||||
display={!displayFilter}
|
display={!displayFilter}
|
||||||
@ -105,39 +106,45 @@ export default class ListPosters extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Posters(props) {
|
class Posters extends React.PureComponent {
|
||||||
if (props.loading) {
|
constructor(props) {
|
||||||
return (<Loader />);
|
super(props);
|
||||||
}
|
}
|
||||||
|
render() {
|
||||||
|
if (this.props.loading) {
|
||||||
|
return (<Loader />);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.elmts.size === 0) {
|
||||||
|
return (
|
||||||
|
<div className="jumbotron">
|
||||||
|
<h2>No result</h2>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (props.elmts.length === 0) {
|
|
||||||
return (
|
return (
|
||||||
<div className="jumbotron">
|
<div>
|
||||||
<h2>No result</h2>
|
<InfiniteScroll
|
||||||
|
hasMore={this.props.hasMore}
|
||||||
|
loadMore={this.props.loadMore}
|
||||||
|
className="row"
|
||||||
|
>
|
||||||
|
{this.props.elmts.toIndexedSeq().map(function(movie, index) {
|
||||||
|
const imdbId = movie.get('imdb_id');
|
||||||
|
const selected = (imdbId === this.props.selectedImdbId) ? true : false;
|
||||||
|
return (
|
||||||
|
<ListPoster
|
||||||
|
index={index}
|
||||||
|
data={movie}
|
||||||
|
key={imdbId}
|
||||||
|
selected={selected}
|
||||||
|
onClick={() => this.props.onClick(imdbId)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} ,this)}
|
||||||
|
</InfiniteScroll>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<InfiniteScroll
|
|
||||||
hasMore={props.hasMore}
|
|
||||||
loadMore={props.loadMore}
|
|
||||||
className="row"
|
|
||||||
>
|
|
||||||
{props.elmts.map(function(el, index) {
|
|
||||||
const selected = (el.imdb_id === props.selectedImdbId) ? true : false;
|
|
||||||
return (
|
|
||||||
<ListPoster
|
|
||||||
index={index}
|
|
||||||
data={el}
|
|
||||||
key={el.imdb_id}
|
|
||||||
selected={selected}
|
|
||||||
onClick={() => props.onClick(el.imdb_id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
)}
|
|
||||||
</InfiniteScroll>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import { connect } from 'react-redux'
|
|||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
import { addTorrent } from '../../actions/torrents'
|
import { addTorrent } from '../../actions/torrents'
|
||||||
import { refreshSubtitles } from '../../actions/subtitles'
|
import { refreshSubtitles } from '../../actions/subtitles'
|
||||||
import { addMovieToWishlist, deleteMovieFromWishlist,
|
import { addMovieToWishlist, deleteMovie, deleteMovieFromWishlist,
|
||||||
getMovieDetails, selectMovie } from '../../actions/movies'
|
getMovieDetails, selectMovie, updateFilter } from '../../actions/movies'
|
||||||
|
|
||||||
import DownloadButton from '../buttons/download'
|
import DownloadButton from '../buttons/download'
|
||||||
import SubtitlesButton from '../buttons/subtitles'
|
import SubtitlesButton from '../buttons/subtitles'
|
||||||
@ -14,48 +14,55 @@ import ListPosters from '../list/posters'
|
|||||||
import ListDetails from '../list/details'
|
import ListDetails from '../list/details'
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return { movieStore: state.movieStore };
|
return {
|
||||||
|
loading : state.movieStore.get('loading'),
|
||||||
|
movies : state.movieStore.get('movies'),
|
||||||
|
filter : state.movieStore.get('filter'),
|
||||||
|
selectedImdbId : state.movieStore.get('selectedImdbId'),
|
||||||
|
lastFetchUrl : state.movieStore.get('lastFetchUrl'),
|
||||||
|
exploreOptions : state.movieStore.get('exploreOptions'),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
const mapDispatchToProps = (dipatch) =>
|
const mapDispatchToProps = (dipatch) =>
|
||||||
bindActionCreators({ selectMovie, getMovieDetails, addTorrent,
|
bindActionCreators({ selectMovie, getMovieDetails, addTorrent,
|
||||||
addMovieToWishlist, deleteMovieFromWishlist, refreshSubtitles }, dipatch)
|
addMovieToWishlist, deleteMovie, deleteMovieFromWishlist,
|
||||||
|
refreshSubtitles, updateFilter }, dipatch)
|
||||||
|
|
||||||
function MovieButtons(props) {
|
function MovieButtons(props) {
|
||||||
const imdb_link = `http://www.imdb.com/title/${props.movie.imdb_id}`;
|
const imdb_link = `http://www.imdb.com/title/${props.movie.get('imdb_id')}`;
|
||||||
const hasMovie = (props.movie.polochon_url !== "");
|
const hasMovie = (props.movie.get('polochon_url') !== "");
|
||||||
return (
|
return (
|
||||||
<div className="list-details-buttons btn-toolbar">
|
<div className="list-details-buttons btn-toolbar">
|
||||||
<ActionsButton
|
<ActionsButton
|
||||||
fetching={props.fetching}
|
fetching={props.movie.get('fetchingDetails')}
|
||||||
movieId={props.movie.imdb_id}
|
movieId={props.movie.get('imdb_id')}
|
||||||
getDetails={props.getMovieDetails}
|
getDetails={props.getMovieDetails}
|
||||||
deleteMovie={props.deleteMovie}
|
deleteMovie={props.deleteMovie}
|
||||||
hasMovie={hasMovie}
|
hasMovie={hasMovie}
|
||||||
wishlisted={props.movie.wishlisted}
|
wishlisted={props.movie.get('wishlisted')}
|
||||||
addToWishlist={props.addToWishlist}
|
addToWishlist={props.addToWishlist}
|
||||||
deleteFromWishlist={props.deleteFromWishlist}
|
deleteFromWishlist={props.deleteFromWishlist}
|
||||||
lastFetchUrl={props.lastFetchUrl}
|
lastFetchUrl={props.lastFetchUrl}
|
||||||
fetchMovies={props.fetchMovies}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{props.movie.torrents &&
|
{props.movie.get('torrents') !== null &&
|
||||||
<TorrentsButton
|
<TorrentsButton
|
||||||
torrents={props.movie.torrents}
|
torrents={props.movie.get('torrents')}
|
||||||
addTorrent={props.addTorrent}
|
addTorrent={props.addTorrent}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<DownloadButton
|
<DownloadButton
|
||||||
url={props.movie.polochon_url}
|
url={props.movie.get('polochon_url')}
|
||||||
subtitles={props.movie.subtitles}
|
subtitles={props.movie.get('subtitles')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SubtitlesButton
|
<SubtitlesButton
|
||||||
url={props.movie.polochon_url}
|
fetching={props.movie.get('fetchingSubtitles')}
|
||||||
subtitles={props.movie.subtitles}
|
url={props.movie.get('polochon_url')}
|
||||||
|
subtitles={props.movie.get('subtitles')}
|
||||||
refreshSubtitles={props.refreshSubtitles}
|
refreshSubtitles={props.refreshSubtitles}
|
||||||
resourceID={props.movie.imdb_id}
|
resourceID={props.movie.get('imdb_id')}
|
||||||
data={props.movie}
|
|
||||||
type="movie"
|
type="movie"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -71,42 +78,36 @@ class MovieList extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const movies = this.props.movieStore.movies;
|
let selectedMovie = undefined;
|
||||||
const selectedMovieId = this.props.movieStore.selectedImdbId;
|
if (this.props.movies !== undefined && this.props.movies.has(this.props.selectedImdbId)) {
|
||||||
let index = movies.map((el) => el.imdb_id).indexOf(selectedMovieId);
|
selectedMovie = this.props.movies.get(this.props.selectedImdbId);
|
||||||
if (index === -1) {
|
|
||||||
index = 0;
|
|
||||||
}
|
}
|
||||||
const selectedMovie = movies[index];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row" id="container">
|
<div className="row" id="container">
|
||||||
<ListPosters
|
<ListPosters
|
||||||
data={movies}
|
data={this.props.movies}
|
||||||
type="movies"
|
type="movies"
|
||||||
formModel="movieStore"
|
placeHolder="Filter movies..."
|
||||||
filterControlModel="movieStore.filter"
|
exploreOptions={this.props.exploreOptions}
|
||||||
filterControlPlaceHolder="Filter movies..."
|
selectedImdbId={this.props.selectedImdbId}
|
||||||
exploreOptions={this.props.movieStore.exploreOptions}
|
updateFilter={this.props.updateFilter}
|
||||||
selectedImdbId={selectedMovieId}
|
filter={this.props.filter}
|
||||||
filter={this.props.movieStore.filter}
|
|
||||||
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}
|
router={this.props.router}
|
||||||
loading={this.props.movieStore.loading}
|
loading={this.props.loading}
|
||||||
/>
|
/>
|
||||||
{selectedMovie &&
|
{selectedMovie !== undefined &&
|
||||||
<ListDetails data={selectedMovie}>
|
<ListDetails data={selectedMovie}>
|
||||||
<MovieButtons
|
<MovieButtons
|
||||||
movie={selectedMovie}
|
movie={selectedMovie}
|
||||||
fetching={this.props.movieStore.fetchingDetails}
|
|
||||||
getMovieDetails={this.props.getMovieDetails}
|
getMovieDetails={this.props.getMovieDetails}
|
||||||
addTorrent={this.props.addTorrent}
|
addTorrent={this.props.addTorrent}
|
||||||
deleteMovie={this.props.deleteMovie}
|
deleteMovie={this.props.deleteMovie}
|
||||||
addToWishlist={this.props.addMovieToWishlist}
|
addToWishlist={this.props.addMovieToWishlist}
|
||||||
deleteFromWishlist={this.props.deleteMovieFromWishlist}
|
deleteFromWishlist={this.props.deleteMovieFromWishlist}
|
||||||
lastFetchUrl={this.props.movieStore.lastFetchUrl}
|
lastFetchUrl={this.props.lastFetchUrl}
|
||||||
refreshSubtitles={this.props.refreshSubtitles}
|
refreshSubtitles={this.props.refreshSubtitles}
|
||||||
/>
|
/>
|
||||||
</ListDetails>
|
</ListDetails>
|
||||||
|
@ -41,20 +41,12 @@ export default class TorrentsButton extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildMenuItems(torrents) {
|
function buildMenuItems(torrents) {
|
||||||
// Organise by source
|
const t = torrents.groupBy((el) => el.get('source'));
|
||||||
let sources = {}
|
|
||||||
for (let torrent of torrents) {
|
|
||||||
if (!sources[torrent.source]) {
|
|
||||||
sources[torrent.source] = [];
|
|
||||||
}
|
|
||||||
sources[torrent.source].push(torrent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the array of entries
|
// Build the array of entries
|
||||||
let entries = [];
|
let entries = [];
|
||||||
let sourceNames = Object.keys(sources);
|
let dividerCount = t.size - 1;
|
||||||
let dividerCount = sourceNames.length - 1;
|
for (let [source, torrentList] of t.entrySeq()) {
|
||||||
for (let source of sourceNames) {
|
|
||||||
// Push the title
|
// Push the title
|
||||||
entries.push({
|
entries.push({
|
||||||
type: "header",
|
type: "header",
|
||||||
@ -62,11 +54,11 @@ function buildMenuItems(torrents) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Push the torrents
|
// Push the torrents
|
||||||
for (let torrent of sources[source]) {
|
for (let torrent of torrentList) {
|
||||||
entries.push({
|
entries.push({
|
||||||
type: "entry",
|
type: "entry",
|
||||||
quality: torrent.quality,
|
quality: torrent.get('quality'),
|
||||||
url: torrent.url,
|
url: torrent.get('url'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,96 +1,63 @@
|
|||||||
const defaultState = {
|
import { OrderedMap, Map, fromJS } from 'immutable'
|
||||||
|
|
||||||
|
const defaultState = Map({
|
||||||
loading: false,
|
loading: false,
|
||||||
movies: [],
|
movies: OrderedMap(),
|
||||||
filter: "",
|
filter: "",
|
||||||
perPage: 30,
|
|
||||||
selectedImdbId: "",
|
selectedImdbId: "",
|
||||||
fetchingDetails: false,
|
|
||||||
lastFetchUrl: "",
|
lastFetchUrl: "",
|
||||||
exploreOptions: {},
|
exploreOptions: Map(),
|
||||||
};
|
});
|
||||||
|
|
||||||
export default function movieStore(state = defaultState, action) {
|
export default function movieStore(state = defaultState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'MOVIE_LIST_FETCH_PENDING':
|
case 'MOVIE_LIST_FETCH_PENDING':
|
||||||
return Object.assign({}, state, {
|
return state.set('loading', true);
|
||||||
loading: true,
|
|
||||||
})
|
|
||||||
case 'MOVIE_LIST_FETCH_FULFILLED':
|
case 'MOVIE_LIST_FETCH_FULFILLED':
|
||||||
|
|
||||||
|
let movies = Map();
|
||||||
|
action.payload.response.data.map(function (movie) {
|
||||||
|
movie.fetchingDetails = false;
|
||||||
|
movie.fetchingSubtitles = false;
|
||||||
|
movies = movies.set(movie.imdb_id, fromJS(movie));
|
||||||
|
})
|
||||||
|
|
||||||
|
// Select the first movie if the list is not empty
|
||||||
let selectedImdbId = "";
|
let selectedImdbId = "";
|
||||||
// Select the first movie
|
if (movies.size > 0) {
|
||||||
if (action.payload.response.data.length > 0) {
|
|
||||||
// Sort by year
|
// Sort by year
|
||||||
action.payload.response.data.sort((a,b) => b.year - a.year);
|
movies = movies.sort((a,b) => b.get('year') - a.get('year'));
|
||||||
selectedImdbId = action.payload.response.data[0].imdb_id;
|
selectedImdbId = movies.first().get('imdb_id');
|
||||||
}
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
movies: action.payload.response.data,
|
|
||||||
selectedImdbId: selectedImdbId,
|
|
||||||
filter: defaultState.filter,
|
|
||||||
perPage: defaultState.perPage,
|
|
||||||
loading: false,
|
|
||||||
})
|
|
||||||
case 'MOVIE_GET_DETAILS_PENDING':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
fetchingDetails: true,
|
|
||||||
})
|
|
||||||
case 'MOVIE_GET_DETAILS_FULFILLED':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
movies: updateMovieDetails(state.movies.slice(), action.payload.response.data.imdb_id, action.payload.response.data),
|
|
||||||
fetchingDetails: false,
|
|
||||||
})
|
|
||||||
case 'MOVIE_UPDATE_STORE_WISHLIST':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
movies: updateStoreWishlist(state.movies.slice(), action.payload.imdbId, action.payload.wishlisted),
|
|
||||||
})
|
|
||||||
case 'MOVIE_GET_EXPLORE_OPTIONS_FULFILLED':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
exploreOptions: action.payload.response.data,
|
|
||||||
})
|
|
||||||
case 'UPDATE_LAST_MOVIE_FETCH_URL':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
lastFetchUrl: action.payload.url,
|
|
||||||
})
|
|
||||||
case 'MOVIE_SUBTITLES_UPDATE_PENDING':
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
movies: updateMovieSubtitles(state.movies.slice(), state.selectedImdbId, true),
|
|
||||||
})
|
|
||||||
case 'MOVIE_SUBTITLES_UPDATE_FULFILLED':
|
|
||||||
console.log("payload :", action.payload);
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
movies: updateMovieSubtitles(state.movies.slice(), state.selectedImdbId, false, action.payload.response.data),
|
|
||||||
})
|
|
||||||
case 'SELECT_MOVIE':
|
|
||||||
// Don't select the movie if we're fetching another movie's details
|
|
||||||
if (state.fetchingDetails) {
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.assign({}, state, {
|
return state.delete('movies').merge(Map({
|
||||||
selectedImdbId: action.imdbId,
|
movies: movies,
|
||||||
})
|
filter: "",
|
||||||
|
loading: false,
|
||||||
|
selectedImdbId: selectedImdbId,
|
||||||
|
}))
|
||||||
|
case 'MOVIE_GET_DETAILS_PENDING':
|
||||||
|
return state.setIn(['movies', action.payload.main.imdbId, 'fetchingDetails'], true);
|
||||||
|
case 'MOVIE_GET_DETAILS_FULFILLED':
|
||||||
|
let movie = action.payload.response.data;
|
||||||
|
movie.fetchingDetails = false;
|
||||||
|
movie.fetchingSubtitles = false;
|
||||||
|
return state.setIn(['movies', movie.imdb_id], fromJS(movie));
|
||||||
|
case 'MOVIE_UPDATE_STORE_WISHLIST':
|
||||||
|
return state.setIn(['movies', action.payload.imdbId, 'wishlisted'], action.payload.wishlisted);
|
||||||
|
case 'MOVIE_GET_EXPLORE_OPTIONS_FULFILLED':
|
||||||
|
return state.set('exploreOptions', fromJS(action.payload.response.data));
|
||||||
|
case 'UPDATE_LAST_MOVIE_FETCH_URL':
|
||||||
|
return state.set('lastFetchUrl', action.payload.url);
|
||||||
|
case 'MOVIE_SUBTITLES_UPDATE_PENDING':
|
||||||
|
return state.setIn(['movies', action.payload.main.imdbId, 'fetchingSubtitles'], true);
|
||||||
|
case 'MOVIE_SUBTITLES_UPDATE_FULFILLED':
|
||||||
|
return state.setIn(['movies', action.payload.main.imdbId, 'fetchingSubtitles'], false).setIn(['movies', action.payload.main.imdbId, 'subtitles'], fromJS(action.payload.data.response));
|
||||||
|
case 'SELECT_MOVIE':
|
||||||
|
return state.set('selectedImdbId', action.payload.imdbId);
|
||||||
|
case 'MOVIE_UPDATE_FILTER':
|
||||||
|
return state.set('filter', action.payload.filter);
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMovieDetails(movies, imdbId, data) {
|
|
||||||
let index = movies.map((el) => el.imdb_id).indexOf(imdbId);
|
|
||||||
movies[index] = data;
|
|
||||||
return movies
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStoreWishlist(movies, imdbId, wishlisted) {
|
|
||||||
let index = movies.map((el) => el.imdb_id).indexOf(imdbId);
|
|
||||||
movies[index].wishlisted = wishlisted;
|
|
||||||
return movies
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMovieSubtitles(movies, imdbId, fetching, data = null) {
|
|
||||||
let index = movies.map((el) => el.imdb_id).indexOf(imdbId);
|
|
||||||
if (data) {
|
|
||||||
movies[index].subtitles = data;
|
|
||||||
}
|
|
||||||
movies[index].fetchingSubtitles = fetching;
|
|
||||||
return movies
|
|
||||||
}
|
|
||||||
|
@ -38,7 +38,8 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
|||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
main: mainPayload,
|
main: mainPayload,
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: fulfilled,
|
type: fulfilled,
|
||||||
|
@ -130,7 +130,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.movieStore.exploreOptions).length === 0) {
|
if (state.movieStore.get('exploreOptions').size === 0) {
|
||||||
store.dispatch(getMovieExploreOptions());
|
store.dispatch(getMovieExploreOptions());
|
||||||
}
|
}
|
||||||
store.dispatch(fetchMovies(
|
store.dispatch(fetchMovies(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user