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