Merge branch 'subtitles' into 'master'

Add subtitles in the front end

See merge request !72
This commit is contained in:
Grégoire Delattre 2017-05-29 12:49:06 +00:00
commit 12e43b4397
15 changed files with 301 additions and 70 deletions

View File

@ -325,12 +325,23 @@ func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.R
return env.RenderError(w, err) return env.RenderError(w, err)
} }
err = client.UpdateSubtitles(&papi.Movie{ImdbID: id}) movie := &papi.Movie{ImdbID: id}
refreshSubs, err := client.UpdateSubtitles(movie)
if err != nil { if err != nil {
return env.RenderError(w, err) return env.RenderError(w, err)
} }
return env.RenderOK(w, "Subtitles refreshed") subs := []subtitles.Subtitle{}
for _, lang := range refreshSubs {
subtitleURL, _ := client.SubtitleURL(movie, lang)
subs = append(subs, subtitles.Subtitle{
Language: lang,
URL: subtitleURL,
VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", id, lang),
})
}
return env.RenderJSON(w, subs)
} }
// DownloadVVTSubtitle returns a vvt subtitle for the movie // DownloadVVTSubtitle returns a vvt subtitle for the movie

View File

@ -12,6 +12,7 @@ import (
"github.com/odwrtw/polochon/lib" "github.com/odwrtw/polochon/lib"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/subtitles"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/users"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
) )
@ -31,12 +32,7 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
type Alias Movie type Alias Movie
var downloadURL string var downloadURL string
type Subtitle struct { var subs []subtitles.Subtitle
Language string `json:"language"`
URL string `json:"url"`
VVTFile string `json:"vvt_file"`
}
var subtitles []Subtitle
// If the episode is present, fill the downloadURL // If the episode is present, fill the downloadURL
if m.pMovie != nil { if m.pMovie != nil {
// Get the DownloadURL // Get the DownloadURL
@ -44,7 +40,7 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
// Append the Subtitles // Append the Subtitles
for _, l := range m.pMovie.Subtitles { for _, l := range m.pMovie.Subtitles {
subtitleURL, _ := m.client.SubtitleURL(m.pMovie, l) subtitleURL, _ := m.client.SubtitleURL(m.pMovie, l)
subtitles = append(subtitles, Subtitle{ subs = append(subs, subtitles.Subtitle{
Language: l, Language: l,
URL: subtitleURL, URL: subtitleURL,
VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, l), VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, l),
@ -55,14 +51,14 @@ func (m *Movie) MarshalJSON() ([]byte, error) {
// Marshal the movie with its polochon_url // Marshal the movie with its polochon_url
movieToMarshal := &struct { movieToMarshal := &struct {
*Alias *Alias
PolochonURL string `json:"polochon_url"` PolochonURL string `json:"polochon_url"`
PosterURL string `json:"poster_url"` PosterURL string `json:"poster_url"`
Subtitles []Subtitle `json:"subtitles"` Subtitles []subtitles.Subtitle `json:"subtitles"`
}{ }{
Alias: (*Alias)(m), Alias: (*Alias)(m),
PolochonURL: downloadURL, PolochonURL: downloadURL,
PosterURL: m.PosterURL(), PosterURL: m.PosterURL(),
Subtitles: subtitles, Subtitles: subs,
} }
return json.Marshal(movieToMarshal) return json.Marshal(movieToMarshal)

View File

@ -9,6 +9,7 @@ import (
polochon "github.com/odwrtw/polochon/lib" polochon "github.com/odwrtw/polochon/lib"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/backend"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/subtitles"
"gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web" "gitlab.quimbo.fr/odwrtw/canape-sql/src/internal/web"
) )
@ -24,12 +25,7 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
type alias Episode type alias Episode
var downloadURL string var downloadURL string
type Subtitle struct { var subs []subtitles.Subtitle
Language string `json:"language"`
URL string `json:"url"`
VVTFile string `json:"vvt_file"`
}
var subtitles []Subtitle
// If the episode is present, fill the downloadURL // If the episode is present, fill the downloadURL
if e.pShow != nil { if e.pShow != nil {
pEpisode := e.pShow.GetEpisode(e.Season, e.Episode) pEpisode := e.pShow.GetEpisode(e.Season, e.Episode)
@ -45,7 +41,7 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
// Append the Subtitles // Append the Subtitles
for _, l := range pEpisode.Subtitles { for _, l := range pEpisode.Subtitles {
subtitleURL, _ := e.client.SubtitleURL(pEpisode, l) subtitleURL, _ := e.client.SubtitleURL(pEpisode, l)
subtitles = append(subtitles, Subtitle{ subs = append(subs, subtitles.Subtitle{
Language: l, Language: l,
URL: subtitleURL, URL: subtitleURL,
VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, l), VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, l),
@ -57,12 +53,12 @@ func (e *Episode) MarshalJSON() ([]byte, error) {
// Marshal the episode with its polochon_url // Marshal the episode with its polochon_url
episodeToMarshal := &struct { episodeToMarshal := &struct {
*alias *alias
PolochonURL string `json:"polochon_url"` PolochonURL string `json:"polochon_url"`
Subtitles []Subtitle `json:"subtitles"` Subtitles []subtitles.Subtitle `json:"subtitles"`
}{ }{
alias: (*alias)(e), alias: (*alias)(e),
PolochonURL: downloadURL, PolochonURL: downloadURL,
Subtitles: subtitles, Subtitles: subs,
} }
return json.Marshal(episodeToMarshal) return json.Marshal(episodeToMarshal)

View File

@ -374,16 +374,28 @@ func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http
return env.RenderError(w, err) return env.RenderError(w, err)
} }
err = client.UpdateSubtitles(&papi.Episode{ e := &papi.Episode{
ShowImdbID: id, ShowImdbID: id,
Season: season, Season: season,
Episode: episode, Episode: episode,
}) }
refreshedSubs, err := client.UpdateSubtitles(e)
if err != nil { if err != nil {
return env.RenderError(w, err) return env.RenderError(w, err)
} }
return env.RenderOK(w, "Subtitles refreshed") subs := []subtitles.Subtitle{}
for _, lang := range refreshedSubs {
subtitleURL, _ := client.SubtitleURL(e, lang)
subs = append(subs, subtitles.Subtitle{
Language: lang,
URL: subtitleURL,
VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, lang),
})
}
return env.RenderJSON(w, subs)
} }
// DownloadVVTSubtitle returns a vvt subtitle for the movie // DownloadVVTSubtitle returns a vvt subtitle for the movie

View File

@ -0,0 +1,8 @@
package subtitles
// Subtitle represents a Subtitle
type Subtitle struct {
Language string `json:"language"`
URL string `json:"url"`
VVTFile string `json:"vvt_file"`
}

View File

@ -0,0 +1,36 @@
import { configureAxios, request } from '../requests'
import { addAlertOk } from './alerts'
export function refreshSubtitles(type, id, season, episode) {
switch (type) {
case 'movie':
var resourceURL = `/movies/${id}`
return request(
'MOVIE_SUBTITLES_UPDATE',
configureAxios().post(`${resourceURL}/subtitles/refresh`),
[
addAlertOk("Subtitles refreshed"),
],
{
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"),
],
{
imdb_id: id,
season: season,
episode: episode,
},
)
default:
console.log("refreshSubtitles - Unknown type " + type)
}
}

View File

@ -0,0 +1,97 @@
import React from 'react'
import { DropdownButton, MenuItem } from 'react-bootstrap'
export default class SubtitlesButton extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e, url) {
e.preventDefault();
if (this.props.fetching) {
return
}
// Refresh the subtitles
this.props.refreshSubtitles(this.props.type, this.props.resourceID, this.props.data.season, this.props.data.episode);
}
render() {
// If there is no URL, the resource is not in polochon, we won't be able to download subtitles
if (this.props.url === "") {
return null;
}
// Build the button
const entries = buildMenuItems(this.props.subtitles);
let btnSize = "small";
if (this.props.xs) {
btnSize = "xsmall";
}
return (
<DropdownButton bsStyle="success" bsSize={btnSize} title="Subtitles" id="download-subtitles-button" dropup>
{entries.map(function(e, index) {
switch (e.type) {
case 'action':
return (
<MenuItem key={index} onClick={(event) => this.handleClick(event, e.url)}>
{this.props.fetchingSubtitles ||
<span>
<i className="fa fa-refresh">
</i> Refresh
</span>
}
{this.props.fetchingSubtitles &&
<span>
<i className="fa fa-spin fa-refresh">
</i> Refreshing
</span>
}
</MenuItem>
);
case 'divider':
return (
<MenuItem key={index} divider></MenuItem>
);
case 'entry':
return (
<MenuItem key={index} href={e.url}>
<i className="fa fa-download"></i> {e.lang}
</MenuItem>
);
}
}, this)}
</DropdownButton>
);
}
}
function buildMenuItems(subtitles) {
// Build the array of entries
let entries = [];
// Push the refresh button
entries.push({
type: "action",
value: "Refresh",
});
// If there is no subtitles, stop here
if (!subtitles) {
return entries;
}
// Push the divider
entries.push({ type: "divider" });
// Push the subtitles
for (let sub of subtitles) {
entries.push({
type: "entry",
// Take only the last part of fr_FR
lang: sub.language.split("_")[1],
url: sub.url,
});
}
return entries;
}

View File

@ -2,10 +2,12 @@ import React from 'react'
import { connect } from 'react-redux' 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 { addMovieToWishlist, deleteMovieFromWishlist, import { addMovieToWishlist, deleteMovieFromWishlist,
getMovieDetails, selectMovie } from '../../actions/movies' getMovieDetails, selectMovie } from '../../actions/movies'
import DownloadButton from '../buttons/download' import DownloadButton from '../buttons/download'
import SubtitlesButton from '../buttons/subtitles'
import TorrentsButton from './torrents' import TorrentsButton from './torrents'
import ActionsButton from './actions' import ActionsButton from './actions'
import ListPosters from '../list/posters' import ListPosters from '../list/posters'
@ -16,7 +18,7 @@ function mapStateToProps(state) {
} }
const mapDispatchToProps = (dipatch) => const mapDispatchToProps = (dipatch) =>
bindActionCreators({ selectMovie, getMovieDetails, addTorrent, bindActionCreators({ selectMovie, getMovieDetails, addTorrent,
addMovieToWishlist, deleteMovieFromWishlist }, dipatch) addMovieToWishlist, deleteMovieFromWishlist, refreshSubtitles }, 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.imdb_id}`;
@ -48,6 +50,15 @@ function MovieButtons(props) {
subtitles={props.movie.subtitles} subtitles={props.movie.subtitles}
/> />
<SubtitlesButton
url={props.movie.polochon_url}
subtitles={props.movie.subtitles}
refreshSubtitles={props.refreshSubtitles}
resourceID={props.movie.imdb_id}
data={props.movie}
type="movie"
/>
<a type="button" className="btn btn-warning btn-sm" href={imdb_link}> <a type="button" className="btn btn-warning btn-sm" href={imdb_link}>
<i className="fa fa-external-link"></i> IMDB <i className="fa fa-external-link"></i> IMDB
</a> </a>
@ -96,6 +107,7 @@ class MovieList extends React.Component {
addToWishlist={this.props.addMovieToWishlist} addToWishlist={this.props.addMovieToWishlist}
deleteFromWishlist={this.props.deleteMovieFromWishlist} deleteFromWishlist={this.props.deleteMovieFromWishlist}
lastFetchUrl={this.props.movieStore.lastFetchUrl} lastFetchUrl={this.props.movieStore.lastFetchUrl}
refreshSubtitles={this.props.refreshSubtitles}
/> />
</ListDetails> </ListDetails>
} }

View File

@ -2,11 +2,13 @@ import React from 'react'
import { connect } from 'react-redux' 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 { addShowToWishlist, deleteFromWishlist, getEpisodeDetails, import { addShowToWishlist, deleteFromWishlist, getEpisodeDetails,
updateEpisodeDetailsStore, updateShowDetails } from '../../actions/shows' updateEpisodeDetailsStore, updateShowDetails } from '../../actions/shows'
import Loader from '../loader/loader' import Loader from '../loader/loader'
import DownloadButton from '../buttons/download' import DownloadButton from '../buttons/download'
import SubtitlesButton from '../buttons/subtitles'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' import { OverlayTrigger, Tooltip } from 'react-bootstrap'
@ -18,7 +20,8 @@ function mapStateToProps(state) {
} }
const mapDispatchToProps = (dispatch) => const mapDispatchToProps = (dispatch) =>
bindActionCreators({addTorrent, addShowToWishlist, deleteFromWishlist, bindActionCreators({addTorrent, addShowToWishlist, deleteFromWishlist,
updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails }, dispatch) updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails,
refreshSubtitles }, dispatch)
class ShowDetails extends React.Component { class ShowDetails extends React.Component {
render() { render() {
@ -39,6 +42,7 @@ class ShowDetails extends React.Component {
addToWishlist={this.props.addShowToWishlist} addToWishlist={this.props.addShowToWishlist}
getEpisodeDetails={this.props.getEpisodeDetails} getEpisodeDetails={this.props.getEpisodeDetails}
updateEpisodeDetailsStore={this.props.updateEpisodeDetailsStore} updateEpisodeDetailsStore={this.props.updateEpisodeDetailsStore}
refreshSubtitles={this.props.refreshSubtitles}
/> />
</div> </div>
); );
@ -113,6 +117,7 @@ function SeasonsList(props){
addToWishlist={props.addToWishlist} addToWishlist={props.addToWishlist}
getEpisodeDetails={props.getEpisodeDetails} getEpisodeDetails={props.getEpisodeDetails}
updateEpisodeDetailsStore={props.updateEpisodeDetailsStore} updateEpisodeDetailsStore={props.updateEpisodeDetailsStore}
refreshSubtitles={props.refreshSubtitles}
/> />
</div> </div>
) )
@ -159,6 +164,7 @@ class Season extends React.Component {
addToWishlist={this.props.addToWishlist} addToWishlist={this.props.addToWishlist}
getEpisodeDetails={this.props.getEpisodeDetails} getEpisodeDetails={this.props.getEpisodeDetails}
updateEpisodeDetailsStore={this.props.updateEpisodeDetailsStore} updateEpisodeDetailsStore={this.props.updateEpisodeDetailsStore}
refreshSubtitles={this.props.refreshSubtitles}
/> />
) )
}, this)} }, this)}
@ -181,8 +187,17 @@ function Episode(props) {
{props.data.episode} {props.data.episode}
</th> </th>
<td className="col-xs-5">{props.data.title}</td> <td className="col-xs-5">{props.data.title}</td>
<td className="col-xs-6"> <td className="col-xs-6 list-details-button">
<span className="pull-right"> <span className="pull-right episode-buttons btn-toolbar">
<SubtitlesButton
url={props.data.polochon_url}
subtitles={props.data.subtitles}
refreshSubtitles={props.refreshSubtitles}
resourceID={props.data.show_imdb_id}
data={props.data}
type="episode"
xs
/>
{props.data.torrents && props.data.torrents.map(function(torrent, index) { {props.data.torrents && props.data.torrents.map(function(torrent, index) {
let key = `${props.data.season}-${props.data.episode}-${torrent.source}-${torrent.quality}`; let key = `${props.data.season}-${props.data.episode}-${torrent.source}-${torrent.quality}`;
return ( return (
@ -196,7 +211,6 @@ function Episode(props) {
<DownloadButton <DownloadButton
url={props.data.polochon_url} url={props.data.polochon_url}
subtitles={props.data.subtitles} subtitles={props.data.subtitles}
customClassName="episode-button"
xs xs
/> />
<GetDetailsButton <GetDetailsButton
@ -221,7 +235,7 @@ class Torrent extends React.Component {
} }
render() { render() {
return ( return (
<span className="episode-button"> <span>
<a type="button" <a type="button"
className="btn btn-primary btn-xs" className="btn btn-primary btn-xs"
onClick={(e) => this.handleClick(e, this.props.data.url)} onClick={(e) => this.handleClick(e, this.props.data.url)}
@ -306,7 +320,7 @@ class TrackButton extends React.Component {
<Tooltip id={tooltipId}>Track show from here</Tooltip> <Tooltip id={tooltipId}>Track show from here</Tooltip>
); );
return ( return (
<OverlayTrigger placement="top" overlay={tooltip} className="episode-button"> <OverlayTrigger placement="top" overlay={tooltip}>
<a type="button" className="btn btn-default btn-xs" onClick={(e) => this.handleClick(e)}> <a type="button" className="btn btn-default btn-xs" onClick={(e) => this.handleClick(e)}>
<i className="fa fa-bookmark"></i> <i className="fa fa-bookmark"></i>
</a> </a>
@ -326,24 +340,23 @@ class GetDetailsButton extends React.Component {
return return
} }
this.props.updateEpisodeDetailsStore(this.props.data.show_imdb_id, this.props.data.season, this.props.data.episode); 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); this.props.getEpisodeDetails(this.props.data.show_imdb_id, this.props.data.season, this.props.data.episode);
} }
render() { render() {
return ( return (
<span className="episode-button"> <a type="button" className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}>
<a type="button" className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}> {this.props.data.fetching ||
{this.props.data.fetching || <span>
<span> <i className="fa fa-refresh"></i> Refresh
<i className="fa fa-refresh"></i> Refresh </span>
</span> }
} {this.props.data.fetching &&
{this.props.data.fetching && <span>
<span> <i className="fa fa-spin fa-refresh"></i> Refreshing
<i className="fa fa-spin fa-refresh"></i> Refreshing </span>
</span> }
} </a>
</a>
</span>
); );
} }
} }

View File

@ -18,13 +18,13 @@ export default function movieStore(state = defaultState, action) {
case 'MOVIE_LIST_FETCH_FULFILLED': case 'MOVIE_LIST_FETCH_FULFILLED':
let selectedImdbId = ""; let selectedImdbId = "";
// Select the first movie // Select the first movie
if (action.payload.data.length > 0) { if (action.payload.response.data.length > 0) {
// Sort by year // Sort by year
action.payload.data.sort((a,b) => b.year - a.year); action.payload.response.data.sort((a,b) => b.year - a.year);
selectedImdbId = action.payload.data[0].imdb_id; selectedImdbId = action.payload.response.data[0].imdb_id;
} }
return Object.assign({}, state, { return Object.assign({}, state, {
movies: action.payload.data, movies: action.payload.response.data,
selectedImdbId: selectedImdbId, selectedImdbId: selectedImdbId,
filter: defaultState.filter, filter: defaultState.filter,
perPage: defaultState.perPage, perPage: defaultState.perPage,
@ -36,7 +36,7 @@ export default function movieStore(state = defaultState, action) {
}) })
case 'MOVIE_GET_DETAILS_FULFILLED': case 'MOVIE_GET_DETAILS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
movies: updateMovieDetails(state.movies.slice(), action.payload.data.imdb_id, action.payload.data), movies: updateMovieDetails(state.movies.slice(), action.payload.response.data.imdb_id, action.payload.response.data),
fetchingDetails: false, fetchingDetails: false,
}) })
case 'MOVIE_UPDATE_STORE_WISHLIST': case 'MOVIE_UPDATE_STORE_WISHLIST':
@ -45,12 +45,21 @@ export default function movieStore(state = defaultState, action) {
}) })
case 'MOVIE_GET_EXPLORE_OPTIONS_FULFILLED': case 'MOVIE_GET_EXPLORE_OPTIONS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
exploreOptions: action.payload.data, exploreOptions: action.payload.response.data,
}) })
case 'UPDATE_LAST_MOVIE_FETCH_URL': case 'UPDATE_LAST_MOVIE_FETCH_URL':
return Object.assign({}, state, { return Object.assign({}, state, {
lastFetchUrl: action.payload.url, 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': case 'SELECT_MOVIE':
// Don't select the movie if we're fetching another movie's details // Don't select the movie if we're fetching another movie's details
if (state.fetchingDetails) { if (state.fetchingDetails) {
@ -76,3 +85,12 @@ function updateStoreWishlist(movies, imdbId, wishlisted) {
movies[index].wishlisted = wishlisted; movies[index].wishlisted = wishlisted;
return movies 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
}

View File

@ -21,11 +21,12 @@ export default function showStore(state = defaultState, action) {
case 'SHOW_LIST_FETCH_FULFILLED': case 'SHOW_LIST_FETCH_FULFILLED':
let selectedImdbId = ""; let selectedImdbId = "";
// Select the first show // Select the first show
if (action.payload.data.length > 0) { console.log("Hey", action.payload);
selectedImdbId = action.payload.data[0].imdb_id; if (action.payload.response.data.length > 0) {
selectedImdbId = action.payload.response.data[0].imdb_id;
} }
return Object.assign({}, state, { return Object.assign({}, state, {
shows: action.payload.data, shows: action.payload.response.data,
selectedImdbId: selectedImdbId, selectedImdbId: selectedImdbId,
filter: defaultState.filter, filter: defaultState.filter,
perPage: defaultState.perPage, perPage: defaultState.perPage,
@ -37,7 +38,7 @@ export default function showStore(state = defaultState, action) {
}) })
case 'SHOW_GET_DETAILS_FULFILLED': case 'SHOW_GET_DETAILS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
shows: updateShowDetails(state.shows.slice(), action.payload.data), shows: updateShowDetails(state.shows.slice(), action.payload.response.data),
getDetails: false, getDetails: false,
}) })
case 'SHOW_FETCH_DETAILS_PENDING': case 'SHOW_FETCH_DETAILS_PENDING':
@ -46,7 +47,7 @@ export default function showStore(state = defaultState, action) {
}) })
case 'SHOW_FETCH_DETAILS_FULFILLED': case 'SHOW_FETCH_DETAILS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
show: sortEpisodes(action.payload.data), show: sortEpisodes(action.payload.response.data),
loading: false, loading: false,
}) })
case 'EPISODE_GET_DETAILS': case 'EPISODE_GET_DETAILS':
@ -55,7 +56,7 @@ export default function showStore(state = defaultState, action) {
}) })
case 'EPISODE_GET_DETAILS_FULFILLED': case 'EPISODE_GET_DETAILS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
show: updateEpisode(Object.assign({}, state.show), false, action.payload.data), show: updateEpisode(Object.assign({}, state.show), false, action.payload.response.data),
}) })
case 'EXPLORE_SHOWS_PENDING': case 'EXPLORE_SHOWS_PENDING':
return Object.assign({}, state, { return Object.assign({}, state, {
@ -63,12 +64,12 @@ export default function showStore(state = defaultState, action) {
}) })
case 'EXPLORE_SHOWS_FULFILLED': case 'EXPLORE_SHOWS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
shows: action.payload.data, shows: action.payload.response.data,
loading: false, loading: false,
}) })
case 'SHOW_GET_EXPLORE_OPTIONS_FULFILLED': case 'SHOW_GET_EXPLORE_OPTIONS_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
exploreOptions: action.payload.data, exploreOptions: action.payload.response.data,
}) })
case 'SHOW_UPDATE_STORE_WISHLIST': case 'SHOW_UPDATE_STORE_WISHLIST':
return Object.assign({}, state, { return Object.assign({}, state, {
@ -79,6 +80,15 @@ export default function showStore(state = defaultState, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
lastShowsFetchUrl: action.payload.url, 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': case 'SELECT_SHOW':
// Don't select the show if we're fetching another show's details // Don't select the show if we're fetching another show's details
if (state.fetchingDetails) { if (state.fetchingDetails) {
@ -113,6 +123,17 @@ function updateEpisode(show, fetching, data = null) {
return show 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) { function sortEpisodes(show) {
let episodes = show.episodes; let episodes = show.episodes;
delete show["episodes"]; delete show["episodes"];

View File

@ -12,7 +12,7 @@ export default function torrentStore(state = defaultState, action) {
case 'TORRENTS_FETCH_FULFILLED': case 'TORRENTS_FETCH_FULFILLED':
return state.merge(fromJS({ return state.merge(fromJS({
fetching: false, fetching: false,
torrents: action.payload.data, torrents: action.payload.response.data,
})); }));
default: default:
return state return state

View File

@ -17,18 +17,18 @@ export default function userStore(state = defaultState, action) {
userLoading: true, userLoading: true,
}) })
case 'USER_LOGIN_FULFILLED': case 'USER_LOGIN_FULFILLED':
if (action.payload.status === "error") { if (action.payload.response.status === "error") {
return logoutUser(state) return logoutUser(state)
} }
return updateFromToken(state, action.payload.data.token) return updateFromToken(state, action.payload.response.data.token)
case 'USER_SET_TOKEN': case 'USER_SET_TOKEN':
return updateFromToken(state, action.payload.token) return updateFromToken(state, action.payload.token)
case 'USER_LOGOUT': case 'USER_LOGOUT':
return logoutUser(state) return logoutUser(state)
case 'GET_USER_FULFILLED': case 'GET_USER_FULFILLED':
return Object.assign({}, state, { return Object.assign({}, state, {
polochonToken: action.payload.data.token, polochonToken: action.payload.response.data.token,
polochonUrl: action.payload.data.url, polochonUrl: action.payload.response.data.url,
}) })
default: default:
return state; return state;

View File

@ -16,7 +16,7 @@ export function configureAxios(headers = {}) {
// This function takes en event prefix to dispatch evens during the life of the // This function takes en event prefix to dispatch evens during the life of the
// request, it also take a promise (axios request) // request, it also take a promise (axios request)
export function request(eventPrefix, promise, callbackEvents = null) { export function request(eventPrefix, promise, callbackEvents = null, mainPayload = null) {
// Events // Events
const pending = `${eventPrefix}_PENDING`; const pending = `${eventPrefix}_PENDING`;
const fulfilled = `${eventPrefix}_FULFILLED`; const fulfilled = `${eventPrefix}_FULFILLED`;
@ -24,6 +24,9 @@ export function request(eventPrefix, promise, callbackEvents = null) {
return function(dispatch) { return function(dispatch) {
dispatch({ dispatch({
type: pending, type: pending,
payload: {
main: mainPayload,
}
}) })
promise promise
.then(response => { .then(response => {
@ -33,12 +36,16 @@ export function request(eventPrefix, promise, callbackEvents = null) {
type: 'ADD_ALERT_ERROR', type: 'ADD_ALERT_ERROR',
payload: { payload: {
message: response.data.message, message: response.data.message,
main: mainPayload,
} }
}) })
} }
dispatch({ dispatch({
type: fulfilled, type: fulfilled,
payload: response.data, payload: {
response: response.data,
main: mainPayload,
},
}) })
if (callbackEvents) { if (callbackEvents) {
for (let event of callbackEvents) { for (let event of callbackEvents) {
@ -57,6 +64,7 @@ export function request(eventPrefix, promise, callbackEvents = null) {
type: 'ADD_ALERT_ERROR', type: 'ADD_ALERT_ERROR',
payload: { payload: {
message: error.response.data, message: error.response.data,
main: mainPayload,
} }
}) })
}) })

View File

@ -46,8 +46,11 @@ body {
max-height: 300px; max-height: 300px;
} }
.episode-button { .episode-buttons {
padding-right: 5px; display: flex;
span {
margin-left: 5px;
}
} }
.navbar { .navbar {