commit
44d953e566
23
.eslintrc
Normal file
23
.eslintrc
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"settings": {
|
||||
"ecmascript": 6,
|
||||
"jsx": true
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"strict": 1,
|
||||
"quotes": 1,
|
||||
"no-unused-vars": 1,
|
||||
"camelcase": 1,
|
||||
"no-underscore-dangle": 1,
|
||||
"react/jsx-uses-react": 1,
|
||||
"react/jsx-uses-vars": 1
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@
|
||||
"react-infinite-scroller": "^1.0.4",
|
||||
"react-loading": "^0.0.9",
|
||||
"react-redux": "^4.4.6",
|
||||
"react-redux-form": "^1.2.4",
|
||||
"react-router": "^3.0.0",
|
||||
"react-router-bootstrap": "^0.23.1",
|
||||
"react-router-redux": "^4.0.7",
|
||||
@ -34,12 +33,15 @@
|
||||
"axios": "^0.15.2",
|
||||
"babel": "^6.5.2",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-eslint": "^7.2.3",
|
||||
"babel-loader": "^6.2.7",
|
||||
"babel-preset-es2015": "^6.18.0",
|
||||
"babel-preset-latest": "^6.16.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"css-loader": "^0.26.0",
|
||||
"del": "^2.2.2",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-react": "^7.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^6.1.2",
|
||||
|
@ -1,6 +1,6 @@
|
||||
export function addAlertError(message) {
|
||||
return {
|
||||
type: 'ADD_ALERT_ERROR',
|
||||
type: "ADD_ALERT_ERROR",
|
||||
payload: {
|
||||
message,
|
||||
}
|
||||
@ -9,7 +9,7 @@ export function addAlertError(message) {
|
||||
|
||||
export function addAlertOk(message) {
|
||||
return {
|
||||
type: 'ADD_ALERT_OK',
|
||||
type: "ADD_ALERT_OK",
|
||||
payload: {
|
||||
message,
|
||||
}
|
||||
@ -18,6 +18,6 @@ export function addAlertOk(message) {
|
||||
|
||||
export function dismissAlert() {
|
||||
return {
|
||||
type: 'DISMISS_ALERT',
|
||||
type: "DISMISS_ALERT",
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
import { configureAxios, request } from "../requests"
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
import { addAlertOk } from "./alerts"
|
||||
|
||||
export function updateLastMovieFetchUrl(url) {
|
||||
return {
|
||||
type: 'UPDATE_LAST_MOVIE_FETCH_URL',
|
||||
type: "UPDATE_LAST_MOVIE_FETCH_URL",
|
||||
payload: {
|
||||
url: url,
|
||||
},
|
||||
@ -13,28 +13,43 @@ export function updateLastMovieFetchUrl(url) {
|
||||
|
||||
export function selectMovie(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_MOVIE',
|
||||
imdbId
|
||||
type: "SELECT_MOVIE",
|
||||
payload: {
|
||||
imdbId,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function updateFilter(filter) {
|
||||
return {
|
||||
type: "MOVIE_UPDATE_FILTER",
|
||||
payload: {
|
||||
filter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function getMovieExploreOptions() {
|
||||
return request(
|
||||
'MOVIE_GET_EXPLORE_OPTIONS',
|
||||
configureAxios().get('/movies/explore/options')
|
||||
"MOVIE_GET_EXPLORE_OPTIONS",
|
||||
configureAxios().get("/movies/explore/options")
|
||||
)
|
||||
}
|
||||
|
||||
export function getMovieDetails(imdbId) {
|
||||
return request(
|
||||
'MOVIE_GET_DETAILS',
|
||||
configureAxios().post(`/movies/${imdbId}/refresh`)
|
||||
"MOVIE_GET_DETAILS",
|
||||
configureAxios().post(`/movies/${imdbId}/refresh`),
|
||||
null,
|
||||
{
|
||||
imdbId,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteMovie(imdbId, lastFetchUrl) {
|
||||
return request(
|
||||
'MOVIE_DELETE',
|
||||
"MOVIE_DELETE",
|
||||
configureAxios().delete(`/movies/${imdbId}`),
|
||||
[
|
||||
fetchMovies(lastFetchUrl),
|
||||
@ -45,10 +60,9 @@ export function deleteMovie(imdbId, lastFetchUrl) {
|
||||
|
||||
export function addMovieToWishlist(imdbId) {
|
||||
return request(
|
||||
'MOVIE_ADD_TO_WISHLIST',
|
||||
"MOVIE_ADD_TO_WISHLIST",
|
||||
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Movie added to the wishlist"),
|
||||
updateMovieWishlistStore(imdbId, true),
|
||||
],
|
||||
)
|
||||
@ -56,10 +70,9 @@ export function addMovieToWishlist(imdbId) {
|
||||
|
||||
export function deleteMovieFromWishlist(imdbId) {
|
||||
return request(
|
||||
'MOVIE_DELETE_FROM_WISHLIST',
|
||||
"MOVIE_DELETE_FROM_WISHLIST",
|
||||
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Movie deleted from the wishlist"),
|
||||
updateMovieWishlistStore(imdbId, false),
|
||||
],
|
||||
)
|
||||
@ -67,7 +80,7 @@ export function deleteMovieFromWishlist(imdbId) {
|
||||
|
||||
export function updateMovieWishlistStore(imdbId, wishlisted) {
|
||||
return {
|
||||
type: 'MOVIE_UPDATE_STORE_WISHLIST',
|
||||
type: "MOVIE_UPDATE_STORE_WISHLIST",
|
||||
payload: {
|
||||
imdbId,
|
||||
wishlisted,
|
||||
@ -77,7 +90,7 @@ export function updateMovieWishlistStore(imdbId, wishlisted) {
|
||||
|
||||
export function fetchMovies(url) {
|
||||
return request(
|
||||
'MOVIE_LIST_FETCH',
|
||||
"MOVIE_LIST_FETCH",
|
||||
configureAxios().get(url),
|
||||
[
|
||||
updateLastMovieFetchUrl(url),
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
import { configureAxios, request } from "../requests"
|
||||
|
||||
export function fetchShows(url) {
|
||||
return request(
|
||||
'SHOW_LIST_FETCH',
|
||||
"SHOW_LIST_FETCH",
|
||||
configureAxios().get(url),
|
||||
[
|
||||
updateLastShowsFetchUrl(url),
|
||||
@ -14,15 +12,17 @@ export function fetchShows(url) {
|
||||
|
||||
export function getShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_GET_DETAILS',
|
||||
configureAxios().post(`/shows/${imdbId}/refresh`)
|
||||
"SHOW_GET_DETAILS",
|
||||
configureAxios().post(`/shows/${imdbId}/refresh`),
|
||||
null,
|
||||
{ imdbId }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function getEpisodeDetails(imdbId, season, episode) {
|
||||
return request(
|
||||
'EPISODE_GET_DETAILS',
|
||||
"EPISODE_GET_DETAILS",
|
||||
configureAxios().post(`/shows/${imdbId}/seasons/${season}/episodes/${episode}`),
|
||||
null,
|
||||
{
|
||||
@ -35,20 +35,21 @@ export function getEpisodeDetails(imdbId, season, episode) {
|
||||
|
||||
export function fetchShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_FETCH_DETAILS',
|
||||
configureAxios().get(`/shows/${imdbId}`)
|
||||
"SHOW_FETCH_DETAILS",
|
||||
configureAxios().get(`/shows/${imdbId}`),
|
||||
null,
|
||||
{ imdbId }
|
||||
)
|
||||
}
|
||||
|
||||
export function addShowToWishlist(imdbId, season = null, episode = null) {
|
||||
return request(
|
||||
'SHOW_ADD_TO_WISHLIST',
|
||||
"SHOW_ADD_TO_WISHLIST",
|
||||
configureAxios().post(`/wishlist/shows/${imdbId}`, {
|
||||
season: season,
|
||||
episode: episode,
|
||||
}),
|
||||
[
|
||||
addAlertOk("Show added to the wishlist"),
|
||||
updateShowWishlistStore(imdbId, true, season, episode),
|
||||
],
|
||||
)
|
||||
@ -56,10 +57,9 @@ export function addShowToWishlist(imdbId, season = null, episode = null) {
|
||||
|
||||
export function deleteShowFromWishlist(imdbId) {
|
||||
return request(
|
||||
'SHOW_DELETE_FROM_WISHLIST',
|
||||
"SHOW_DELETE_FROM_WISHLIST",
|
||||
configureAxios().delete(`/wishlist/shows/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Show deleted from the wishlist"),
|
||||
updateShowWishlistStore(imdbId, false),
|
||||
],
|
||||
)
|
||||
@ -67,7 +67,7 @@ export function deleteShowFromWishlist(imdbId) {
|
||||
|
||||
export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) {
|
||||
return {
|
||||
type: 'SHOW_UPDATE_STORE_WISHLIST',
|
||||
type: "SHOW_UPDATE_STORE_WISHLIST",
|
||||
payload: {
|
||||
wishlisted: wishlisted,
|
||||
imdbId,
|
||||
@ -79,21 +79,33 @@ export function updateShowWishlistStore(imdbId, wishlisted, season = null, episo
|
||||
|
||||
export function getShowExploreOptions() {
|
||||
return request(
|
||||
'SHOW_GET_EXPLORE_OPTIONS',
|
||||
configureAxios().get('/shows/explore/options')
|
||||
"SHOW_GET_EXPLORE_OPTIONS",
|
||||
configureAxios().get("/shows/explore/options")
|
||||
)
|
||||
}
|
||||
|
||||
export function selectShow(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_SHOW',
|
||||
imdbId
|
||||
type: "SELECT_SHOW",
|
||||
payload: {
|
||||
imdbId,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function updateFilter(filter) {
|
||||
return {
|
||||
type: "SHOWS_UPDATE_FILTER",
|
||||
payload: {
|
||||
filter,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function updateLastShowsFetchUrl(url) {
|
||||
return {
|
||||
type: 'UPDATE_LAST_SHOWS_FETCH_URL',
|
||||
type: "UPDATE_LAST_SHOWS_FETCH_URL",
|
||||
payload: {
|
||||
url: url,
|
||||
},
|
||||
|
@ -1,30 +1,28 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
import { configureAxios, request } from "../requests"
|
||||
|
||||
export function refreshSubtitles(type, id, season, episode) {
|
||||
switch (type) {
|
||||
case 'movie':
|
||||
case "movie":
|
||||
var resourceURL = `/movies/${id}`
|
||||
return request(
|
||||
'MOVIE_SUBTITLES_UPDATE',
|
||||
"MOVIE_SUBTITLES_UPDATE",
|
||||
configureAxios().post(`${resourceURL}/subtitles/refresh`),
|
||||
null,
|
||||
{ imdb_id: id },
|
||||
{ imdbId: id },
|
||||
)
|
||||
case 'episode':
|
||||
case "episode":
|
||||
var resourceURL = `/shows/${id}/seasons/${season}/episodes/${episode}`
|
||||
return request(
|
||||
'EPISODE_SUBTITLES_UPDATE',
|
||||
"EPISODE_SUBTITLES_UPDATE",
|
||||
configureAxios().post(`${resourceURL}/subtitles/refresh`),
|
||||
null,
|
||||
{
|
||||
imdb_id: id,
|
||||
imdbId: id,
|
||||
season: season,
|
||||
episode: episode,
|
||||
},
|
||||
)
|
||||
default:
|
||||
console.log("refreshSubtitles - Unknown type " + type)
|
||||
console.warn("refreshSubtitles - Unknown type " + type)
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
import { configureAxios, request } from "../requests"
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
import { addAlertOk } from "./alerts"
|
||||
|
||||
export function addTorrent(url) {
|
||||
return request(
|
||||
'ADD_TORRENT',
|
||||
configureAxios().post('/torrents', {
|
||||
"ADD_TORRENT",
|
||||
configureAxios().post("/torrents", {
|
||||
url: url,
|
||||
}),
|
||||
[
|
||||
@ -16,7 +16,7 @@ export function addTorrent(url) {
|
||||
|
||||
export function fetchTorrents() {
|
||||
return request(
|
||||
'TORRENTS_FETCH',
|
||||
configureAxios().get('/torrents')
|
||||
"TORRENTS_FETCH",
|
||||
configureAxios().get("/torrents")
|
||||
)
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
import { configureAxios, request } from "../requests"
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
import { addAlertOk } from "./alerts"
|
||||
|
||||
export function userLogout() {
|
||||
return {
|
||||
type: 'USER_LOGOUT',
|
||||
type: "USER_LOGOUT",
|
||||
}
|
||||
}
|
||||
|
||||
export function loginUser(username, password) {
|
||||
return request(
|
||||
'USER_LOGIN',
|
||||
"USER_LOGIN",
|
||||
configureAxios().post(
|
||||
'/users/login',
|
||||
"/users/login",
|
||||
{
|
||||
username: username,
|
||||
password: password,
|
||||
@ -23,8 +23,8 @@ export function loginUser(username, password) {
|
||||
|
||||
export function updateUser(config) {
|
||||
return request(
|
||||
'USER_UPDATE',
|
||||
configureAxios().post('/users/edit', config),
|
||||
"USER_UPDATE",
|
||||
configureAxios().post("/users/edit", config),
|
||||
[
|
||||
addAlertOk("User updated"),
|
||||
],
|
||||
@ -33,14 +33,14 @@ export function updateUser(config) {
|
||||
|
||||
export function userSignUp(config) {
|
||||
return request(
|
||||
'USER_SIGNUP',
|
||||
configureAxios().post('/users/signup', config)
|
||||
"USER_SIGNUP",
|
||||
configureAxios().post("/users/signup", config)
|
||||
)
|
||||
}
|
||||
|
||||
export function getUserInfos() {
|
||||
return request(
|
||||
'GET_USER',
|
||||
configureAxios().get('/users/details')
|
||||
"GET_USER",
|
||||
configureAxios().get("/users/details")
|
||||
)
|
||||
}
|
||||
|
@ -1,52 +1,51 @@
|
||||
// Html page
|
||||
import 'file-loader?name=[name].[ext]!../index.html'
|
||||
import "file-loader?name=[name].[ext]!../index.html"
|
||||
|
||||
// Import default image
|
||||
import 'file-loader?name=img/[name].png!../img/noimage.png'
|
||||
import "file-loader?name=img/[name].png!../img/noimage.png"
|
||||
|
||||
// Import favicon settings
|
||||
import 'file-loader?name=[name].png!../img/android-chrome-192x192.png'
|
||||
import 'file-loader?name=[name].png!../img/android-chrome-512x512.png'
|
||||
import 'file-loader?name=[name].png!../img/apple-touch-icon.png'
|
||||
import 'file-loader?name=[name].png!../img/favicon-16x16.png'
|
||||
import 'file-loader?name=[name].png!../img/favicon-32x32.png'
|
||||
import 'file-loader?name=[name].png!../img/favicon.ico'
|
||||
import 'file-loader?name=[name].png!../img/safari-pinned-tab.svg'
|
||||
import "file-loader?name=[name].png!../img/android-chrome-192x192.png"
|
||||
import "file-loader?name=[name].png!../img/android-chrome-512x512.png"
|
||||
import "file-loader?name=[name].png!../img/apple-touch-icon.png"
|
||||
import "file-loader?name=[name].png!../img/favicon-16x16.png"
|
||||
import "file-loader?name=[name].png!../img/favicon-32x32.png"
|
||||
import "file-loader?name=[name].png!../img/favicon.ico"
|
||||
import "file-loader?name=[name].png!../img/safari-pinned-tab.svg"
|
||||
|
||||
// Import manifest
|
||||
import 'file-loader?name=[name].json!../manifest.json'
|
||||
import "file-loader?name=[name].json!../manifest.json"
|
||||
|
||||
// Styles
|
||||
import '../less/app.less'
|
||||
import "../less/app.less"
|
||||
|
||||
// React
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Provider, connect } from 'react-redux'
|
||||
import { Router } from 'react-router'
|
||||
import { routerActions } from 'react-router-redux'
|
||||
import React from "react"
|
||||
import ReactDOM from "react-dom"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { Provider, connect } from "react-redux"
|
||||
import { Router } from "react-router"
|
||||
|
||||
// Action creators
|
||||
import { dismissAlert } from './actions/alerts'
|
||||
import { dismissAlert } from "./actions/alerts"
|
||||
|
||||
// Store
|
||||
import store, { history } from './store'
|
||||
import store, { history } from "./store"
|
||||
|
||||
// Components
|
||||
import NavBar from './components/navbar'
|
||||
import Alert from './components/alerts/alert'
|
||||
import NavBar from "./components/navbar"
|
||||
import Alert from "./components/alerts/alert"
|
||||
|
||||
// Routes
|
||||
import getRoutes from './routes'
|
||||
import getRoutes from "./routes"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
let torrentCount = 0;
|
||||
if (state.torrentStore.has('torrents')) {
|
||||
torrentCount = state.torrentStore.get('torrents').size;
|
||||
if (state.torrentStore.has("torrents") && state.torrentStore.get("torrents") !== undefined) {
|
||||
torrentCount = state.torrentStore.get("torrents").size;
|
||||
}
|
||||
return {
|
||||
username: state.userStore.username,
|
||||
username: state.userStore.get("username"),
|
||||
torrentCount: torrentCount,
|
||||
alerts: state.alerts,
|
||||
}
|
||||
@ -80,4 +79,4 @@ ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Router history={history} routes={getRoutes(App)} />
|
||||
</Provider>
|
||||
),document.getElementById('app'));
|
||||
),document.getElementById("app"));
|
||||
|
@ -1,17 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
import SweetAlert from 'react-bootstrap-sweetalert';
|
||||
import React from "react"
|
||||
import SweetAlert from "react-bootstrap-sweetalert";
|
||||
|
||||
export default function Alert(props) {
|
||||
if (!props.alerts.show) {
|
||||
if (!props.alerts.get("show")) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SweetAlert
|
||||
type={props.alerts.type}
|
||||
type={props.alerts.get("type")}
|
||||
title={props.alerts.get("message")}
|
||||
onConfirm={props.dismissAlert}
|
||||
title={props.alerts.message}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import { MenuItem } from 'react-bootstrap'
|
||||
import { MenuItem } from "react-bootstrap"
|
||||
|
||||
export class WishlistButton extends React.Component {
|
||||
import RefreshIndicator from "./refresh"
|
||||
|
||||
export class WishlistButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
@ -36,7 +38,7 @@ export class WishlistButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteButton extends React.Component {
|
||||
export class DeleteButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
@ -56,7 +58,7 @@ export class DeleteButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export class RefreshButton extends React.Component {
|
||||
export class RefreshButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
@ -71,16 +73,7 @@ export class RefreshButton extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<MenuItem onClick={this.handleClick}>
|
||||
{this.props.fetching ||
|
||||
<span>
|
||||
<i className="fa fa-refresh"></i> Refresh
|
||||
</span>
|
||||
}
|
||||
{this.props.fetching &&
|
||||
<span>
|
||||
<i className="fa fa-spin fa-refresh"></i> Refreshing
|
||||
</span>
|
||||
}
|
||||
<RefreshIndicator refresh={this.props.fetching} />
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import { Button, Dropdown, MenuItem, Modal } from 'react-bootstrap'
|
||||
import { Button, Dropdown, MenuItem, Modal } from "react-bootstrap"
|
||||
|
||||
export default class DownloadButton extends React.Component {
|
||||
export default class DownloadButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.showModal = this.showModal.bind(this);
|
||||
@ -66,30 +66,25 @@ export default class DownloadButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class Player extends React.Component {
|
||||
class Player extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
var subtitles = [];
|
||||
if (props.subtitles && props.subtitles.length) {
|
||||
subtitles = props.subtitles;
|
||||
}
|
||||
this.state = {
|
||||
subtitles: subtitles,
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const subtitles = this.props.subtitles;
|
||||
const hasSubtitles = !(subtitles === undefined || subtitles === null || subtitles.size === 0);
|
||||
return (
|
||||
<div className="embed-responsive embed-responsive-16by9">
|
||||
<video controls>
|
||||
<source src={this.props.url} type="video/mp4"/>
|
||||
{this.props.subtitles.map(function(el, index) {
|
||||
{hasSubtitles && subtitles.toIndexedSeq().map(function(el, index) {
|
||||
return (
|
||||
<track
|
||||
key={index}
|
||||
kind="subtitles"
|
||||
label={el.language}
|
||||
src={el.vvt_file}
|
||||
srcLang={el.language}
|
||||
label={el.get("language")}
|
||||
src={el.get("vvt_file")}
|
||||
srcLang={el.get("language")}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
15
src/public/js/components/buttons/imdb.js
Normal file
15
src/public/js/components/buttons/imdb.js
Normal file
@ -0,0 +1,15 @@
|
||||
import React from "react"
|
||||
|
||||
export default function ImdbLink(props) {
|
||||
const link = `http://www.imdb.com/title/${props.imdbId}`;
|
||||
let className = "btn btn-warning";
|
||||
if (props.size) {
|
||||
className += " btn-" + props.size;
|
||||
}
|
||||
return (
|
||||
<a type="button" className={className} href={link}>
|
||||
<i className="fa fa-external-link"></i> IMDB
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
18
src/public/js/components/buttons/refresh.js
Normal file
18
src/public/js/components/buttons/refresh.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from "react"
|
||||
|
||||
export default function RefreshIndicator(props) {
|
||||
if (!props.refresh) {
|
||||
return (
|
||||
<span>
|
||||
<i className="fa fa-refresh"></i> Refresh
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span>
|
||||
<i className="fa fa-spin fa-refresh"></i> Refreshing
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,97 +1,60 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import { DropdownButton, MenuItem } from 'react-bootstrap'
|
||||
import { DropdownButton, MenuItem } from "react-bootstrap"
|
||||
|
||||
export default class SubtitlesButton extends React.Component {
|
||||
import RefreshIndicator from "./refresh"
|
||||
|
||||
export default function SubtitlesButton(props) {
|
||||
const btnSize = props.xs ? "xsmall" : "small";
|
||||
const subtitles = props.subtitles;
|
||||
const hasSubtitles = !(subtitles === undefined || subtitles === null || subtitles.size === 0);
|
||||
return (
|
||||
<DropdownButton
|
||||
bsStyle="success"
|
||||
bsSize={btnSize}
|
||||
title="Subtitles"
|
||||
id="download-subtitles-button"
|
||||
dropup>
|
||||
<RefreshButton
|
||||
type={props.type}
|
||||
resourceID={props.resourceID}
|
||||
season={props.season}
|
||||
episode={props.episode}
|
||||
fetching={props.fetching}
|
||||
refreshSubtitles={props.refreshSubtitles}
|
||||
/>
|
||||
{hasSubtitles &&
|
||||
<MenuItem divider></MenuItem>
|
||||
}
|
||||
{hasSubtitles && subtitles.toIndexedSeq().map(function(subtitle, index) {
|
||||
return (
|
||||
<MenuItem key={index} href={subtitle.get("url")}>
|
||||
<i className="fa fa-download"></i> {subtitle.get("language").split("_")[1]}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
|
||||
class RefreshButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick(e, url) {
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
if (this.props.fetching) {
|
||||
return
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
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>
|
||||
<MenuItem onClick={this.handleClick}>
|
||||
<RefreshIndicator refresh={this.props.fetching} />
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -1,61 +1,96 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
export default function ListDetails(props) {
|
||||
let genres;
|
||||
if (props.data.genres) {
|
||||
// Uppercase first genres
|
||||
genres = props.data.genres.map(
|
||||
(word) => word[0].toUpperCase() + word.substr(1)
|
||||
).join(', ');
|
||||
}
|
||||
|
||||
let wishlistStr = "";
|
||||
if (props.data.wishlisted === true) {
|
||||
wishlistStr = "Wishlisted";
|
||||
}
|
||||
if (props.data.tracked_episode !== null && props.data.tracked_season != null) {
|
||||
let season = props.data.tracked_season;
|
||||
let episode = props.data.tracked_episode;
|
||||
if ((season === 0) && (episode === 0)) {
|
||||
wishlistStr = "Whole show tracked";
|
||||
} else {
|
||||
wishlistStr = `Tracked from season ${season} episode ${episode}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-xs-7 col-md-4">
|
||||
<div className="affix">
|
||||
<h1 className="hidden-xs">{props.data.title}</h1>
|
||||
<h3 className="visible-xs">{props.data.title}</h3>
|
||||
{wishlistStr !== "" &&
|
||||
<span className="label label-default">
|
||||
<i className="fa fa-bookmark"></i> {wishlistStr}
|
||||
</span>
|
||||
}
|
||||
<h4>{props.data.year}</h4>
|
||||
{props.data.runtime &&
|
||||
<p>
|
||||
<i className="fa fa-clock-o"></i>
|
||||
{props.data.runtime} min
|
||||
</p>
|
||||
}
|
||||
{props.data.genres &&
|
||||
<p>
|
||||
<i className="fa fa-tags"></i>
|
||||
{genres}
|
||||
</p>
|
||||
}
|
||||
<p>
|
||||
<i className="fa fa-star-o"></i>
|
||||
{Number(props.data.rating).toFixed(1)}
|
||||
{props.data.votes &&
|
||||
<small>({props.data.votes} counts)</small>
|
||||
}
|
||||
</p>
|
||||
<p className="plot">{props.data.plot}</p>
|
||||
<h1 className="hidden-xs">{props.data.get("title")}</h1>
|
||||
<h3 className="visible-xs">{props.data.get("title")}</h3>
|
||||
<TrackingLabel
|
||||
wishlisted={props.data.get("wishlisted")}
|
||||
trackedSeason={props.data.get("tracked_season")}
|
||||
trackedEpisode={props.data.get("tracked_episode")}
|
||||
/>
|
||||
<h4>{props.data.get("year")}</h4>
|
||||
<Runtime runtime={props.data.get("runtime")} />
|
||||
<Genres genres={props.data.get("genres")} />
|
||||
<Ratings
|
||||
rating={props.data.get("rating")}
|
||||
votes={props.data.get("votes")}
|
||||
/>
|
||||
<p className="plot">{props.data.get("plot")}</p>
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Runtime(props) {
|
||||
if (props.runtime === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p>
|
||||
<i className="fa fa-clock-o"></i>
|
||||
{props.runtime} min
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function Ratings(props) {
|
||||
if (props.rating === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p>
|
||||
<i className="fa fa-star-o"></i>
|
||||
{Number(props.rating).toFixed(1)}
|
||||
{props.votes !== undefined &&
|
||||
<small>({props.votes} counts)</small>
|
||||
}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function TrackingLabel(props) {
|
||||
let wishlistStr = props.wishlisted ? "Wishlisted" : "";
|
||||
|
||||
if (props.trackedEpisode !== null && props.trackedSeason !== null
|
||||
&& props.trackedEpisode !== undefined && props.trackedSeason !== undefined) {
|
||||
if ((props.trackedSeason === 0) && (props.trackedEpisode === 0)) {
|
||||
wishlistStr = "Whole show tracked";
|
||||
} else {
|
||||
wishlistStr = `Tracked from season ${props.trackedSeason} episode ${props.trackedEpisode}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (wishlistStr === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="label label-default">
|
||||
<i className="fa fa-bookmark"></i> {wishlistStr}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function Genres(props) {
|
||||
if (props.genres === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Uppercase first genres
|
||||
const prettyGenres = props.genres.toJS().map(
|
||||
(word) => word[0].toUpperCase() + word.substr(1)
|
||||
).join(", ");
|
||||
|
||||
return (
|
||||
<p>
|
||||
<i className="fa fa-tags"></i>
|
||||
{prettyGenres}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Form, FormGroup, FormControl, ControlLabel } from 'react-bootstrap'
|
||||
import React from "react"
|
||||
import { Form, FormGroup, FormControl, ControlLabel } from "react-bootstrap"
|
||||
|
||||
export default class ExplorerOptions extends React.Component {
|
||||
export default class ExplorerOptions extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSourceChange = this.handleSourceChange.bind(this);
|
||||
@ -9,7 +9,7 @@ export default class ExplorerOptions extends React.Component {
|
||||
}
|
||||
handleSourceChange(event) {
|
||||
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}`);
|
||||
}
|
||||
handleCategoryChange(event) {
|
||||
@ -40,7 +40,7 @@ export default class ExplorerOptions extends React.Component {
|
||||
}
|
||||
|
||||
// Options are not yet fetched
|
||||
if (Object.keys(this.props.options).length === 0) {
|
||||
if (this.props.options.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export default class ExplorerOptions extends React.Component {
|
||||
|
||||
let source = this.props.params.source;
|
||||
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 (
|
||||
<div className="row">
|
||||
@ -67,8 +67,9 @@ export default class ExplorerOptions extends React.Component {
|
||||
onChange={this.handleSourceChange}
|
||||
value={source}
|
||||
>
|
||||
{Object.keys(this.props.options).map(function(source) {
|
||||
return (<option key={source} value={source}>{this.prettyName(source)}</option>)
|
||||
{this.props.options.keySeq().map(function(source) {
|
||||
return (
|
||||
<option key={source} value={source}>{this.prettyName(source)}</option>)
|
||||
}, this)}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
@ -1,30 +1,42 @@
|
||||
import React from 'react'
|
||||
import { Control, Form } from 'react-redux-form';
|
||||
import React from "react"
|
||||
|
||||
export default function ListFilter(props) {
|
||||
if (!props.display) {
|
||||
return null;
|
||||
export default class ListFilter extends React.PureComponent {
|
||||
constructor(props) {
|
||||
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) {
|
||||
return null;
|
||||
// Start filtering at 3 chars
|
||||
if (value.length >= 3) {
|
||||
this.props.updateFilter(value);
|
||||
} else {
|
||||
this.props.updateFilter("");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12 list-filter">
|
||||
<Form model={props.formModel} className="input-group hidebtn-xs" >
|
||||
<Control.text
|
||||
model={props.controlModel}
|
||||
className="form-control input-sm"
|
||||
placeholder={props.controlPlaceHolder}
|
||||
updateOn="change"
|
||||
/>
|
||||
<span className="input-group-btn hidden-xs">
|
||||
<button className="btn btn-default btn-sm" type="button">Filter</button>
|
||||
</span>
|
||||
</Form>
|
||||
render() {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12 list-filter">
|
||||
<form className="input-group" onSubmit={(ev) => this.handleChange(ev)}>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder={this.props.placeHolder}
|
||||
onChange={this.handleChange}
|
||||
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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,41 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
export default function ListPoster(props) {
|
||||
const selected = props.selected ? ' thumbnail-selected' : '';
|
||||
const imgClass = 'thumbnail' + selected;
|
||||
const displayClearFixLg = (props.index % 6) === 0;
|
||||
const displayClearFixMd = (props.index % 4) === 0;
|
||||
const displayClearFixSm = (props.index % 2) === 0;
|
||||
return (
|
||||
<div>
|
||||
{displayClearFixLg &&
|
||||
<div className="clearfix visible-lg"></div>
|
||||
}
|
||||
{displayClearFixMd &&
|
||||
<div className="clearfix visible-md"></div>
|
||||
}
|
||||
{displayClearFixSm &&
|
||||
<div className="clearfix visible-sm"></div>
|
||||
}
|
||||
<div className="col-xs-12 col-sm-6 col-md-3 col-lg-2">
|
||||
<a className={imgClass}>
|
||||
<img
|
||||
src={props.data.poster_url}
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
</a>
|
||||
export default class Poster extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (nextProps.index !== this.props.index) { return true }
|
||||
if (nextProps.selected !== this.props.selected) { return true }
|
||||
if (nextProps.data.get("poster_url") !== this.props.data.get("poster_url")) { return true }
|
||||
return false;
|
||||
}
|
||||
render() {
|
||||
const selected = this.props.selected ? " thumbnail-selected" : "";
|
||||
const imgClass = "thumbnail" + selected;
|
||||
const displayClearFixLg = (this.props.index % 6) === 0;
|
||||
const displayClearFixMd = (this.props.index % 4) === 0;
|
||||
const displayClearFixSm = (this.props.index % 2) === 0;
|
||||
return (
|
||||
<div>
|
||||
{displayClearFixLg &&
|
||||
<div className="clearfix visible-lg"></div>
|
||||
}
|
||||
{displayClearFixMd &&
|
||||
<div className="clearfix visible-md"></div>
|
||||
}
|
||||
{displayClearFixSm &&
|
||||
<div className="clearfix visible-sm"></div>
|
||||
}
|
||||
<div className="col-xs-12 col-sm-6 col-md-3 col-lg-2">
|
||||
<a className={imgClass}>
|
||||
<img
|
||||
src={this.props.data.get("poster_url")}
|
||||
onClick={this.props.onClick}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,17 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import fuzzy from 'fuzzy';
|
||||
import InfiniteScroll from 'react-infinite-scroller';
|
||||
import fuzzy from "fuzzy";
|
||||
import InfiniteScroll from "react-infinite-scroller";
|
||||
|
||||
import ListFilter from './filter'
|
||||
import ExplorerOptions from './explorerOptions'
|
||||
import ListPoster from './poster'
|
||||
import ListFilter from "./filter"
|
||||
import ExplorerOptions from "./explorerOptions"
|
||||
import Poster from "./poster"
|
||||
|
||||
import Loader from '../loader/loader'
|
||||
import Loader from "../loader/loader"
|
||||
|
||||
const DEFAULT_LIST_SIZE = 30;
|
||||
const DEFAULT_ADD_EXTRA_ITEMS = 30;
|
||||
|
||||
export default class ListPosters extends React.Component {
|
||||
export default class ListPosters extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.loadMore = this.loadMore.bind(this);
|
||||
@ -24,14 +23,14 @@ export default class ListPosters extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.props.data.length) {
|
||||
if (this.props.data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(this.getNextState(this.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 nextListSize = currentListSize + DEFAULT_ADD_EXTRA_ITEMS;
|
||||
let hasMore = true;
|
||||
@ -47,44 +46,44 @@ export default class ListPosters extends React.Component {
|
||||
};
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
render() {
|
||||
let elmts = this.props.data.slice();
|
||||
const listSize = elmts.length;
|
||||
let elmts = this.props.data;
|
||||
const listSize = elmts !== undefined ? elmts.size : 0;
|
||||
const colSize = (listSize !== 0) ? "col-xs-5 col-md-8" : "col-xs-12";
|
||||
|
||||
// Filter the list of elements
|
||||
if (this.props.filter !== "") {
|
||||
const filtered = fuzzy.filter(this.props.filter, elmts, {
|
||||
extract: (el) => el.title
|
||||
});
|
||||
elmts = filtered.map((el) => el.original);
|
||||
elmts = elmts.filter((v) => fuzzy.test(this.props.filter, v.get("title")), this);
|
||||
} else {
|
||||
elmts = elmts.slice(0, this.state.items);
|
||||
}
|
||||
|
||||
// Chose when to display filter / explore options
|
||||
let displayFilter = true;
|
||||
if (this.props.params
|
||||
if ((this.props.params
|
||||
&& this.props.params.category
|
||||
&& this.props.params.category !== ""
|
||||
&& this.props.params.source
|
||||
&& this.props.params.source !== "") {
|
||||
&& this.props.params.source !== "")
|
||||
|| (listSize === 0)) {
|
||||
displayFilter = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={colSize}>
|
||||
<ListFilter
|
||||
listSize={listSize}
|
||||
display={displayFilter}
|
||||
formModel={this.props.formModel}
|
||||
controlModel={this.props.filterControlModel}
|
||||
controlPlaceHolder={this.props.filterControlPlaceHolder}
|
||||
/>
|
||||
{displayFilter &&
|
||||
<ListFilter
|
||||
updateFilter={this.props.updateFilter}
|
||||
placeHolder={this.props.placeHolder}
|
||||
/>
|
||||
}
|
||||
<ExplorerOptions
|
||||
type={this.props.type}
|
||||
display={!displayFilter}
|
||||
@ -105,39 +104,45 @@ export default class ListPosters extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function Posters(props) {
|
||||
if (props.loading) {
|
||||
return (<Loader />);
|
||||
class Posters extends React.PureComponent {
|
||||
constructor(props) {
|
||||
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 (
|
||||
<div className="jumbotron">
|
||||
<h2>No result</h2>
|
||||
<div>
|
||||
<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 (
|
||||
<Poster
|
||||
index={index}
|
||||
data={movie}
|
||||
key={imdbId}
|
||||
selected={selected}
|
||||
onClick={() => this.props.onClick(imdbId)}
|
||||
/>
|
||||
)
|
||||
} ,this)}
|
||||
</InfiniteScroll>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import Loading from 'react-loading'
|
||||
import React from "react"
|
||||
import Loading from "react-loading"
|
||||
|
||||
export default function Loader() {
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import { WishlistButton, DeleteButton, RefreshButton } from '../buttons/actions'
|
||||
import { DropdownButton } from 'react-bootstrap'
|
||||
import { WishlistButton, DeleteButton, RefreshButton } from "../buttons/actions"
|
||||
import { DropdownButton } from "react-bootstrap"
|
||||
|
||||
export default function ActionsButton(props) {
|
||||
return (
|
||||
|
@ -1,112 +1,112 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { addTorrent } from '../../actions/torrents'
|
||||
import { refreshSubtitles } from '../../actions/subtitles'
|
||||
import { addMovieToWishlist, deleteMovieFromWishlist,
|
||||
getMovieDetails, selectMovie } from '../../actions/movies'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent } from "../../actions/torrents"
|
||||
import { refreshSubtitles } from "../../actions/subtitles"
|
||||
import { addMovieToWishlist, deleteMovie, deleteMovieFromWishlist,
|
||||
getMovieDetails, selectMovie, updateFilter } from "../../actions/movies"
|
||||
|
||||
import DownloadButton from '../buttons/download'
|
||||
import SubtitlesButton from '../buttons/subtitles'
|
||||
import TorrentsButton from './torrents'
|
||||
import ActionsButton from './actions'
|
||||
import ListPosters from '../list/posters'
|
||||
import ListDetails from '../list/details'
|
||||
import DownloadButton from "../buttons/download"
|
||||
import SubtitlesButton from "../buttons/subtitles"
|
||||
import ImdbButton from "../buttons/imdb"
|
||||
import TorrentsButton from "./torrents"
|
||||
import ActionsButton from "./actions"
|
||||
import ListPosters from "../list/posters"
|
||||
import ListDetails from "../list/details"
|
||||
|
||||
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) =>
|
||||
bindActionCreators({ selectMovie, getMovieDetails, addTorrent,
|
||||
addMovieToWishlist, deleteMovieFromWishlist, refreshSubtitles }, dipatch)
|
||||
addMovieToWishlist, deleteMovie, deleteMovieFromWishlist,
|
||||
refreshSubtitles, updateFilter }, dipatch)
|
||||
|
||||
function MovieButtons(props) {
|
||||
const imdb_link = `http://www.imdb.com/title/${props.movie.imdb_id}`;
|
||||
const hasMovie = (props.movie.polochon_url !== "");
|
||||
const hasMovie = (props.movie.get("polochon_url") !== "");
|
||||
return (
|
||||
<div className="list-details-buttons btn-toolbar">
|
||||
<ActionsButton
|
||||
fetching={props.fetching}
|
||||
movieId={props.movie.imdb_id}
|
||||
fetching={props.movie.get("fetchingDetails")}
|
||||
movieId={props.movie.get("imdb_id")}
|
||||
getDetails={props.getMovieDetails}
|
||||
deleteMovie={props.deleteMovie}
|
||||
hasMovie={hasMovie}
|
||||
wishlisted={props.movie.wishlisted}
|
||||
wishlisted={props.movie.get("wishlisted")}
|
||||
addToWishlist={props.addToWishlist}
|
||||
deleteFromWishlist={props.deleteFromWishlist}
|
||||
lastFetchUrl={props.lastFetchUrl}
|
||||
fetchMovies={props.fetchMovies}
|
||||
/>
|
||||
|
||||
{props.movie.torrents &&
|
||||
<TorrentsButton
|
||||
torrents={props.movie.torrents}
|
||||
addTorrent={props.addTorrent}
|
||||
/>
|
||||
}
|
||||
{props.movie.get("torrents") !== null &&
|
||||
<TorrentsButton
|
||||
torrents={props.movie.get("torrents")}
|
||||
addTorrent={props.addTorrent}
|
||||
/>
|
||||
}
|
||||
|
||||
<DownloadButton
|
||||
url={props.movie.polochon_url}
|
||||
subtitles={props.movie.subtitles}
|
||||
/>
|
||||
<DownloadButton
|
||||
url={props.movie.get("polochon_url")}
|
||||
subtitles={props.movie.get("subtitles")}
|
||||
/>
|
||||
|
||||
<SubtitlesButton
|
||||
url={props.movie.polochon_url}
|
||||
subtitles={props.movie.subtitles}
|
||||
refreshSubtitles={props.refreshSubtitles}
|
||||
resourceID={props.movie.imdb_id}
|
||||
data={props.movie}
|
||||
type="movie"
|
||||
/>
|
||||
{props.movie.get("polochon_url") !== "" &&
|
||||
<SubtitlesButton
|
||||
fetching={props.movie.get("fetchingSubtitles")}
|
||||
subtitles={props.movie.get("subtitles")}
|
||||
refreshSubtitles={props.refreshSubtitles}
|
||||
resourceID={props.movie.get("imdb_id")}
|
||||
type="movie"
|
||||
/>
|
||||
}
|
||||
|
||||
<a type="button" className="btn btn-warning btn-sm" href={imdb_link}>
|
||||
<i className="fa fa-external-link"></i> IMDB
|
||||
</a>
|
||||
<ImdbButton imdbId={props.movie.get("imdb_id")} size="sm"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
class MovieList extends React.Component {
|
||||
class MovieList extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
const movies = this.props.movieStore.movies;
|
||||
const selectedMovieId = this.props.movieStore.selectedImdbId;
|
||||
let index = movies.map((el) => el.imdb_id).indexOf(selectedMovieId);
|
||||
if (index === -1) {
|
||||
index = 0;
|
||||
let selectedMovie = undefined;
|
||||
if (this.props.movies !== undefined && this.props.movies.has(this.props.selectedImdbId)) {
|
||||
selectedMovie = this.props.movies.get(this.props.selectedImdbId);
|
||||
}
|
||||
const selectedMovie = movies[index];
|
||||
|
||||
return (
|
||||
<div className="row" id="container">
|
||||
<ListPosters
|
||||
data={movies}
|
||||
data={this.props.movies}
|
||||
type="movies"
|
||||
formModel="movieStore"
|
||||
filterControlModel="movieStore.filter"
|
||||
filterControlPlaceHolder="Filter movies..."
|
||||
exploreOptions={this.props.movieStore.exploreOptions}
|
||||
selectedImdbId={selectedMovieId}
|
||||
filter={this.props.movieStore.filter}
|
||||
perPage={this.props.movieStore.perPage}
|
||||
placeHolder="Filter movies..."
|
||||
exploreOptions={this.props.exploreOptions}
|
||||
selectedImdbId={this.props.selectedImdbId}
|
||||
updateFilter={this.props.updateFilter}
|
||||
filter={this.props.filter}
|
||||
onClick={this.props.selectMovie}
|
||||
params={this.props.params}
|
||||
router={this.props.router}
|
||||
loading={this.props.movieStore.loading}
|
||||
loading={this.props.loading}
|
||||
/>
|
||||
{selectedMovie &&
|
||||
{selectedMovie !== undefined &&
|
||||
<ListDetails data={selectedMovie}>
|
||||
<MovieButtons
|
||||
movie={selectedMovie}
|
||||
fetching={this.props.movieStore.fetchingDetails}
|
||||
getMovieDetails={this.props.getMovieDetails}
|
||||
addTorrent={this.props.addTorrent}
|
||||
deleteMovie={this.props.deleteMovie}
|
||||
addToWishlist={this.props.addMovieToWishlist}
|
||||
deleteFromWishlist={this.props.deleteMovieFromWishlist}
|
||||
lastFetchUrl={this.props.movieStore.lastFetchUrl}
|
||||
lastFetchUrl={this.props.lastFetchUrl}
|
||||
refreshSubtitles={this.props.refreshSubtitles}
|
||||
/>
|
||||
</ListDetails>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import { DropdownButton, MenuItem } from 'react-bootstrap'
|
||||
import { DropdownButton, MenuItem } from "react-bootstrap"
|
||||
|
||||
export default class TorrentsButton extends React.Component {
|
||||
export default class TorrentsButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
@ -17,17 +17,17 @@ export default class TorrentsButton extends React.Component {
|
||||
<DropdownButton className="btn btn-default btn-sm" title="Torrents" id="download-torrents-button" dropup>
|
||||
{entries.map(function(e, index) {
|
||||
switch (e.type) {
|
||||
case 'header':
|
||||
case "header":
|
||||
return (
|
||||
<MenuItem key={index} className="text-warning" header>
|
||||
{e.value}
|
||||
</MenuItem>
|
||||
);
|
||||
case 'divider':
|
||||
case "divider":
|
||||
return (
|
||||
<MenuItem key={index} divider></MenuItem>
|
||||
);
|
||||
case 'entry':
|
||||
case "entry":
|
||||
return (
|
||||
<MenuItem key={index} href={e.url} onClick={(event) => this.handleClick(event, e.url)}>
|
||||
{e.quality}
|
||||
@ -41,20 +41,12 @@ export default class TorrentsButton extends React.Component {
|
||||
}
|
||||
|
||||
function buildMenuItems(torrents) {
|
||||
// Organise by source
|
||||
let sources = {}
|
||||
for (let torrent of torrents) {
|
||||
if (!sources[torrent.source]) {
|
||||
sources[torrent.source] = [];
|
||||
}
|
||||
sources[torrent.source].push(torrent);
|
||||
}
|
||||
const t = torrents.groupBy((el) => el.get("source"));
|
||||
|
||||
// Build the array of entries
|
||||
let entries = [];
|
||||
let sourceNames = Object.keys(sources);
|
||||
let dividerCount = sourceNames.length - 1;
|
||||
for (let source of sourceNames) {
|
||||
let dividerCount = t.size - 1;
|
||||
for (let [source, torrentList] of t.entrySeq()) {
|
||||
// Push the title
|
||||
entries.push({
|
||||
type: "header",
|
||||
@ -62,11 +54,11 @@ function buildMenuItems(torrents) {
|
||||
});
|
||||
|
||||
// Push the torrents
|
||||
for (let torrent of sources[source]) {
|
||||
for (let torrent of torrentList) {
|
||||
entries.push({
|
||||
type: "entry",
|
||||
quality: torrent.quality,
|
||||
url: torrent.url,
|
||||
quality: torrent.get("quality"),
|
||||
url: torrent.get("url"),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from 'react-bootstrap'
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
import React from "react"
|
||||
import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from "react-bootstrap"
|
||||
import { LinkContainer } from "react-router-bootstrap"
|
||||
|
||||
export default class NavBar extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@ -25,10 +25,10 @@ export default class NavBar extends React.PureComponent {
|
||||
}
|
||||
}
|
||||
shouldDisplayMoviesSearch(router) {
|
||||
return this.matchPath(router, 'movies');
|
||||
return this.matchPath(router, "movies");
|
||||
}
|
||||
shouldDisplayShowsSearch(router) {
|
||||
return this.matchPath(router, 'shows');
|
||||
return this.matchPath(router, "shows");
|
||||
}
|
||||
matchPath(router, keyword) {
|
||||
const location = router.getCurrentLocation().pathname;
|
||||
@ -103,7 +103,7 @@ class Search extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function MoviesDropdown(props) {
|
||||
function MoviesDropdown() {
|
||||
return(
|
||||
<Nav>
|
||||
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
||||
@ -118,7 +118,7 @@ function MoviesDropdown(props) {
|
||||
);
|
||||
}
|
||||
|
||||
function ShowsDropdown(props) {
|
||||
function ShowsDropdown() {
|
||||
return(
|
||||
<Nav>
|
||||
<NavDropdown title="Shows" id="navbar-shows-dropdown">
|
||||
@ -161,7 +161,7 @@ function UserDropdown(props) {
|
||||
}
|
||||
}
|
||||
|
||||
function WishlistDropdown(props) {
|
||||
function WishlistDropdown() {
|
||||
return(
|
||||
<Nav>
|
||||
<NavDropdown title="Wishlist" id="navbar-wishlit-dropdown">
|
||||
|
@ -1,22 +1,22 @@
|
||||
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, updateShowDetails } from '../../actions/shows'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent } from "../../actions/torrents"
|
||||
import { refreshSubtitles } from "../../actions/subtitles"
|
||||
import { addShowToWishlist, deleteShowFromWishlist, getEpisodeDetails, updateShowDetails } from "../../actions/shows"
|
||||
|
||||
import Loader from '../loader/loader'
|
||||
import DownloadButton from '../buttons/download'
|
||||
import SubtitlesButton from '../buttons/subtitles'
|
||||
import Loader from "../loader/loader"
|
||||
import DownloadButton from "../buttons/download"
|
||||
import SubtitlesButton from "../buttons/subtitles"
|
||||
import ImdbButton from "../buttons/imdb"
|
||||
import RefreshIndicator from "../buttons/refresh"
|
||||
|
||||
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
loading: state.showStore.loading,
|
||||
show: state.showStore.get('show'),
|
||||
show: state.showStore.get("show"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
@ -70,31 +70,28 @@ function Header(props){
|
||||
function HeaderThumbnail(props){
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-2 text-center">
|
||||
<img src={props.data.get('poster_url')}
|
||||
<img src={props.data.get("poster_url")}
|
||||
className="show-thumbnail thumbnail-selected img-thumbnail img-responsive"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HeaderDetails(props){
|
||||
const imdbLink = `http://www.imdb.com/title/${props.data.get('imdb_id')}`;
|
||||
return (
|
||||
<div className="col-xs-12 col-sm-10">
|
||||
<dl className="dl-horizontal">
|
||||
<dt>Title</dt>
|
||||
<dd>{props.data.get('title')}</dd>
|
||||
<dd>{props.data.get("title")}</dd>
|
||||
<dt>Plot</dt>
|
||||
<dd className="plot">{props.data.get('plot')}</dd>
|
||||
<dd className="plot">{props.data.get("plot")}</dd>
|
||||
<dt>IMDB</dt>
|
||||
<dd>
|
||||
<a type="button" className="btn btn-warning btn-xs" href={imdbLink}>
|
||||
<i className="fa fa-external-link"></i> Open in IMDB
|
||||
</a>
|
||||
<ImdbButton imdbId={props.data.get("imdb_id")} size="xs"/>
|
||||
</dd>
|
||||
<dt>Year</dt>
|
||||
<dd>{props.data.get('year')}</dd>
|
||||
<dd>{props.data.get("year")}</dd>
|
||||
<dt>Rating</dt>
|
||||
<dd>{props.data.get('rating')}</dd>
|
||||
<dd>{props.data.get("rating")}</dd>
|
||||
</dl>
|
||||
<TrackHeader
|
||||
data={props.data}
|
||||
@ -108,7 +105,7 @@ function HeaderDetails(props){
|
||||
function SeasonsList(props){
|
||||
return (
|
||||
<div>
|
||||
{props.data.get('seasons').entrySeq().map(function([season, data]) {
|
||||
{props.data.get("seasons").entrySeq().map(function([season, data]) {
|
||||
return (
|
||||
|
||||
<div className="col-xs-12 col-sm-10 col-sm-offset-1 col-md-10 col-md-offset-1" key={season.toString()}>
|
||||
@ -156,7 +153,7 @@ class Season extends React.Component {
|
||||
<table className="table table-striped table-hover">
|
||||
<tbody>
|
||||
{this.props.data.toList().map(function(episode) {
|
||||
let key = `${episode.get('season')}-${episode.get('episode')}`;
|
||||
let key = `${episode.get("season")}-${episode.get("episode")}`;
|
||||
return (
|
||||
<Episode
|
||||
key={key}
|
||||
@ -177,12 +174,6 @@ class Season extends React.Component {
|
||||
}
|
||||
|
||||
function Episode(props) {
|
||||
// TODO: remove this when everything uses immutable
|
||||
let subtitles;
|
||||
if (props.data.has('subtitles') && props.data.get('subtitles')) {
|
||||
subtitles = props.data.get('subtitles').toJS();
|
||||
}
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<th scope="row" className="col-xs-2">
|
||||
@ -190,24 +181,26 @@ function Episode(props) {
|
||||
data={props.data}
|
||||
addToWishlist={props.addToWishlist}
|
||||
/>
|
||||
{props.data.get('episode')}
|
||||
{props.data.get("episode")}
|
||||
</th>
|
||||
<td className="col-xs-12">
|
||||
{props.data.get('title')}
|
||||
{props.data.get("title")}
|
||||
|
||||
<span className="pull-right episode-buttons">
|
||||
<SubtitlesButton
|
||||
url={props.data.get('polochon_url')}
|
||||
subtitles={subtitles}
|
||||
refreshSubtitles={props.refreshSubtitles}
|
||||
resourceID={props.data.get('show_imdb_id')}
|
||||
season={props.data.get('season')}
|
||||
episode={props.data.get('episode')}
|
||||
type="episode"
|
||||
xs
|
||||
/>
|
||||
{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')}`;
|
||||
{props.data.get("polochon_url") !== "" &&
|
||||
<SubtitlesButton
|
||||
fetching={props.data.get("fetchingSubtitles")}
|
||||
subtitles={props.data.get("subtitles")}
|
||||
refreshSubtitles={props.refreshSubtitles}
|
||||
resourceID={props.data.get("show_imdb_id")}
|
||||
season={props.data.get("season")}
|
||||
episode={props.data.get("episode")}
|
||||
type="episode"
|
||||
xs
|
||||
/>
|
||||
}
|
||||
{props.data.get("torrents") && props.data.get("torrents").toList().map(function(torrent) {
|
||||
let key = `${props.data.get("season")}-${props.data.get("episode")}-${torrent.get("source")}-${torrent.get("quality")}`;
|
||||
return (
|
||||
<Torrent
|
||||
data={torrent}
|
||||
@ -217,8 +210,8 @@ function Episode(props) {
|
||||
)
|
||||
})}
|
||||
<DownloadButton
|
||||
url={props.data.get('polochon_url')}
|
||||
subtitles={subtitles}
|
||||
url={props.data.get("polochon_url")}
|
||||
subtitles={props.data.get("subtitles")}
|
||||
xs
|
||||
/>
|
||||
<GetDetailsButton
|
||||
@ -229,9 +222,9 @@ function Episode(props) {
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Torrent extends React.Component {
|
||||
class Torrent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
@ -245,25 +238,25 @@ class Torrent extends React.Component {
|
||||
<span>
|
||||
<a type="button"
|
||||
className="btn btn-primary btn-xs"
|
||||
onClick={(e) => this.handleClick(e, this.props.data.get('url'))}
|
||||
onClick={(e) => this.handleClick(e, this.props.data.get("url"))}
|
||||
href={this.props.data.url} >
|
||||
<i className="fa fa-download"></i> {this.props.data.get('quality')}
|
||||
<i className="fa fa-download"></i> {this.props.data.get("quality")}
|
||||
</a>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TrackHeader extends React.Component {
|
||||
class TrackHeader extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick(e, url) {
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
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 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(imdbId);
|
||||
@ -272,9 +265,8 @@ class TrackHeader extends React.Component {
|
||||
}
|
||||
}
|
||||
render() {
|
||||
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 trackedSeason = this.props.data.get("tracked_season");
|
||||
const trackedEpisode = this.props.data.get("tracked_episode");
|
||||
const wishlisted = (trackedSeason !== null && trackedEpisode !== null);
|
||||
let msg;
|
||||
if (wishlisted) {
|
||||
@ -314,16 +306,16 @@ class TrackHeader extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class TrackButton extends React.Component {
|
||||
class TrackButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick(e, url) {
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
const imdbId = this.props.data.get('show_imdb_id');
|
||||
const season = this.props.data.get('season');
|
||||
const episode = this.props.data.get('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() {
|
||||
@ -341,34 +333,25 @@ class TrackButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
class GetDetailsButton extends React.Component {
|
||||
class GetDetailsButton extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick(e, url) {
|
||||
handleClick(e) {
|
||||
e.preventDefault();
|
||||
if (this.props.data.get('fetching')) {
|
||||
if (this.props.data.get("fetching")) {
|
||||
return
|
||||
}
|
||||
const imdbId = this.props.data.get('show_imdb_id');
|
||||
const season = this.props.data.get('season');
|
||||
const episode = this.props.data.get('episode');
|
||||
const imdbId = this.props.data.get("show_imdb_id");
|
||||
const season = this.props.data.get("season");
|
||||
const episode = this.props.data.get("episode");
|
||||
this.props.getEpisodeDetails(imdbId, season, episode);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<a type="button" className="btn btn-xs btn-info" onClick={(e) => this.handleClick(e)}>
|
||||
{this.props.data.get('fetching') ||
|
||||
<span>
|
||||
<i className="fa fa-refresh"></i> Refresh
|
||||
</span>
|
||||
}
|
||||
{this.props.data.get('fetching') &&
|
||||
<span>
|
||||
<i className="fa fa-spin fa-refresh"></i> Refreshing
|
||||
</span>
|
||||
}
|
||||
<RefreshIndicator refresh={this.props.data.get("fetching")} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
@ -1,46 +1,48 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { selectShow, addShowToWishlist,
|
||||
deleteShowFromWishlist, getShowDetails } from '../../actions/shows'
|
||||
deleteShowFromWishlist, getShowDetails, updateFilter } from "../../actions/shows"
|
||||
|
||||
import ListDetails from '../list/details'
|
||||
import ListPosters from '../list/posters'
|
||||
import ShowButtons from './listButtons'
|
||||
import ListDetails from "../list/details"
|
||||
import ListPosters from "../list/posters"
|
||||
import ShowButtons from "./listButtons"
|
||||
|
||||
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) =>
|
||||
bindActionCreators({ selectShow, addShowToWishlist,
|
||||
deleteShowFromWishlist, getShowDetails }, dispatch)
|
||||
deleteShowFromWishlist, getShowDetails, updateFilter }, dispatch)
|
||||
|
||||
class ShowList extends React.Component {
|
||||
class ShowList extends React.PureComponent {
|
||||
render() {
|
||||
const shows = this.props.showsStore.shows;
|
||||
const selectedShowId = this.props.showsStore.selectedImdbId;
|
||||
let index = shows.map((el) => el.imdb_id).indexOf(selectedShowId);
|
||||
if (index === -1) {
|
||||
index = 0;
|
||||
let selectedShow;
|
||||
if (this.props.selectedImdbId !== "") {
|
||||
selectedShow = this.props.shows.get(this.props.selectedImdbId);
|
||||
}
|
||||
const selectedShow = shows[index];
|
||||
|
||||
return (
|
||||
<div className="row" id="container">
|
||||
<ListPosters
|
||||
data={shows}
|
||||
data={this.props.shows}
|
||||
type="shows"
|
||||
formModel="showsStore"
|
||||
filterControlModel="showsStore.filter"
|
||||
filterControlPlaceHolder="Filter shows..."
|
||||
exploreOptions={this.props.showsStore.exploreOptions}
|
||||
selectedImdbId={selectedShowId}
|
||||
filter={this.props.showsStore.filter}
|
||||
perPage={this.props.showsStore.perPage}
|
||||
placeHolder="Filter shows..."
|
||||
exploreOptions={this.props.exploreOptions}
|
||||
updateFilter={this.props.updateFilter}
|
||||
selectedImdbId={this.props.selectedImdbId}
|
||||
filter={this.props.filter}
|
||||
onClick={this.props.selectShow}
|
||||
router={this.props.router}
|
||||
params={this.props.params}
|
||||
loading={this.props.showsStore.loading}
|
||||
loading={this.props.loading}
|
||||
/>
|
||||
{selectedShow &&
|
||||
<ListDetails data={selectedShow} >
|
||||
@ -49,7 +51,7 @@ class ShowList extends React.Component {
|
||||
deleteFromWishlist={this.props.deleteShowFromWishlist}
|
||||
addToWishlist={this.props.addShowToWishlist}
|
||||
getDetails={this.props.getShowDetails}
|
||||
fetching={this.props.showsStore.getDetails}
|
||||
updateFilter={this.props.updateFilter}
|
||||
/>
|
||||
</ListDetails>
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from 'react'
|
||||
import React from "react"
|
||||
|
||||
import { Link } from 'react-router'
|
||||
import { DropdownButton } from 'react-bootstrap'
|
||||
import { Link } from "react-router"
|
||||
import { DropdownButton } from "react-bootstrap"
|
||||
|
||||
import { WishlistButton, RefreshButton } from '../buttons/actions'
|
||||
import { WishlistButton, RefreshButton } from "../buttons/actions"
|
||||
import ImdbButton from "../buttons/imdb"
|
||||
|
||||
export default function ShowButtons(props) {
|
||||
const imdbLink = `http://www.imdb.com/title/${props.show.imdb_id}`;
|
||||
return (
|
||||
<div className="list-details-buttons btn-toolbar">
|
||||
<ActionsButton
|
||||
@ -14,12 +14,9 @@ export default function ShowButtons(props) {
|
||||
addToWishlist={props.addToWishlist}
|
||||
deleteFromWishlist={props.deleteFromWishlist}
|
||||
getDetails={props.getDetails}
|
||||
fetching={props.fetching}
|
||||
/>
|
||||
<a type="button" className="btn btn-warning btn-sm" href={imdbLink}>
|
||||
<i className="fa fa-external-link"></i> IMDB
|
||||
</a>
|
||||
<Link type="button" className="btn btn-primary btn-sm" to={"/shows/details/" + props.show.imdb_id}>
|
||||
<ImdbButton imdbId={props.show.get("imdb_id")} size="sm"/>
|
||||
<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
|
||||
</Link>
|
||||
</div>
|
||||
@ -27,16 +24,16 @@ export default function ShowButtons(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 (
|
||||
<DropdownButton className="btn btn-default btn-sm" title="Actions" id="actions-button" dropup>
|
||||
<RefreshButton
|
||||
fetching={props.fetching}
|
||||
resourceId={props.show.imdb_id}
|
||||
fetching={props.show.get("fetchingDetails")}
|
||||
resourceId={props.show.get("imdb_id")}
|
||||
getDetails={props.getDetails}
|
||||
/>
|
||||
<WishlistButton
|
||||
resourceId={props.show.imdb_id}
|
||||
resourceId={props.show.get("imdb_id")}
|
||||
wishlisted={wishlisted}
|
||||
addToWishlist={props.addToWishlist}
|
||||
deleteFromWishlist={props.deleteFromWishlist}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { addTorrent } from '../../actions/torrents'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
import { addTorrent } from "../../actions/torrents"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { torrents: state.torrentStore.get('torrents') };
|
||||
return { torrents: state.torrentStore.get("torrents") };
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ addTorrent }, dispatch)
|
||||
@ -24,7 +24,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(TorrentList);
|
||||
class AddTorrent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { url: '' };
|
||||
this.state = { url: "" };
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
}
|
||||
@ -35,7 +35,7 @@ class AddTorrent extends React.PureComponent {
|
||||
if (this.state.url === "") {
|
||||
return;
|
||||
}
|
||||
this.setState({ url: '' });
|
||||
this.setState({ url: "" });
|
||||
this.props.func(this.state.url);
|
||||
}
|
||||
render() {
|
||||
@ -96,23 +96,23 @@ class List extends React.PureComponent {
|
||||
|
||||
class Torrent extends React.PureComponent {
|
||||
render() {
|
||||
const done = this.props.data.get('is_finished');
|
||||
var progressStyle = 'progress-bar progress-bar-warning';
|
||||
const done = this.props.data.get("is_finished");
|
||||
var progressStyle = "progress-bar progress-bar-warning";
|
||||
if (done) {
|
||||
progressStyle = 'progress-bar progress-bar-success';
|
||||
progressStyle = "progress-bar progress-bar-success";
|
||||
}
|
||||
var percentDone = this.props.data.get('percent_done');
|
||||
var percentDone = this.props.data.get("percent_done");
|
||||
const started = (percentDone !== 0);
|
||||
if (started) {
|
||||
percentDone = Number(percentDone).toFixed(1) + '%';
|
||||
percentDone = Number(percentDone).toFixed(1) + "%";
|
||||
}
|
||||
|
||||
var downloadedSize = prettySize(this.props.data.get('downloaded_size'));
|
||||
var totalSize = prettySize(this.props.data.get('total_size'));
|
||||
var downloadRate = prettySize(this.props.data.get('download_rate')) + "/s";
|
||||
var downloadedSize = prettySize(this.props.data.get("downloaded_size"));
|
||||
var totalSize = prettySize(this.props.data.get("total_size"));
|
||||
var downloadRate = prettySize(this.props.data.get("download_rate")) + "/s";
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">{this.props.data.get('name')}</div>
|
||||
<div className="panel-heading">{this.props.data.get("name")}</div>
|
||||
<div className="panel-body">
|
||||
{started &&
|
||||
<div className="progress progress-striped">
|
||||
@ -138,7 +138,7 @@ class Torrent extends React.PureComponent {
|
||||
|
||||
function prettySize(fileSizeInBytes) {
|
||||
var i = -1;
|
||||
var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
var byteUnits = [" kB", " MB", " GB", " TB", "PB", "EB", "ZB", "YB"];
|
||||
do {
|
||||
fileSizeInBytes = fileSizeInBytes / 1024;
|
||||
i++;
|
||||
|
@ -1,29 +1,55 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
|
||||
import { Control, Form } from 'react-redux-form';
|
||||
import { updateUser } from '../../actions/users'
|
||||
import { updateUser } from "../../actions/users"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { user: state.userStore };
|
||||
return {
|
||||
polochonToken: state.userStore.get("polochonToken"),
|
||||
polochonUrl: state.userStore.get("polochonUrl"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ updateUser }, dispatch)
|
||||
|
||||
class UserEdit extends React.Component {
|
||||
class UserEdit extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
polochonToken: props.polochonToken,
|
||||
polochonUrl: props.polochonUrl,
|
||||
};
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.handleUrlInput = this.handleUrlInput.bind(this);
|
||||
this.handleTokenInput = this.handleTokenInput.bind(this);
|
||||
}
|
||||
handleSubmit() {
|
||||
handleSubmit(ev) {
|
||||
ev.preventDefault();
|
||||
this.props.updateUser({
|
||||
'polochon_url': this.props.user.polochonUrl,
|
||||
'polochon_token': this.props.user.polochonToken,
|
||||
'password': this.refs.newPassword.value,
|
||||
'password_confirm': this.refs.newPasswordConfirm.value,
|
||||
"polochon_url": this.refs.polochonUrl.value,
|
||||
"polochon_token": this.refs.polochonToken.value,
|
||||
"password": this.refs.newPassword.value,
|
||||
"password_confirm": this.refs.newPasswordConfirm.value,
|
||||
});
|
||||
}
|
||||
handleTokenInput() {
|
||||
this.setState({ polochonToken: this.refs.polochonToken.value });
|
||||
}
|
||||
handleUrlInput() {
|
||||
this.setState({ polochonUrl: this.refs.polochonUrl.value });
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if ((nextProps.polochonUrl !== "")
|
||||
&& (this.state.polochonUrl === "")
|
||||
&& (nextProps.polochonToken !== "")
|
||||
&& (this.state.polochonToken === "")) {
|
||||
this.setState({
|
||||
polochonToken: nextProps.polochonToken,
|
||||
polochonUrl: nextProps.polochonUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div className="container">
|
||||
@ -31,35 +57,44 @@ class UserEdit extends React.Component {
|
||||
<div className="col-md-6 col-md-offset-3 col-xs-12">
|
||||
<h2>Edit user</h2>
|
||||
<hr />
|
||||
|
||||
<Form model="userStore" className="form-horizontal" onSubmit={(val) => this.handleSubmit(val)}>
|
||||
<form className="form-horizontal" onSubmit={(ev) => this.handleSubmit(ev)}>
|
||||
<div className="form-group">
|
||||
<label className="control-label">Polochon URL</label>
|
||||
<Control.text model="userStore.polochonUrl" className="form-control" />
|
||||
<input
|
||||
className="form-control"
|
||||
value={this.state.polochonUrl}
|
||||
onChange={this.handleUrlInput}
|
||||
ref="polochonUrl"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Polochon token</label>
|
||||
<Control.text model="userStore.polochonToken" className="form-control"/>
|
||||
<input
|
||||
className="form-control"
|
||||
value={this.state.polochonToken}
|
||||
onChange={this.handleTokenInput}
|
||||
ref="polochonToken"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Password</label>
|
||||
<input autoComplete="off" className="form-control" ref="newPassword" type="password"/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="control-label">Password</label>
|
||||
<input type="password" autoComplete="off" ref="newPassword" className="form-control"/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="control-label">Confirm Password</label>
|
||||
<input autoComplete="off" className="form-control" ref="newPasswordConfirm" type="password"/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label className="control-label">Confirm Password</label>
|
||||
<input type="password" autoComplete="off" ref="newPasswordConfirm" className="form-control"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input className="btn btn-primary pull-right" type="submit" value="Update"/>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" className="btn btn-primary pull-right" value="Update"/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,27 +1,30 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
|
||||
import { loginUser } from '../../actions/users'
|
||||
import { loginUser } from "../../actions/users"
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { user: state.userStore };
|
||||
return {
|
||||
isLogged: state.userStore.get("isLogged"),
|
||||
loading: state.userStore.get("loading"),
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ loginUser }, dispatch)
|
||||
|
||||
class UserLoginForm extends React.Component {
|
||||
class UserLoginForm extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.user.isLogged) {
|
||||
if (!nextProps.isLogged) {
|
||||
return
|
||||
}
|
||||
if (!nextProps.location.query.redirect) {
|
||||
// Redirect home
|
||||
nextProps.router.push('/');
|
||||
nextProps.router.push("/");
|
||||
} else {
|
||||
// Redirect to the previous page
|
||||
nextProps.router.push(nextProps.location.query.redirect);
|
||||
@ -29,7 +32,7 @@ class UserLoginForm extends React.Component {
|
||||
}
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (this.props.user.userLoading) {
|
||||
if (this.props.loading) {
|
||||
return;
|
||||
}
|
||||
const username = this.refs.username.value;
|
||||
@ -57,12 +60,12 @@ class UserLoginForm extends React.Component {
|
||||
<p></p>
|
||||
</div>
|
||||
<div>
|
||||
{this.props.user.userLoading &&
|
||||
{this.props.loading &&
|
||||
<button className="btn btn-primary pull-right">
|
||||
<i className="fa fa-spinner fa-spin"></i>
|
||||
</button>
|
||||
}
|
||||
{this.props.user.userLoading ||
|
||||
{this.props.loading ||
|
||||
<input className="btn btn-primary pull-right" type="submit" value="Log in"/>
|
||||
}
|
||||
<br/>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import React from "react"
|
||||
import { connect } from "react-redux"
|
||||
import { bindActionCreators } from "redux"
|
||||
|
||||
import { userSignUp } from '../../actions/users'
|
||||
import { userSignUp } from "../../actions/users"
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ userSignUp }, dispatch)
|
||||
|
||||
class UserSignUp extends React.Component {
|
||||
class UserSignUp extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
@ -15,9 +15,9 @@ class UserSignUp extends React.Component {
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
this.props.userSignUp({
|
||||
'username': this.refs.username.value,
|
||||
'password': this.refs.password.value,
|
||||
'password_confirm': this.refs.passwordConfirm.value,
|
||||
"username": this.refs.username.value,
|
||||
"password": this.refs.password.value,
|
||||
"password_confirm": this.refs.passwordConfirm.value,
|
||||
});
|
||||
}
|
||||
render() {
|
||||
|
@ -1,30 +1,29 @@
|
||||
const defaultState = {
|
||||
import { Map } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
show: false,
|
||||
message: "",
|
||||
type: "",
|
||||
};
|
||||
});
|
||||
|
||||
export default function Alert(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'ADD_ALERT_ERROR':
|
||||
return Object.assign({}, state, {
|
||||
message: action.payload.message,
|
||||
show: true,
|
||||
type: "error",
|
||||
})
|
||||
case 'ADD_ALERT_OK':
|
||||
return Object.assign({}, state, {
|
||||
|
||||
const handlers = {
|
||||
"ADD_ALERT_ERROR": (state, action) => state.merge(Map({
|
||||
message: action.payload.message,
|
||||
show: true,
|
||||
type: "error",
|
||||
})),
|
||||
"ADD_ALERT_OK": (state, action) => state.merge(Map({
|
||||
message: action.payload.message,
|
||||
show: true,
|
||||
type: "success",
|
||||
})
|
||||
case 'DISMISS_ALERT':
|
||||
return Object.assign({}, state, {
|
||||
})),
|
||||
"DISMISS_ALERT": state => state.merge(Map({
|
||||
message: "",
|
||||
show: false,
|
||||
type: "",
|
||||
})
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
})),
|
||||
}
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||
|
@ -1,17 +1,14 @@
|
||||
import { combineForms } from 'react-redux-form'
|
||||
import { routerReducer } from 'react-router-redux'
|
||||
import { combineReducers } from "redux";
|
||||
import { routerReducer } from "react-router-redux"
|
||||
|
||||
import movieStore from './movies'
|
||||
import showsStore from './shows'
|
||||
import showStore from './show'
|
||||
import userStore from './users'
|
||||
import alerts from './alerts'
|
||||
import torrentStore from './torrents'
|
||||
import movieStore from "./movies"
|
||||
import showsStore from "./shows"
|
||||
import showStore from "./show"
|
||||
import userStore from "./users"
|
||||
import alerts from "./alerts"
|
||||
import torrentStore from "./torrents"
|
||||
|
||||
// Use combine form form react-redux-form, it's a thin wrapper arround the
|
||||
// default combinedReducers provided with React. It allows the forms to be
|
||||
// linked directly to the store.
|
||||
const rootReducer = combineForms({
|
||||
const rootReducer = combineReducers({
|
||||
routing: routerReducer,
|
||||
movieStore,
|
||||
showsStore,
|
||||
|
@ -1,96 +1,52 @@
|
||||
const defaultState = {
|
||||
import { OrderedMap, Map, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
loading: false,
|
||||
movies: [],
|
||||
movies: OrderedMap(),
|
||||
filter: "",
|
||||
perPage: 30,
|
||||
selectedImdbId: "",
|
||||
fetchingDetails: false,
|
||||
lastFetchUrl: "",
|
||||
exploreOptions: {},
|
||||
};
|
||||
exploreOptions: Map(),
|
||||
});
|
||||
|
||||
export default function movieStore(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'MOVIE_LIST_FETCH_PENDING':
|
||||
return Object.assign({}, state, {
|
||||
loading: true,
|
||||
})
|
||||
case 'MOVIE_LIST_FETCH_FULFILLED':
|
||||
let selectedImdbId = "";
|
||||
// Select the first movie
|
||||
if (action.payload.response.data.length > 0) {
|
||||
// Sort by year
|
||||
action.payload.response.data.sort((a,b) => b.year - a.year);
|
||||
selectedImdbId = action.payload.response.data[0].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
|
||||
}
|
||||
const handlers = {
|
||||
"MOVIE_LIST_FETCH_PENDING": state => state.set("loading", true),
|
||||
"MOVIE_LIST_FETCH_FULFILLED": (state, action) => {
|
||||
let movies = Map();
|
||||
action.payload.response.data.map(function (movie) {
|
||||
movie.fetchingDetails = false;
|
||||
movie.fetchingSubtitles = false;
|
||||
movies = movies.set(movie.imdb_id, fromJS(movie));
|
||||
})
|
||||
|
||||
return Object.assign({}, state, {
|
||||
selectedImdbId: action.imdbId,
|
||||
})
|
||||
default:
|
||||
return state
|
||||
}
|
||||
// Select the first movie if the list is not empty
|
||||
let selectedImdbId = "";
|
||||
if (movies.size > 0) {
|
||||
// Sort by year
|
||||
movies = movies.sort((a,b) => b.get("year") - a.get("year"));
|
||||
selectedImdbId = movies.first().get("imdb_id");
|
||||
}
|
||||
|
||||
return state.delete("movies").merge(Map({
|
||||
movies: movies,
|
||||
filter: "",
|
||||
loading: false,
|
||||
selectedImdbId: selectedImdbId,
|
||||
}))
|
||||
},
|
||||
"MOVIE_GET_DETAILS_PENDING" : (state, action) => state.setIn(["movies", action.payload.main.imdbId, "fetchingDetails"], true),
|
||||
"MOVIE_GET_DETAILS_FULFILLED" : (state, action) => state.setIn(["movies", action.payload.response.data.imdb_id], fromJS(action.payload.response.data))
|
||||
.setIn(["movies", action.payload.response.data.imdb_id, "fetchingDetails"], false)
|
||||
.setIn(["movies", action.payload.response.data.imdb_id, "fetchingSubtitles"], false),
|
||||
"MOVIE_UPDATE_STORE_WISHLIST" : (state, action) => state.setIn(["movies", action.payload.imdbId, "wishlisted"], action.payload.wishlisted),
|
||||
"MOVIE_GET_EXPLORE_OPTIONS_FULFILLED" : (state, action) => state.set("exploreOptions", fromJS(action.payload.response.data)),
|
||||
"UPDATE_LAST_MOVIE_FETCH_URL" : (state, action) => state.set("lastFetchUrl", action.payload.url),
|
||||
"MOVIE_SUBTITLES_UPDATE_PENDING" : (state, action) => state.setIn(["movies", action.payload.main.imdbId, "fetchingSubtitles"], true),
|
||||
"MOVIE_SUBTITLES_UPDATE_FULFILLED" : (state, action) => state.setIn(["movies", action.payload.main.imdbId, "fetchingSubtitles"], false)
|
||||
.setIn(["movies", action.payload.main.imdbId, "subtitles"], fromJS(action.payload.response.data)),
|
||||
"SELECT_MOVIE" : (state, action) => state.set("selectedImdbId", action.payload.imdbId),
|
||||
"MOVIE_UPDATE_FILTER" : (state, action) => state.set("filter", action.payload.filter),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
export default (state = defaultState, action) =>
|
||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { OrderedMap, Map, List, fromJS } from 'immutable'
|
||||
import { OrderedMap, Map, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
loading: false,
|
||||
@ -7,88 +7,70 @@ const defaultState = Map({
|
||||
}),
|
||||
});
|
||||
|
||||
export default function showStore(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'SHOW_FETCH_DETAILS_PENDING':
|
||||
return state.set('loading', true)
|
||||
case 'SHOW_FETCH_DETAILS_FULFILLED':
|
||||
return sortEpisodes(state, action.payload.response.data);
|
||||
case 'SHOW_UPDATE_STORE_WISHLIST':
|
||||
let season = action.payload.season;
|
||||
let episode = action.payload.episode;
|
||||
if (action.payload.wishlisted && season === null) {
|
||||
season = 0;
|
||||
episode = 0;
|
||||
const handlers = {
|
||||
"SHOW_FETCH_DETAILS_PENDING": state => state.set("loading", true),
|
||||
"SHOW_FETCH_DETAILS_FULFILLED": (state, action) => sortEpisodes(state, action.payload.response.data),
|
||||
"SHOW_UPDATE_STORE_WISHLIST": (state, action) => {
|
||||
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,
|
||||
}
|
||||
return state.mergeDeep(fromJS({
|
||||
'show': {
|
||||
'tracked_season': season,
|
||||
'tracked_episode': episode,
|
||||
}
|
||||
}));
|
||||
case 'EPISODE_GET_DETAILS_PENDING':
|
||||
return state.setIn(['show', 'seasons', action.payload.main.season, action.payload.main.episode, 'fetching'], true);
|
||||
case 'EPISODE_GET_DETAILS_FULFILLED':
|
||||
let data = action.payload.response.data;
|
||||
if (!data) { return state }
|
||||
data.fetching = false;
|
||||
return state.setIn(['show', 'seasons', data.season, data.episode], fromJS(data));
|
||||
case 'EPISODE_SUBTITLES_UPDATE_PENDING':
|
||||
return state.setIn(['show', 'seasons', action.payload.main.season, action.payload.main.episode, 'fetchingSubtitles'], true);
|
||||
case 'EPISODE_SUBTITLES_UPDATE_FULFILLED':
|
||||
let epId = ['show', 'seasons', action.payload.main.season, action.payload.main.episode];
|
||||
let ep = state.getIn(epId);
|
||||
ep = ep.set('subtitles', fromJS(action.payload.response.data)).set('fetchingSubtitles', false);
|
||||
return state.setIn(epId, ep);
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}))},
|
||||
"EPISODE_GET_DETAILS_PENDING": (state, action) => state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetching"], true),
|
||||
"EPISODE_GET_DETAILS_FULFILLED": (state, action) => {
|
||||
let data = action.payload.response.data;
|
||||
if (!data) { return state }
|
||||
data.fetching = false;
|
||||
return state.setIn(["show", "seasons", data.season, data.episode], fromJS(data));
|
||||
},
|
||||
"EPISODE_SUBTITLES_UPDATE_PENDING" : (state, action) =>
|
||||
state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetchingSubtitles"], true),
|
||||
"EPISODE_SUBTITLES_UPDATE_FULFILLED": (state, action) =>
|
||||
state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "subtitles"], fromJS(action.payload.response.data))
|
||||
.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetchingSubtitles"], false),
|
||||
}
|
||||
|
||||
function updateEpisode(state, fetching, data = null) {
|
||||
if (data === null) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (!state.hasIn(['show', 'season', data.season, data.episode])) {
|
||||
return show;
|
||||
}
|
||||
|
||||
data.fetching = fetching
|
||||
return show.updateIn(['show', 'seasons', data.season, data.episode], fromJS(data));
|
||||
}
|
||||
|
||||
function sortEpisodes(state, show) {
|
||||
const sortEpisodes = (state, show) => {
|
||||
let episodes = show.episodes;
|
||||
delete show["episodes"];
|
||||
|
||||
let ret = state.set('loading', false);
|
||||
let ret = state.set("loading", false);
|
||||
if (episodes.length == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Set the show data
|
||||
ret = ret.set('show', fromJS(show));
|
||||
ret = ret.set("show", fromJS(show));
|
||||
|
||||
// Set the show episodes
|
||||
for (let ep of episodes) {
|
||||
ep.fetching = false;
|
||||
ret = ret.setIn(['show', 'seasons', ep.season, ep.episode], fromJS(ep));
|
||||
ret = ret.setIn(["show", "seasons", ep.season, ep.episode], fromJS(ep));
|
||||
}
|
||||
|
||||
// Sort the episodes
|
||||
ret = ret.updateIn(['show', 'seasons'], function(seasons) {
|
||||
ret = ret.updateIn(["show", "seasons"], function(seasons) {
|
||||
return seasons.map(function(episodes) {
|
||||
return episodes.sort((a,b) => a.get('episode') - b.get('episode'));
|
||||
return episodes.sort((a,b) => a.get("episode") - b.get("episode"));
|
||||
});
|
||||
});
|
||||
|
||||
// Sort the seasons
|
||||
ret = ret.updateIn(['show', 'seasons'], function(seasons) {
|
||||
ret = ret.updateIn(["show", "seasons"], function(seasons) {
|
||||
return seasons.sort(function(a,b) {
|
||||
return a.first().get('season') - b.first().get('season');
|
||||
return a.first().get("season") - b.first().get("season");
|
||||
});
|
||||
});
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||
|
@ -1,101 +1,67 @@
|
||||
const defaultState = {
|
||||
import { OrderedMap, Map, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
loading: false,
|
||||
shows: [],
|
||||
shows: OrderedMap(),
|
||||
filter: "",
|
||||
perPage: 30,
|
||||
selectedImdbId: "",
|
||||
getDetails: false,
|
||||
lastShowsFetchUrl: "",
|
||||
exploreOptions: {},
|
||||
};
|
||||
lastFetchUrl: "",
|
||||
exploreOptions: Map(),
|
||||
});
|
||||
|
||||
export default function showsStore(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'SHOW_LIST_FETCH_PENDING':
|
||||
return Object.assign({}, state, {
|
||||
loading: true,
|
||||
})
|
||||
case 'SHOW_LIST_FETCH_FULFILLED':
|
||||
let selectedImdbId = "";
|
||||
// Select the first show
|
||||
console.log("Hey", action.payload);
|
||||
if (action.payload.response.data.length > 0) {
|
||||
selectedImdbId = action.payload.response.data[0].imdb_id;
|
||||
}
|
||||
return Object.assign({}, state, {
|
||||
shows: action.payload.response.data,
|
||||
selectedImdbId: selectedImdbId,
|
||||
filter: defaultState.filter,
|
||||
loading: false,
|
||||
})
|
||||
case 'SHOW_GET_DETAILS_PENDING':
|
||||
return Object.assign({}, state, {
|
||||
getDetails: true,
|
||||
})
|
||||
case 'SHOW_GET_DETAILS_FULFILLED':
|
||||
return Object.assign({}, state, {
|
||||
shows: updateShowDetails(state.shows.slice(), action.payload.response.data),
|
||||
getDetails: false,
|
||||
})
|
||||
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':
|
||||
return Object.assign({}, state, {
|
||||
exploreOptions: action.payload.response.data,
|
||||
})
|
||||
case 'SHOW_UPDATE_STORE_WISHLIST':
|
||||
return Object.assign({}, state, {
|
||||
shows: updateShowsStoreWishlist(state.shows.slice(), action.payload),
|
||||
})
|
||||
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
|
||||
}
|
||||
const handlers = {
|
||||
"SHOW_LIST_FETCH_PENDING": state => state.set("loading", true),
|
||||
"SHOW_LIST_FETCH_FULFILLED": (state, action) => {
|
||||
let shows = Map();
|
||||
action.payload.response.data.map(function (show) {
|
||||
show.fetchingDetails = false;
|
||||
show.fetchingSubtitles = false;
|
||||
shows = shows.set(show.imdb_id, fromJS(show));
|
||||
});
|
||||
|
||||
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) {
|
||||
season = 0;
|
||||
let selectedImdbId = "";
|
||||
if (shows.size > 0) {
|
||||
// Sort by year
|
||||
shows = shows.sort((a,b) => b.get("year") - a.get("year"));
|
||||
selectedImdbId = shows.first().get("imdb_id");
|
||||
}
|
||||
if (episode === null) {
|
||||
episode = 0;
|
||||
|
||||
return state.delete("shows").merge(Map({
|
||||
shows: shows,
|
||||
filter: "",
|
||||
loading: false,
|
||||
selectedImdbId: selectedImdbId,
|
||||
}));
|
||||
},
|
||||
"SHOW_GET_DETAILS_PENDING": (state, action) => state.setIn(["shows", action.payload.main.imdbId, "fetchingDetails"], true),
|
||||
"SHOW_GET_DETAILS_FULFILLED": (state, action) => {
|
||||
let show = action.payload.response.data;
|
||||
show.fetchingDetails = false;
|
||||
show.fetchingSubtitles = false;
|
||||
return state.setIn(["shows", show.imdb_id], fromJS(show));
|
||||
},
|
||||
"SHOW_GET_EXPLORE_OPTIONS_FULFILLED": (state, action) => state.set("exploreOptions", fromJS(action.payload.response.data)),
|
||||
"SHOW_UPDATE_STORE_WISHLIST": (state, action) => {
|
||||
let season = action.payload.season;
|
||||
let episode = action.payload.episode;
|
||||
if (action.payload.wishlisted) {
|
||||
if (season === null) {
|
||||
season = 0;
|
||||
}
|
||||
if (episode === null) {
|
||||
episode = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
shows[index].tracked_season = season;
|
||||
shows[index].tracked_episode = episode;
|
||||
return shows
|
||||
|
||||
return state.mergeIn(["shows", action.payload.imdbId], Map({
|
||||
"tracked_season": season,
|
||||
"tracked_episode": episode,
|
||||
}));
|
||||
},
|
||||
"UPDATE_LAST_SHOWS_FETCH_URL": (state, action) => state.set("lastFetchUrl", action.payload.url),
|
||||
"SELECT_SHOW": (state, action) => state.set("selectedImdbId", action.payload.imdbId),
|
||||
"SHOWS_UPDATE_FILTER": (state, action) => state.set("filter", action.payload.filter),
|
||||
}
|
||||
|
||||
function updateShowDetails(shows, data) {
|
||||
let index = shows.map((el) => el.imdb_id).indexOf(data.imdb_id);
|
||||
shows[index] = data;
|
||||
return shows
|
||||
}
|
||||
export default (state = defaultState, action) =>
|
||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||
|
@ -1,20 +1,17 @@
|
||||
import { Map, List, fromJS } from 'immutable'
|
||||
import { Map, List, fromJS } from "immutable"
|
||||
|
||||
const defaultState = Map({
|
||||
'fetching': false,
|
||||
'torrents': List(),
|
||||
"fetching": false,
|
||||
"torrents": List(),
|
||||
});
|
||||
|
||||
export default function torrentStore(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'TORRENTS_FETCH_PENDING':
|
||||
return state.set('fetching', false);
|
||||
case 'TORRENTS_FETCH_FULFILLED':
|
||||
return state.merge(fromJS({
|
||||
fetching: false,
|
||||
torrents: action.payload.response.data,
|
||||
}));
|
||||
default:
|
||||
return state
|
||||
}
|
||||
const handlers = {
|
||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", false),
|
||||
"TORRENTS_FETCH_FULFILLED": (state, action) => state.merge(fromJS({
|
||||
fetching: false,
|
||||
torrents: action.payload.response.data,
|
||||
})),
|
||||
}
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||
|
@ -1,59 +1,54 @@
|
||||
import jwtDecode from 'jwt-decode'
|
||||
import Cookies from 'universal-cookie'
|
||||
import { Map } from "immutable"
|
||||
|
||||
const defaultState = {
|
||||
userLoading: false,
|
||||
import jwtDecode from "jwt-decode"
|
||||
import Cookies from "universal-cookie"
|
||||
|
||||
const defaultState = Map({
|
||||
loading: false,
|
||||
username: "",
|
||||
isAdmin: false,
|
||||
isLogged: false,
|
||||
polochonToken: "",
|
||||
polochonUrl: "",
|
||||
};
|
||||
});
|
||||
|
||||
export default function userStore(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'USER_LOGIN_PENDING':
|
||||
return Object.assign({}, state, {
|
||||
userLoading: true,
|
||||
})
|
||||
case 'USER_LOGIN_FULFILLED':
|
||||
if (action.payload.response.status === "error") {
|
||||
return logoutUser(state)
|
||||
}
|
||||
return updateFromToken(state, action.payload.response.data.token)
|
||||
case 'USER_SET_TOKEN':
|
||||
return updateFromToken(state, action.payload.token)
|
||||
case 'USER_LOGOUT':
|
||||
return logoutUser(state)
|
||||
case 'GET_USER_FULFILLED':
|
||||
return Object.assign({}, state, {
|
||||
polochonToken: action.payload.response.data.token,
|
||||
polochonUrl: action.payload.response.data.url,
|
||||
})
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
const handlers = {
|
||||
"USER_LOGIN_PENDING": state => state.set("loading", true),
|
||||
"USER_LOGIN_FULFILLED": (state, action) => {
|
||||
if (action.payload.response.status === "error") {
|
||||
return logoutUser()
|
||||
}
|
||||
return updateFromToken(state, action.payload.response.data.token)
|
||||
},
|
||||
"USER_SET_TOKEN": (state, action) => updateFromToken(state, action.payload.token),
|
||||
"USER_LOGOUT": () => logoutUser(),
|
||||
"GET_USER_FULFILLED": (state, action) => state.merge(Map({
|
||||
polochonToken: action.payload.response.data.token,
|
||||
polochonUrl: action.payload.response.data.url,
|
||||
})),
|
||||
}
|
||||
|
||||
function logoutUser(state) {
|
||||
localStorage.removeItem('token');
|
||||
function logoutUser() {
|
||||
localStorage.removeItem("token");
|
||||
const cookies = new Cookies();
|
||||
cookies.remove('token');
|
||||
|
||||
return Object.assign({}, state, defaultState)
|
||||
cookies.remove("token");
|
||||
return defaultState
|
||||
}
|
||||
|
||||
function updateFromToken(state, token) {
|
||||
const decodedToken = jwtDecode(token);
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem("token", token);
|
||||
|
||||
const cookies = new Cookies();
|
||||
cookies.set('token', token);
|
||||
cookies.set("token", token);
|
||||
|
||||
return Object.assign({}, state, {
|
||||
return state.merge(Map({
|
||||
userLoading: false,
|
||||
isLogged: true,
|
||||
isAdmin: decodedToken.isAdmin,
|
||||
username: decodedToken.username,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export default (state = defaultState, action) =>
|
||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import axios from 'axios'
|
||||
import axios from "axios"
|
||||
|
||||
// This functions returns an axios instance, the token is added to the
|
||||
// configuration if found in the localStorage
|
||||
export function configureAxios(headers = {}) {
|
||||
// Get the token from the localStorate
|
||||
const token = localStorage.getItem('token');
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
headers = { 'Authorization': `Bearer ${token}` };
|
||||
headers = { "Authorization": `Bearer ${token}` };
|
||||
}
|
||||
|
||||
return axios.create({
|
||||
@ -30,15 +30,16 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
||||
})
|
||||
promise
|
||||
.then(response => {
|
||||
if (response.data.status === 'error')
|
||||
if (response.data.status === "error")
|
||||
{
|
||||
dispatch({
|
||||
type: 'ADD_ALERT_ERROR',
|
||||
type: "ADD_ALERT_ERROR",
|
||||
payload: {
|
||||
message: response.data.message,
|
||||
main: mainPayload,
|
||||
}
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
dispatch({
|
||||
type: fulfilled,
|
||||
@ -57,11 +58,11 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
||||
// Unauthorized
|
||||
if (error.response && error.response.status == 401) {
|
||||
dispatch({
|
||||
type: 'USER_LOGOUT',
|
||||
type: "USER_LOGOUT",
|
||||
})
|
||||
}
|
||||
dispatch({
|
||||
type: 'ADD_ALERT_ERROR',
|
||||
type: "ADD_ALERT_ERROR",
|
||||
payload: {
|
||||
message: error.response.data,
|
||||
main: mainPayload,
|
||||
|
@ -1,31 +1,29 @@
|
||||
import React from 'react'
|
||||
import MovieList from "./components/movies/list"
|
||||
import ShowList from "./components/shows/list"
|
||||
import ShowDetails from "./components/shows/details"
|
||||
import UserLoginForm from "./components/users/login"
|
||||
import UserEdit from "./components/users/edit"
|
||||
import UserSignUp from "./components/users/signup"
|
||||
import TorrentList from "./components/torrents/list"
|
||||
|
||||
import MovieList from './components/movies/list'
|
||||
import ShowList from './components/shows/list'
|
||||
import ShowDetails from './components/shows/details'
|
||||
import UserLoginForm from './components/users/login'
|
||||
import UserEdit from './components/users/edit'
|
||||
import UserSignUp from './components/users/signup'
|
||||
import TorrentList from './components/torrents/list'
|
||||
import { fetchTorrents } from "./actions/torrents"
|
||||
import { userLogout, getUserInfos } from "./actions/users"
|
||||
import { fetchMovies, getMovieExploreOptions } from "./actions/movies"
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "./actions/shows"
|
||||
|
||||
import { fetchTorrents } from './actions/torrents'
|
||||
import { userLogout, getUserInfos } from './actions/users'
|
||||
import { fetchMovies, getMovieExploreOptions } from './actions/movies'
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from './actions/shows'
|
||||
|
||||
import store from './store'
|
||||
import store from "./store"
|
||||
|
||||
// This function returns true if the user is logged in, false otherwise
|
||||
function isLoggedIn() {
|
||||
const state = store.getState();
|
||||
const isLogged = state.userStore.isLogged;
|
||||
let token = localStorage.getItem('token');
|
||||
let token = localStorage.getItem("token");
|
||||
|
||||
// Let's check if the user has a token, if he does let's assume he's logged
|
||||
// in. If that's not the case he will be logged out on the fisrt query
|
||||
if (token && token !== "") {
|
||||
store.dispatch({
|
||||
type: 'USER_SET_TOKEN',
|
||||
type: "USER_SET_TOKEN",
|
||||
payload: {
|
||||
token: token,
|
||||
},
|
||||
@ -43,7 +41,7 @@ var pollingTorrentsId;
|
||||
const loginCheck = function(nextState, replace, next, f = null) {
|
||||
const loggedIn = isLoggedIn();
|
||||
if (!loggedIn) {
|
||||
replace('/users/login');
|
||||
replace("/users/login");
|
||||
} else {
|
||||
if (f) {
|
||||
f();
|
||||
@ -61,19 +59,19 @@ const loginCheck = function(nextState, replace, next, f = null) {
|
||||
next();
|
||||
}
|
||||
|
||||
const defaultRoute = '/movies/explore/yts/seeds';
|
||||
const defaultRoute = "/movies/explore/yts/seeds";
|
||||
export default function getRoutes(App) {
|
||||
return {
|
||||
path: '/',
|
||||
path: "/",
|
||||
component: App,
|
||||
indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)},
|
||||
childRoutes: [
|
||||
{
|
||||
path: '/users/signup',
|
||||
path: "/users/signup",
|
||||
component: UserSignUp
|
||||
},
|
||||
{
|
||||
path: '/users/login',
|
||||
path: "/users/login",
|
||||
component: UserLoginForm,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
if (isLoggedIn()) {
|
||||
@ -84,7 +82,7 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/edit',
|
||||
path: "/users/edit",
|
||||
component: UserEdit,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
@ -93,7 +91,7 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/logout',
|
||||
path: "/users/logout",
|
||||
onEnter: function(nextState, replace, next) {
|
||||
// Stop polling
|
||||
if (pollingTorrentsId !== null) {
|
||||
@ -101,12 +99,12 @@ export default function getRoutes(App) {
|
||||
pollingTorrentsId = null;
|
||||
}
|
||||
store.dispatch(userLogout());
|
||||
replace('/users/login');
|
||||
replace("/users/login");
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/search/:search',
|
||||
path: "/movies/search/:search",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
@ -115,22 +113,22 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/polochon',
|
||||
path: "/movies/polochon",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies('/movies/polochon'));
|
||||
store.dispatch(fetchMovies("/movies/polochon"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/explore/:source/:category',
|
||||
path: "/movies/explore/:source/:category",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (Object.keys(state.movieStore.exploreOptions).length === 0) {
|
||||
if (state.movieStore.get("exploreOptions").size === 0) {
|
||||
store.dispatch(getMovieExploreOptions());
|
||||
}
|
||||
store.dispatch(fetchMovies(
|
||||
@ -140,16 +138,16 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/wishlist',
|
||||
path: "/movies/wishlist",
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies('/wishlist/movies'));
|
||||
store.dispatch(fetchMovies("/wishlist/movies"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/search/:search',
|
||||
path: "/shows/search/:search",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
@ -158,25 +156,25 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/polochon',
|
||||
path: "/shows/polochon",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows('/shows/polochon'));
|
||||
store.dispatch(fetchShows("/shows/polochon"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/wishlist',
|
||||
path: "/shows/wishlist",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows('/wishlist/shows'));
|
||||
store.dispatch(fetchShows("/wishlist/shows"));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/details/:imdbId',
|
||||
path: "/shows/details/:imdbId",
|
||||
component: ShowDetails,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
@ -185,13 +183,13 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/explore/:source/:category',
|
||||
path: "/shows/explore/:source/:category",
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (Object.keys(state.showsStore.exploreOptions).length === 0) {
|
||||
if (state.showsStore.get("exploreOptions").size === 0) {
|
||||
store.dispatch(getShowExploreOptions());
|
||||
}
|
||||
store.dispatch(fetchShows(
|
||||
@ -201,7 +199,7 @@ export default function getRoutes(App) {
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/torrents',
|
||||
path: "/torrents",
|
||||
component: TorrentList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { createStore, applyMiddleware, compose } from 'redux';
|
||||
import { syncHistoryWithStore } from 'react-router-redux'
|
||||
import { hashHistory } from 'react-router'
|
||||
import thunk from 'redux-thunk'
|
||||
import { routerMiddleware } from 'react-router-redux'
|
||||
import { createStore, applyMiddleware, compose } from "redux";
|
||||
import { syncHistoryWithStore } from "react-router-redux"
|
||||
import { hashHistory } from "react-router"
|
||||
import thunk from "redux-thunk"
|
||||
import { routerMiddleware } from "react-router-redux"
|
||||
|
||||
// Import the root reducer
|
||||
import rootReducer from './reducers/index'
|
||||
import rootReducer from "./reducers/index"
|
||||
|
||||
const routingMiddleware = routerMiddleware(hashHistory)
|
||||
|
||||
const middlewares = [thunk, routingMiddleware];
|
||||
|
||||
// Only use in development mode (set in webpack)
|
||||
if (process.env.NODE_ENV === `development`) {
|
||||
const createLogger = require(`redux-logger`);
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const createLogger = require("redux-logger");
|
||||
const logger = createLogger();
|
||||
middlewares.push(logger);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user