commit
e85554fbaa
@ -11,6 +11,7 @@
|
||||
"font-awesome": "^4.7.0",
|
||||
"fuzzy": "^0.1.3",
|
||||
"history": "^4.4.0",
|
||||
"immutable": "^3.8.1",
|
||||
"jquery": "^2.2.4",
|
||||
"jwt-decode": "^2.1.0",
|
||||
"react": "^15.3.2",
|
||||
|
@ -1,293 +0,0 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
// ======================
|
||||
// Alerts
|
||||
// ======================
|
||||
|
||||
export function addAlertError(message) {
|
||||
return {
|
||||
type: 'ADD_ALERT_ERROR',
|
||||
payload: {
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addAlertOk(message) {
|
||||
return {
|
||||
type: 'ADD_ALERT_OK',
|
||||
payload: {
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dismissAlert() {
|
||||
return {
|
||||
type: 'DISMISS_ALERT',
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// Users
|
||||
// ======================
|
||||
|
||||
export function userLogout() {
|
||||
return {
|
||||
type: 'USER_LOGOUT',
|
||||
}
|
||||
}
|
||||
|
||||
export function loginUser(username, password) {
|
||||
return request(
|
||||
'USER_LOGIN',
|
||||
configureAxios().post(
|
||||
'/users/login',
|
||||
{
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function updateUser(config) {
|
||||
return request(
|
||||
'USER_UPDATE',
|
||||
configureAxios().post('/users/edit', config),
|
||||
[
|
||||
addAlertOk("User updated"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function userSignUp(config) {
|
||||
return request(
|
||||
'USER_SIGNUP',
|
||||
configureAxios().post('/users/signup', config)
|
||||
)
|
||||
}
|
||||
|
||||
export function getUserInfos() {
|
||||
return request(
|
||||
'GET_USER',
|
||||
configureAxios().get('/users/details')
|
||||
)
|
||||
}
|
||||
|
||||
// ======================
|
||||
// Movies
|
||||
// ======================
|
||||
|
||||
export function updateLastMovieFetchUrl(url) {
|
||||
return {
|
||||
type: 'UPDATE_LAST_MOVIE_FETCH_URL',
|
||||
payload: {
|
||||
url: url,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function selectMovie(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_MOVIE',
|
||||
imdbId
|
||||
}
|
||||
}
|
||||
|
||||
export function getMovieExploreOptions() {
|
||||
return request(
|
||||
'MOVIE_GET_EXPLORE_OPTIONS',
|
||||
configureAxios().get('/movies/explore/options')
|
||||
)
|
||||
}
|
||||
|
||||
export function getMovieDetails(imdbId) {
|
||||
return request(
|
||||
'MOVIE_GET_DETAILS',
|
||||
configureAxios().post(`/movies/${imdbId}/refresh`)
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteMovie(imdbId, lastFetchUrl) {
|
||||
return request(
|
||||
'MOVIE_DELETE',
|
||||
configureAxios().delete(`/movies/${imdbId}`),
|
||||
[
|
||||
fetchMovies(lastFetchUrl),
|
||||
addAlertOk("Movie deleted"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function addMovieToWishlist(imdbId) {
|
||||
return request(
|
||||
'MOVIE_ADD_TO_WISHLIST',
|
||||
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Movie added to the wishlist"),
|
||||
updateMovieWishlistStore(imdbId, true),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteMovieFromWishlist(imdbId) {
|
||||
return request(
|
||||
'MOVIE_DELETE_FROM_WISHLIST',
|
||||
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Movie deleted from the wishlist"),
|
||||
updateMovieWishlistStore(imdbId, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function updateMovieWishlistStore(imdbId, wishlisted) {
|
||||
return {
|
||||
type: 'MOVIE_UPDATE_STORE_WISHLIST',
|
||||
payload: {
|
||||
imdbId,
|
||||
wishlisted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchMovies(url) {
|
||||
return request(
|
||||
'MOVIE_LIST_FETCH',
|
||||
configureAxios().get(url),
|
||||
[
|
||||
updateLastMovieFetchUrl(url),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
// ======================
|
||||
// Shows
|
||||
// ======================
|
||||
|
||||
export function fetchShows(url) {
|
||||
return request(
|
||||
'SHOW_LIST_FETCH',
|
||||
configureAxios().get(url),
|
||||
[
|
||||
updateLastShowsFetchUrl(url),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
export function getShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_GET_DETAILS',
|
||||
configureAxios().post(`/shows/${imdbId}/refresh`)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function getEpisodeDetails(imdbId, season, episode) {
|
||||
return request(
|
||||
'EPISODE_GET_DETAILS',
|
||||
configureAxios().post(`/shows/${imdbId}/seasons/${season}/episodes/${episode}`),
|
||||
)
|
||||
}
|
||||
|
||||
export function updateEpisodeDetailsStore(imdbId, season, episode) {
|
||||
return {
|
||||
type: 'EPISODE_GET_DETAILS',
|
||||
payload: {
|
||||
imdbId,
|
||||
season,
|
||||
episode,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_FETCH_DETAILS',
|
||||
configureAxios().get(`/shows/${imdbId}`)
|
||||
)
|
||||
}
|
||||
|
||||
export function addShowToWishlist(imdbId, season = null, episode = null) {
|
||||
return request(
|
||||
'SHOW_ADD_TO_WISHLIST',
|
||||
configureAxios().post(`/wishlist/shows/${imdbId}`, {
|
||||
season: season,
|
||||
episode: episode,
|
||||
}),
|
||||
[
|
||||
addAlertOk("Show added to the wishlist"),
|
||||
updateShowWishlistStore(imdbId, true, season, episode),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteShowFromWishlist(imdbId) {
|
||||
return request(
|
||||
'SHOW_DELETE_FROM_WISHLIST',
|
||||
configureAxios().delete(`/wishlist/shows/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Show deleted from the wishlist"),
|
||||
updateShowWishlistStore(imdbId, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) {
|
||||
return {
|
||||
type: 'SHOW_UPDATE_STORE_WISHLIST',
|
||||
payload: {
|
||||
wishlisted: wishlisted,
|
||||
imdbId,
|
||||
season,
|
||||
episode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getShowExploreOptions() {
|
||||
return request(
|
||||
'SHOW_GET_EXPLORE_OPTIONS',
|
||||
configureAxios().get('/shows/explore/options')
|
||||
)
|
||||
}
|
||||
|
||||
export function selectShow(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_SHOW',
|
||||
imdbId
|
||||
}
|
||||
}
|
||||
|
||||
export function updateLastShowsFetchUrl(url) {
|
||||
return {
|
||||
type: 'UPDATE_LAST_SHOWS_FETCH_URL',
|
||||
payload: {
|
||||
url: url,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// AddTorrent
|
||||
// ======================
|
||||
|
||||
export function addTorrent(url) {
|
||||
return request(
|
||||
'ADD_TORRENT',
|
||||
configureAxios().post('/torrents', {
|
||||
url: url,
|
||||
}),
|
||||
[
|
||||
addAlertOk("Torrent added"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchTorrents() {
|
||||
return request(
|
||||
'TORRENTS_FETCH',
|
||||
configureAxios().get('/torrents')
|
||||
)
|
||||
}
|
23
src/public/js/actions/alerts.js
Normal file
23
src/public/js/actions/alerts.js
Normal file
@ -0,0 +1,23 @@
|
||||
export function addAlertError(message) {
|
||||
return {
|
||||
type: 'ADD_ALERT_ERROR',
|
||||
payload: {
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addAlertOk(message) {
|
||||
return {
|
||||
type: 'ADD_ALERT_OK',
|
||||
payload: {
|
||||
message,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function dismissAlert() {
|
||||
return {
|
||||
type: 'DISMISS_ALERT',
|
||||
}
|
||||
}
|
86
src/public/js/actions/movies.js
Normal file
86
src/public/js/actions/movies.js
Normal file
@ -0,0 +1,86 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
|
||||
export function updateLastMovieFetchUrl(url) {
|
||||
return {
|
||||
type: 'UPDATE_LAST_MOVIE_FETCH_URL',
|
||||
payload: {
|
||||
url: url,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function selectMovie(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_MOVIE',
|
||||
imdbId
|
||||
}
|
||||
}
|
||||
|
||||
export function getMovieExploreOptions() {
|
||||
return request(
|
||||
'MOVIE_GET_EXPLORE_OPTIONS',
|
||||
configureAxios().get('/movies/explore/options')
|
||||
)
|
||||
}
|
||||
|
||||
export function getMovieDetails(imdbId) {
|
||||
return request(
|
||||
'MOVIE_GET_DETAILS',
|
||||
configureAxios().post(`/movies/${imdbId}/refresh`)
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteMovie(imdbId, lastFetchUrl) {
|
||||
return request(
|
||||
'MOVIE_DELETE',
|
||||
configureAxios().delete(`/movies/${imdbId}`),
|
||||
[
|
||||
fetchMovies(lastFetchUrl),
|
||||
addAlertOk("Movie deleted"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function addMovieToWishlist(imdbId) {
|
||||
return request(
|
||||
'MOVIE_ADD_TO_WISHLIST',
|
||||
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Movie added to the wishlist"),
|
||||
updateMovieWishlistStore(imdbId, true),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteMovieFromWishlist(imdbId) {
|
||||
return request(
|
||||
'MOVIE_DELETE_FROM_WISHLIST',
|
||||
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Movie deleted from the wishlist"),
|
||||
updateMovieWishlistStore(imdbId, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function updateMovieWishlistStore(imdbId, wishlisted) {
|
||||
return {
|
||||
type: 'MOVIE_UPDATE_STORE_WISHLIST',
|
||||
payload: {
|
||||
imdbId,
|
||||
wishlisted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchMovies(url) {
|
||||
return request(
|
||||
'MOVIE_LIST_FETCH',
|
||||
configureAxios().get(url),
|
||||
[
|
||||
updateLastMovieFetchUrl(url),
|
||||
]
|
||||
)
|
||||
}
|
106
src/public/js/actions/shows.js
Normal file
106
src/public/js/actions/shows.js
Normal file
@ -0,0 +1,106 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
|
||||
export function fetchShows(url) {
|
||||
return request(
|
||||
'SHOW_LIST_FETCH',
|
||||
configureAxios().get(url),
|
||||
[
|
||||
updateLastShowsFetchUrl(url),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
export function getShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_GET_DETAILS',
|
||||
configureAxios().post(`/shows/${imdbId}/refresh`)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export function getEpisodeDetails(imdbId, season, episode) {
|
||||
return request(
|
||||
'EPISODE_GET_DETAILS',
|
||||
configureAxios().post(`/shows/${imdbId}/seasons/${season}/episodes/${episode}`),
|
||||
)
|
||||
}
|
||||
|
||||
export function updateEpisodeDetailsStore(imdbId, season, episode) {
|
||||
return {
|
||||
type: 'EPISODE_GET_DETAILS',
|
||||
payload: {
|
||||
imdbId,
|
||||
season,
|
||||
episode,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchShowDetails(imdbId) {
|
||||
return request(
|
||||
'SHOW_FETCH_DETAILS',
|
||||
configureAxios().get(`/shows/${imdbId}`)
|
||||
)
|
||||
}
|
||||
|
||||
export function addShowToWishlist(imdbId, season = null, episode = null) {
|
||||
return request(
|
||||
'SHOW_ADD_TO_WISHLIST',
|
||||
configureAxios().post(`/wishlist/shows/${imdbId}`, {
|
||||
season: season,
|
||||
episode: episode,
|
||||
}),
|
||||
[
|
||||
addAlertOk("Show added to the wishlist"),
|
||||
updateShowWishlistStore(imdbId, true, season, episode),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteShowFromWishlist(imdbId) {
|
||||
return request(
|
||||
'SHOW_DELETE_FROM_WISHLIST',
|
||||
configureAxios().delete(`/wishlist/shows/${imdbId}`),
|
||||
[
|
||||
addAlertOk("Show deleted from the wishlist"),
|
||||
updateShowWishlistStore(imdbId, false),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) {
|
||||
return {
|
||||
type: 'SHOW_UPDATE_STORE_WISHLIST',
|
||||
payload: {
|
||||
wishlisted: wishlisted,
|
||||
imdbId,
|
||||
season,
|
||||
episode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getShowExploreOptions() {
|
||||
return request(
|
||||
'SHOW_GET_EXPLORE_OPTIONS',
|
||||
configureAxios().get('/shows/explore/options')
|
||||
)
|
||||
}
|
||||
|
||||
export function selectShow(imdbId) {
|
||||
return {
|
||||
type: 'SELECT_SHOW',
|
||||
imdbId
|
||||
}
|
||||
}
|
||||
|
||||
export function updateLastShowsFetchUrl(url) {
|
||||
return {
|
||||
type: 'UPDATE_LAST_SHOWS_FETCH_URL',
|
||||
payload: {
|
||||
url: url,
|
||||
},
|
||||
}
|
||||
}
|
22
src/public/js/actions/torrents.js
Normal file
22
src/public/js/actions/torrents.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
|
||||
export function addTorrent(url) {
|
||||
return request(
|
||||
'ADD_TORRENT',
|
||||
configureAxios().post('/torrents', {
|
||||
url: url,
|
||||
}),
|
||||
[
|
||||
addAlertOk("Torrent added"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchTorrents() {
|
||||
return request(
|
||||
'TORRENTS_FETCH',
|
||||
configureAxios().get('/torrents')
|
||||
)
|
||||
}
|
46
src/public/js/actions/users.js
Normal file
46
src/public/js/actions/users.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { configureAxios, request } from '../requests'
|
||||
|
||||
import { addAlertOk } from './alerts'
|
||||
|
||||
export function userLogout() {
|
||||
return {
|
||||
type: 'USER_LOGOUT',
|
||||
}
|
||||
}
|
||||
|
||||
export function loginUser(username, password) {
|
||||
return request(
|
||||
'USER_LOGIN',
|
||||
configureAxios().post(
|
||||
'/users/login',
|
||||
{
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function updateUser(config) {
|
||||
return request(
|
||||
'USER_UPDATE',
|
||||
configureAxios().post('/users/edit', config),
|
||||
[
|
||||
addAlertOk("User updated"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
export function userSignUp(config) {
|
||||
return request(
|
||||
'USER_SIGNUP',
|
||||
configureAxios().post('/users/signup', config)
|
||||
)
|
||||
}
|
||||
|
||||
export function getUserInfos() {
|
||||
return request(
|
||||
'GET_USER',
|
||||
configureAxios().get('/users/details')
|
||||
)
|
||||
}
|
@ -27,11 +27,8 @@ import { Provider, connect } from 'react-redux'
|
||||
import { Router } from 'react-router'
|
||||
import { routerActions } from 'react-router-redux'
|
||||
|
||||
// Root reducer
|
||||
import rootReducer from './reducers/index'
|
||||
|
||||
// Action creators
|
||||
import * as actionCreators from './actions/actionCreators'
|
||||
import { dismissAlert } from './actions/alerts'
|
||||
|
||||
// Store
|
||||
import store, { history } from './store'
|
||||
@ -39,244 +36,48 @@ import store, { history } from './store'
|
||||
// Components
|
||||
import NavBar from './components/navbar'
|
||||
import Alert from './components/alerts/alert'
|
||||
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'
|
||||
|
||||
function Main(props) {
|
||||
return (
|
||||
<div>
|
||||
<NavBar {...props}/>
|
||||
<Alert {...props}/>
|
||||
<div className="container-fluid">
|
||||
{React.cloneElement(props.children, props)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Routes
|
||||
import getRoutes from './routes'
|
||||
|
||||
function mapStateToProps(state) {
|
||||
let torrentCount = 0;
|
||||
if (state.torrentStore.has('torrents')) {
|
||||
torrentCount = state.torrentStore.has('torrents').size;
|
||||
}
|
||||
return {
|
||||
movieStore: state.movieStore,
|
||||
showStore: state.showStore,
|
||||
userStore: state.userStore,
|
||||
torrentStore: state.torrentStore,
|
||||
username: state.userStore.username,
|
||||
torrentCount: torrentCount,
|
||||
alerts: state.alerts,
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return bindActionCreators(actionCreators, dispatch);
|
||||
return bindActionCreators({ dismissAlert }, dispatch);
|
||||
}
|
||||
|
||||
const App = connect(mapStateToProps, mapDispatchToProps)(Main);
|
||||
export function startPollingTorrents() {
|
||||
return request(
|
||||
'TORRENTS_FETCH',
|
||||
configureAxios().get('/torrents')
|
||||
)
|
||||
}
|
||||
|
||||
// This function returns true if the user is logged in, false otherwise
|
||||
function isLoggedIn() {
|
||||
const state = store.getState();
|
||||
const isLogged = state.userStore.isLogged;
|
||||
let token = localStorage.getItem('token');
|
||||
|
||||
// Let's check if the user has a token, if he does let's assume he's logged
|
||||
// in. If that's not the case he will be logged out on the fisrt query
|
||||
if (token && token !== "") {
|
||||
store.dispatch({
|
||||
type: 'USER_SET_TOKEN',
|
||||
payload: {
|
||||
token: token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isLogged || (token && token !== "")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var pollingTorrentsId;
|
||||
const loginCheck = function(nextState, replace, next, f = null) {
|
||||
const loggedIn = isLoggedIn();
|
||||
if (!loggedIn) {
|
||||
replace('/users/login');
|
||||
} else {
|
||||
if (f) {
|
||||
f();
|
||||
}
|
||||
|
||||
// Poll torrents once logged
|
||||
if (!pollingTorrentsId) {
|
||||
// Fetch the torrents every 10s
|
||||
pollingTorrentsId = setInterval(function() {
|
||||
store.dispatch(actionCreators.fetchTorrents());
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const defaultRoute = '/movies/explore/yts/seeds';
|
||||
const routes = {
|
||||
path: '/',
|
||||
component: App,
|
||||
indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)},
|
||||
childRoutes: [
|
||||
{
|
||||
path: '/users/signup',
|
||||
component: UserSignUp
|
||||
},
|
||||
{
|
||||
path: '/users/login',
|
||||
component: UserLoginForm,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
if (isLoggedIn()) {
|
||||
// User is already logged in, redirect him to the default route
|
||||
replace(defaultRoute);
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/edit',
|
||||
component: UserEdit,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next);
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/logout',
|
||||
onEnter: function(nextState, replace, next) {
|
||||
// Stop polling
|
||||
if (pollingTorrentsId !== null) {
|
||||
clearInterval(pollingTorrentsId);
|
||||
pollingTorrentsId = null;
|
||||
}
|
||||
store.dispatch(actionCreators.userLogout());
|
||||
replace('/users/login');
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/search/:search',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchMovies(`/movies/search/${nextState.params.search}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/polochon',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchMovies('/movies/polochon'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/explore/:source/:category',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (Object.keys(state.movieStore.exploreOptions).length === 0) {
|
||||
store.dispatch(actionCreators.getMovieExploreOptions());
|
||||
}
|
||||
store.dispatch(actionCreators.fetchMovies(
|
||||
`/movies/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}`
|
||||
));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/wishlist',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchMovies('/wishlist/movies'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/search/:search',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.search}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/polochon',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchShows('/shows/polochon'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/wishlist',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchShows('/wishlist/shows'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/details/:imdbId',
|
||||
component: ShowDetails,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchShows(`/shows/search/${nextState.params.imdbId}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/explore/:source/:category',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (Object.keys(state.showStore.exploreOptions).length === 0) {
|
||||
store.dispatch(actionCreators.getShowExploreOptions());
|
||||
}
|
||||
store.dispatch(actionCreators.fetchShows(
|
||||
`/shows/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}`
|
||||
));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/torrents',
|
||||
component: TorrentList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(actionCreators.fetchTorrents());
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
function Main(props) {
|
||||
return (
|
||||
<div>
|
||||
<NavBar
|
||||
username={props.username}
|
||||
router={props.router}
|
||||
torrentCount={props.torrentCount}
|
||||
/>
|
||||
<Alert
|
||||
alerts={props.alerts}
|
||||
dismissAlert={props.dismissAlert}
|
||||
/>
|
||||
<div className="container-fluid">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export const App = connect(mapStateToProps, mapDispatchToProps)(Main);
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Router history={history} routes={routes} />
|
||||
<Router history={history} routes={getRoutes(App)} />
|
||||
</Provider>
|
||||
),document.getElementById('app'));
|
||||
|
@ -1,4 +1,9 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { addTorrent } from '../../actions/torrents'
|
||||
import { addMovieToWishlist, deleteMovieFromWishlist,
|
||||
getMovieDetails, selectMovie } from '../../actions/movies'
|
||||
|
||||
import DownloadButton from '../buttons/download'
|
||||
import TorrentsButton from './torrents'
|
||||
@ -6,6 +11,13 @@ import ActionsButton from './actions'
|
||||
import ListPosters from '../list/posters'
|
||||
import ListDetails from '../list/details'
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { movieStore: state.movieStore };
|
||||
}
|
||||
const mapDispatchToProps = (dipatch) =>
|
||||
bindActionCreators({ selectMovie, getMovieDetails, addTorrent,
|
||||
addMovieToWishlist, deleteMovieFromWishlist }, dipatch)
|
||||
|
||||
function MovieButtons(props) {
|
||||
const imdb_link = `http://www.imdb.com/title/${props.movie.imdb_id}`;
|
||||
const hasMovie = (props.movie.polochon_url !== "");
|
||||
@ -16,7 +28,6 @@ function MovieButtons(props) {
|
||||
movieId={props.movie.imdb_id}
|
||||
getDetails={props.getMovieDetails}
|
||||
deleteMovie={props.deleteMovie}
|
||||
isUserAdmin={props.isUserAdmin}
|
||||
hasMovie={hasMovie}
|
||||
wishlisted={props.movie.wishlisted}
|
||||
addToWishlist={props.addToWishlist}
|
||||
@ -44,7 +55,7 @@ function MovieButtons(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default class MovieList extends React.Component {
|
||||
class MovieList extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
@ -65,9 +76,7 @@ export default class MovieList extends React.Component {
|
||||
formModel="movieStore"
|
||||
filterControlModel="movieStore.filter"
|
||||
filterControlPlaceHolder="Filter movies..."
|
||||
fetchExploreOptions={this.props.getMovieExploreOptions}
|
||||
exploreOptions={this.props.movieStore.exploreOptions}
|
||||
explore={this.props.exploreMovies}
|
||||
selectedImdbId={selectedMovieId}
|
||||
filter={this.props.movieStore.filter}
|
||||
perPage={this.props.movieStore.perPage}
|
||||
@ -84,10 +93,8 @@ export default class MovieList extends React.Component {
|
||||
getMovieDetails={this.props.getMovieDetails}
|
||||
addTorrent={this.props.addTorrent}
|
||||
deleteMovie={this.props.deleteMovie}
|
||||
isUserAdmin={this.props.userStore.isAdmin}
|
||||
addToWishlist={this.props.addMovieToWishlist}
|
||||
deleteFromWishlist={this.props.deleteMovieFromWishlist}
|
||||
fetchMovies={this.props.fetchMovies}
|
||||
lastFetchUrl={this.props.movieStore.lastFetchUrl}
|
||||
/>
|
||||
</ListDetails>
|
||||
@ -96,3 +103,4 @@ export default class MovieList extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
|
||||
|
@ -2,41 +2,81 @@ import React from 'react'
|
||||
import { Nav, Navbar, NavItem, NavDropdown, MenuItem } from 'react-bootstrap'
|
||||
import { LinkContainer } from 'react-router-bootstrap'
|
||||
|
||||
export default function NavBar(props) {
|
||||
return (
|
||||
<div>
|
||||
<Navbar fluid fixedTop collapseOnSelect>
|
||||
<Navbar.Header>
|
||||
<LinkContainer to="/">
|
||||
<Navbar.Brand><a href="#">Canapé</a></Navbar.Brand>
|
||||
</LinkContainer>
|
||||
<Navbar.Toggle />
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
<MoviesDropdown username={props.userStore.username} />
|
||||
<ShowsDropdown username={props.userStore.username} />
|
||||
<WishlistDropdown username={props.userStore.username} />
|
||||
<Torrents
|
||||
username={props.userStore.username}
|
||||
torrentsCount={props.torrentStore.torrents.length}
|
||||
/>
|
||||
<UserDropdown username={props.userStore.username} />
|
||||
<Search
|
||||
placeholder="Search movies"
|
||||
router={props.router}
|
||||
path='/movies/search'
|
||||
pathMatch='movies'
|
||||
/>
|
||||
<Search
|
||||
placeholder="Search shows"
|
||||
router={props.router}
|
||||
path='/shows/search'
|
||||
pathMatch='shows'
|
||||
/>
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
export default class NavBar extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
userLoggedIn: (props.username !== ""),
|
||||
displayMoviesSearch: this.shouldDisplayMoviesSearch(props.router),
|
||||
displayShowsSearch: this.shouldDisplayShowsSearch(props.router),
|
||||
};
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
// Update the state based on the next props
|
||||
const shouldDisplayMoviesSearch = this.shouldDisplayMoviesSearch(nextProps.router);
|
||||
const shouldDisplayShowsSearch = this.shouldDisplayShowsSearch(nextProps.router);
|
||||
if ((this.state.displayMoviesSearch !== shouldDisplayMoviesSearch)
|
||||
|| (this.state.displayShowsSearch !== shouldDisplayShowsSearch)) {
|
||||
this.setState({
|
||||
userLoggedIn: (nextProps.username !== ""),
|
||||
displayMoviesSearch: shouldDisplayMoviesSearch,
|
||||
displayShowsSearch: shouldDisplayShowsSearch,
|
||||
});
|
||||
}
|
||||
}
|
||||
shouldDisplayMoviesSearch(router) {
|
||||
return this.matchPath(router, 'movies');
|
||||
}
|
||||
shouldDisplayShowsSearch(router) {
|
||||
return this.matchPath(router, 'shows');
|
||||
}
|
||||
matchPath(router, keyword) {
|
||||
const location = router.getCurrentLocation().pathname;
|
||||
return (location.indexOf(keyword) !== -1)
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Navbar fluid fixedTop collapseOnSelect>
|
||||
<Navbar.Header>
|
||||
<LinkContainer to="/">
|
||||
<Navbar.Brand><a href="#">Canapé</a></Navbar.Brand>
|
||||
</LinkContainer>
|
||||
<Navbar.Toggle />
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
{this.state.userLoggedIn &&
|
||||
<MoviesDropdown />
|
||||
}
|
||||
{this.state.userLoggedIn &&
|
||||
<ShowsDropdown />
|
||||
}
|
||||
{this.state.userLoggedIn &&
|
||||
<WishlistDropdown />
|
||||
}
|
||||
{this.state.userLoggedIn &&
|
||||
<Torrents torrentsCount={this.props.torrentCount} />
|
||||
}
|
||||
<UserDropdown username={this.props.username} />
|
||||
{(this.state.displayMoviesSearch && this.state.userLoggedIn) &&
|
||||
<Search
|
||||
placeholder="Search movies"
|
||||
router={this.props.router}
|
||||
path='/movies/search'
|
||||
/>
|
||||
}
|
||||
{(this.state.displayShowsSearch && this.state.userLoggedIn) &&
|
||||
<Search
|
||||
placeholder="Search shows"
|
||||
router={this.props.router}
|
||||
path='/shows/search'
|
||||
/>
|
||||
}
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Search extends React.Component {
|
||||
@ -48,15 +88,7 @@ class Search extends React.Component {
|
||||
ev.preventDefault();
|
||||
this.props.router.push(`${this.props.path}/${encodeURI(this.input.value)}`);
|
||||
}
|
||||
isActive() {
|
||||
const location = this.props.router.getCurrentLocation().pathname;
|
||||
return (location.indexOf(this.props.pathMatch) !== -1)
|
||||
}
|
||||
render() {
|
||||
if (!this.isActive()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return(
|
||||
<div className="navbar-form navbar-right">
|
||||
<form className="input-group" onSubmit={(ev) => this.handleSearch(ev)}>
|
||||
@ -72,9 +104,6 @@ class Search extends React.Component {
|
||||
}
|
||||
|
||||
function MoviesDropdown(props) {
|
||||
if (props.username === "") {
|
||||
return null;
|
||||
}
|
||||
return(
|
||||
<Nav>
|
||||
<NavDropdown title="Movies" id="navbar-movies-dropdown">
|
||||
@ -90,9 +119,6 @@ function MoviesDropdown(props) {
|
||||
}
|
||||
|
||||
function ShowsDropdown(props) {
|
||||
if (props.username === "") {
|
||||
return null;
|
||||
}
|
||||
return(
|
||||
<Nav>
|
||||
<NavDropdown title="Shows" id="navbar-shows-dropdown">
|
||||
@ -136,9 +162,6 @@ function UserDropdown(props) {
|
||||
}
|
||||
|
||||
function WishlistDropdown(props) {
|
||||
if (props.username === "") {
|
||||
return null;
|
||||
}
|
||||
return(
|
||||
<Nav>
|
||||
<NavDropdown title="Wishlist" id="navbar-wishlit-dropdown">
|
||||
@ -154,9 +177,6 @@ function WishlistDropdown(props) {
|
||||
}
|
||||
|
||||
function Torrents(props) {
|
||||
if (props.username === "") {
|
||||
return null;
|
||||
}
|
||||
return(
|
||||
<Nav>
|
||||
<LinkContainer to="/torrents">
|
||||
|
@ -1,28 +1,40 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { addTorrent } from '../../actions/torrents'
|
||||
import { addShowToWishlist, deleteFromWishlist, getEpisodeDetails,
|
||||
updateEpisodeDetailsStore, updateShowDetails } from '../../actions/shows'
|
||||
|
||||
import Loader from '../loader/loader'
|
||||
import DownloadButton from '../buttons/download'
|
||||
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||
|
||||
export default class ShowDetails extends React.Component {
|
||||
componentWillMount() {
|
||||
this.props.fetchShowDetails(this.props.params.imdbId);
|
||||
}
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
loading: state.showStore.loading,
|
||||
show: state.showStore.show,
|
||||
};
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({addTorrent, addShowToWishlist, deleteFromWishlist,
|
||||
updateShowDetails, updateEpisodeDetailsStore, getEpisodeDetails }, dispatch)
|
||||
|
||||
class ShowDetails extends React.Component {
|
||||
render() {
|
||||
// Loading
|
||||
if (this.props.showStore.loading) {
|
||||
if (this.props.loading) {
|
||||
return (<Loader />);
|
||||
}
|
||||
return (
|
||||
<div className="row" id="container">
|
||||
<Header
|
||||
data={this.props.showStore.show}
|
||||
data={this.props.show}
|
||||
addToWishlist={this.props.addShowToWishlist}
|
||||
deleteFromWishlist={this.props.deleteShowFromWishlist}
|
||||
/>
|
||||
<SeasonsList
|
||||
data={this.props.showStore.show}
|
||||
data={this.props.show}
|
||||
addTorrent={this.props.addTorrent}
|
||||
addToWishlist={this.props.addShowToWishlist}
|
||||
getEpisodeDetails={this.props.getEpisodeDetails}
|
||||
@ -32,6 +44,7 @@ export default class ShowDetails extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowDetails);
|
||||
|
||||
function Header(props){
|
||||
return (
|
||||
|
@ -1,16 +1,21 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { selectShow, addShowToWishlist,
|
||||
deleteFromWishlist, getShowDetails } from '../../actions/shows'
|
||||
|
||||
import ListDetails from '../list/details'
|
||||
import ListPosters from '../list/posters'
|
||||
import ShowButtons from './listButtons'
|
||||
|
||||
export default class ShowList extends React.Component {
|
||||
componentWillMount() {
|
||||
if (this.props.showsUrl) {
|
||||
this.props.fetchShows(this.props.showsUrl);
|
||||
return
|
||||
}
|
||||
}
|
||||
function mapStateToProps(state) {
|
||||
return { showStore: state.showStore };
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ selectShow, addShowToWishlist,
|
||||
deleteFromWishlist, getShowDetails }, dispatch)
|
||||
|
||||
class ShowList extends React.Component {
|
||||
render() {
|
||||
const shows = this.props.showStore.shows;
|
||||
const selectedShowId = this.props.showStore.selectedImdbId;
|
||||
@ -28,9 +33,7 @@ export default class ShowList extends React.Component {
|
||||
formModel="showStore"
|
||||
filterControlModel="showStore.filter"
|
||||
filterControlPlaceHolder="Filter shows..."
|
||||
fetchExploreOptions={this.props.getShowExploreOptions}
|
||||
exploreOptions={this.props.showStore.exploreOptions}
|
||||
explore={this.props.exploreShows}
|
||||
selectedImdbId={selectedShowId}
|
||||
filter={this.props.showStore.filter}
|
||||
perPage={this.props.showStore.perPage}
|
||||
@ -54,3 +57,4 @@ export default class ShowList extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowList);
|
||||
|
@ -1,15 +1,27 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { addTorrent } from '../../actions/torrents'
|
||||
|
||||
export default function TorrentList(props){
|
||||
return (
|
||||
<div>
|
||||
<AddTorrent func={props.addTorrent} />
|
||||
<List torrents={props.torrentStore.torrents} />
|
||||
</div>
|
||||
);
|
||||
function mapStateToProps(state) {
|
||||
return { torrents: state.torrentStore.get('torrents') };
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ addTorrent }, dispatch)
|
||||
|
||||
class AddTorrent extends React.Component {
|
||||
class TorrentList extends React.PureComponent {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<AddTorrent func={this.props.addTorrent} />
|
||||
<List torrents={this.props.torrents} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentList);
|
||||
|
||||
class AddTorrent extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { url: '' };
|
||||
@ -53,72 +65,75 @@ class AddTorrent extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
function List(props){
|
||||
if (props.torrents.length === 0) {
|
||||
class List extends React.PureComponent {
|
||||
render() {
|
||||
if (this.props.torrents.size === 0) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12">
|
||||
<h3>Torrents</h3>
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">No torrents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12">
|
||||
<h3>Torrents</h3>
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">No torrents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-md-12">
|
||||
<h3>Torrents</h3>
|
||||
{props.torrents.map(function(el, index) {
|
||||
{this.props.torrents.map(function(el, index) {
|
||||
return (
|
||||
<Torrent key={index} data={el} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Torrent extends React.PureComponent {
|
||||
render() {
|
||||
const done = this.props.data.get('is_finished');
|
||||
var progressStyle = 'progress-bar progress-bar-warning';
|
||||
if (done) {
|
||||
progressStyle = 'progress-bar progress-bar-success';
|
||||
}
|
||||
var percentDone = this.props.data.get('percent_done');
|
||||
const started = (percentDone !== 0);
|
||||
if (started) {
|
||||
percentDone = Number(percentDone).toFixed(1) + '%';
|
||||
}
|
||||
|
||||
function Torrent(props){
|
||||
const done = props.data.is_finished;
|
||||
var progressStyle = 'progress-bar progress-bar-warning';
|
||||
if (done) {
|
||||
progressStyle = 'progress-bar progress-bar-success';
|
||||
}
|
||||
var percentDone = props.data.percent_done;
|
||||
const started = (percentDone !== 0);
|
||||
if (started) {
|
||||
percentDone = Number(percentDone).toFixed(1) + '%';
|
||||
}
|
||||
|
||||
var downloadedSize = prettySize(props.data.downloaded_size);
|
||||
var totalSize = prettySize(props.data.total_size);
|
||||
var downloadRate = prettySize(props.data.download_rate) + "/s";
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">{props.data.name}</div>
|
||||
<div className="panel-body">
|
||||
{started &&
|
||||
<div className="progress progress-striped">
|
||||
<div
|
||||
className={progressStyle}
|
||||
style={{width: percentDone}}>
|
||||
var downloadedSize = prettySize(this.props.data.get('downloaded_size'));
|
||||
var totalSize = prettySize(this.props.data.get('total_size'));
|
||||
var downloadRate = prettySize(this.props.data.get('download_rate')) + "/s";
|
||||
return (
|
||||
<div className="panel panel-default">
|
||||
<div className="panel-heading">{this.props.data.get('name')}</div>
|
||||
<div className="panel-body">
|
||||
{started &&
|
||||
<div className="progress progress-striped">
|
||||
<div
|
||||
className={progressStyle}
|
||||
style={{width: percentDone}}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{!started &&
|
||||
<p>Download not yet started</p>
|
||||
}
|
||||
{started &&
|
||||
<div>
|
||||
<p>{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}</p>
|
||||
}
|
||||
{!started &&
|
||||
<p>Download not yet started</p>
|
||||
}
|
||||
{started &&
|
||||
<div>
|
||||
<p>{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function prettySize(fileSizeInBytes) {
|
||||
|
@ -1,18 +1,25 @@
|
||||
import React from 'react'
|
||||
import { Control, Form } from 'react-redux-form';
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
export default class UserEdit extends React.Component {
|
||||
componentWillMount() {
|
||||
this.props.getUserInfos();
|
||||
}
|
||||
import { Control, Form } from 'react-redux-form';
|
||||
import { updateUser } from '../../actions/users'
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { user: state.userStore };
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ updateUser }, dispatch)
|
||||
|
||||
class UserEdit extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
handleSubmit() {
|
||||
this.props.updateUser({
|
||||
'polochon_url': this.props.userStore.polochonUrl,
|
||||
'polochon_token': this.props.userStore.polochonToken,
|
||||
'polochon_url': this.props.user.polochonUrl,
|
||||
'polochon_token': this.props.user.polochonToken,
|
||||
'password': this.refs.newPassword.value,
|
||||
'password_confirm': this.refs.newPasswordConfirm.value,
|
||||
});
|
||||
@ -58,3 +65,4 @@ export default class UserEdit extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserEdit);
|
||||
|
@ -1,12 +1,22 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
export default class UserLoginForm extends React.Component {
|
||||
import { loginUser } from '../../actions/users'
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return { user: state.userStore };
|
||||
}
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ loginUser }, dispatch)
|
||||
|
||||
class UserLoginForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!nextProps.userStore.isLogged) {
|
||||
if (!nextProps.user.isLogged) {
|
||||
return
|
||||
}
|
||||
if (!nextProps.location.query.redirect) {
|
||||
@ -19,7 +29,7 @@ export default class UserLoginForm extends React.Component {
|
||||
}
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (this.props.userStore.userLoading) {
|
||||
if (this.props.user.userLoading) {
|
||||
return;
|
||||
}
|
||||
const username = this.refs.username.value;
|
||||
@ -47,12 +57,12 @@ export default class UserLoginForm extends React.Component {
|
||||
<p></p>
|
||||
</div>
|
||||
<div>
|
||||
{this.props.userStore.userLoading &&
|
||||
{this.props.user.userLoading &&
|
||||
<button className="btn btn-primary pull-right">
|
||||
<i className="fa fa-spinner fa-spin"></i>
|
||||
</button>
|
||||
}
|
||||
{this.props.userStore.userLoading ||
|
||||
{this.props.user.userLoading ||
|
||||
<input className="btn btn-primary pull-right" type="submit" value="Log in"/>
|
||||
}
|
||||
<br/>
|
||||
@ -64,3 +74,4 @@ export default class UserLoginForm extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm);
|
||||
|
@ -1,6 +1,13 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { bindActionCreators } from 'redux'
|
||||
|
||||
export default class UserSignUp extends React.Component {
|
||||
import { userSignUp } from '../../actions/users'
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({ userSignUp }, dispatch)
|
||||
|
||||
class UserSignUp extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
@ -46,3 +53,4 @@ export default class UserSignUp extends React.Component {
|
||||
);
|
||||
}
|
||||
}
|
||||
export default connect(null, mapDispatchToProps)(UserSignUp);
|
||||
|
@ -1,19 +1,19 @@
|
||||
const defaultState = {
|
||||
fetching: false,
|
||||
torrents: [],
|
||||
};
|
||||
import { Map, List, fromJS } from 'immutable'
|
||||
|
||||
export default function showStore(state = defaultState, action) {
|
||||
const defaultState = Map({
|
||||
'fetching': false,
|
||||
'torrents': List(),
|
||||
});
|
||||
|
||||
export default function torrentStore(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case 'TORRENTS_FETCH_PENDING':
|
||||
return Object.assign({}, state, {
|
||||
fetching: true,
|
||||
})
|
||||
return state.set('fetching', false);
|
||||
case 'TORRENTS_FETCH_FULFILLED':
|
||||
return Object.assign({}, state, {
|
||||
return state.merge(fromJS({
|
||||
fetching: false,
|
||||
torrents: action.payload.data,
|
||||
})
|
||||
}));
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
221
src/public/js/routes.js
Normal file
221
src/public/js/routes.js
Normal file
@ -0,0 +1,221 @@
|
||||
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 { fetchTorrents } from './actions/torrents'
|
||||
import { userLogout, getUserInfos } from './actions/users'
|
||||
import { fetchMovies, getMovieExploreOptions } from './actions/movies'
|
||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from './actions/shows'
|
||||
|
||||
import store from './store'
|
||||
|
||||
function startPollingTorrents() {
|
||||
return request(
|
||||
'TORRENTS_FETCH',
|
||||
configureAxios().get('/torrents')
|
||||
)
|
||||
}
|
||||
|
||||
// This function returns true if the user is logged in, false otherwise
|
||||
function isLoggedIn() {
|
||||
const state = store.getState();
|
||||
const isLogged = state.userStore.isLogged;
|
||||
let token = localStorage.getItem('token');
|
||||
|
||||
// Let's check if the user has a token, if he does let's assume he's logged
|
||||
// in. If that's not the case he will be logged out on the fisrt query
|
||||
if (token && token !== "") {
|
||||
store.dispatch({
|
||||
type: 'USER_SET_TOKEN',
|
||||
payload: {
|
||||
token: token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isLogged || (token && token !== "")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var pollingTorrentsId;
|
||||
const loginCheck = function(nextState, replace, next, f = null) {
|
||||
const loggedIn = isLoggedIn();
|
||||
if (!loggedIn) {
|
||||
replace('/users/login');
|
||||
} else {
|
||||
if (f) {
|
||||
f();
|
||||
}
|
||||
|
||||
// Poll torrents once logged
|
||||
if (!pollingTorrentsId) {
|
||||
// Fetch the torrents every 10s
|
||||
pollingTorrentsId = setInterval(function() {
|
||||
store.dispatch(fetchTorrents());
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
const defaultRoute = '/movies/explore/yts/seeds';
|
||||
export default function getRoutes(App) {
|
||||
return {
|
||||
path: '/',
|
||||
component: App,
|
||||
indexRoute: {onEnter: ({params}, replace) => replace(defaultRoute)},
|
||||
childRoutes: [
|
||||
{
|
||||
path: '/users/signup',
|
||||
component: UserSignUp
|
||||
},
|
||||
{
|
||||
path: '/users/login',
|
||||
component: UserLoginForm,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
if (isLoggedIn()) {
|
||||
// User is already logged in, redirect him to the default route
|
||||
replace(defaultRoute);
|
||||
}
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/edit',
|
||||
component: UserEdit,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(getUserInfos());
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/users/logout',
|
||||
onEnter: function(nextState, replace, next) {
|
||||
// Stop polling
|
||||
if (pollingTorrentsId !== null) {
|
||||
clearInterval(pollingTorrentsId);
|
||||
pollingTorrentsId = null;
|
||||
}
|
||||
store.dispatch(userLogout());
|
||||
replace('/users/login');
|
||||
next();
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/search/:search',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies(`/movies/search/${nextState.params.search}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/polochon',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies('/movies/polochon'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/explore/:source/:category',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (Object.keys(state.movieStore.exploreOptions).length === 0) {
|
||||
store.dispatch(getMovieExploreOptions());
|
||||
}
|
||||
store.dispatch(fetchMovies(
|
||||
`/movies/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}`
|
||||
));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/movies/wishlist',
|
||||
component: MovieList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchMovies('/wishlist/movies'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/search/:search',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows(`/shows/search/${nextState.params.search}`));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/polochon',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows('/shows/polochon'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/wishlist',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShows('/wishlist/shows'));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/details/:imdbId',
|
||||
component: ShowDetails,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchShowDetails(nextState.params.imdbId));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/shows/explore/:source/:category',
|
||||
component: ShowList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
var state = store.getState();
|
||||
// Fetch the explore options
|
||||
if (Object.keys(state.showStore.exploreOptions).length === 0) {
|
||||
store.dispatch(getShowExploreOptions());
|
||||
}
|
||||
store.dispatch(fetchShows(
|
||||
`/shows/explore?source=${encodeURI(nextState.params.source)}&category=${encodeURI(nextState.params.category)}`
|
||||
));
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/torrents',
|
||||
component: TorrentList,
|
||||
onEnter: function(nextState, replace, next) {
|
||||
loginCheck(nextState, replace, next, function() {
|
||||
store.dispatch(fetchTorrents());
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
119
yarn.lock
119
yarn.lock
@ -1,11 +1,5 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
Base64@~0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028"
|
||||
|
||||
abbrev@1:
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
|
||||
@ -741,6 +735,10 @@ base64-js@^1.0.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
|
||||
|
||||
Base64@~0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.0.tgz#3ca76b85241c7170bf7d9703e7b9aa74630040d4"
|
||||
@ -1541,12 +1539,6 @@ glob-watcher@^0.0.6:
|
||||
dependencies:
|
||||
gaze "^0.5.1"
|
||||
|
||||
glob2base@^0.0.12:
|
||||
version "0.0.12"
|
||||
resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56"
|
||||
dependencies:
|
||||
find-index "^0.1.1"
|
||||
|
||||
glob@^4.3.1:
|
||||
version "4.5.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
|
||||
@ -1575,6 +1567,12 @@ glob@~3.1.21:
|
||||
inherits "1"
|
||||
minimatch "~0.2.11"
|
||||
|
||||
glob2base@^0.0.12:
|
||||
version "0.0.12"
|
||||
resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56"
|
||||
dependencies:
|
||||
find-index "^0.1.1"
|
||||
|
||||
global-modules@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d"
|
||||
@ -1825,7 +1823,7 @@ image-size@~0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.0.tgz#be7aed1c37b5ac3d9ba1d66a24b4c47ff8397651"
|
||||
|
||||
immutable@^3.7.6:
|
||||
immutable, immutable@^3.7.6:
|
||||
version "3.8.1"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
|
||||
|
||||
@ -1850,14 +1848,14 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1, inherits@2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
inherits@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b"
|
||||
|
||||
inherits@2, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
inherits@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
|
||||
@ -2032,14 +2030,14 @@ is-windows@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-0.2.0.tgz#de1aa6d63ea29dd248737b69f1ff8b8002d2108c"
|
||||
|
||||
isarray@^1.0.0, isarray@~1.0.0, isarray@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
||||
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
isexe@^1.1.1:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
|
||||
@ -2182,7 +2180,7 @@ load-json-file@^1.0.0:
|
||||
pinkie-promise "^2.0.0"
|
||||
strip-bom "^2.0.0"
|
||||
|
||||
loader-utils@0.2.x, loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@~0.2.5:
|
||||
loader-utils@^0.2.11, loader-utils@^0.2.5, loader-utils@^0.2.7, loader-utils@~0.2.2, loader-utils@~0.2.5, loader-utils@0.2.x:
|
||||
version "0.2.16"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d"
|
||||
dependencies:
|
||||
@ -2419,14 +2417,14 @@ mime-types@^2.1.12, mime-types@~2.1.7:
|
||||
dependencies:
|
||||
mime-db "~1.24.0"
|
||||
|
||||
mime@1.2.x:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
|
||||
|
||||
mime@^1.2.11:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
|
||||
|
||||
mime@1.2.x:
|
||||
version "1.2.11"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10"
|
||||
|
||||
minimatch@^2.0.1:
|
||||
version "2.0.10"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7"
|
||||
@ -2446,15 +2444,15 @@ minimatch@~0.2.11:
|
||||
lru-cache "2"
|
||||
sigmund "~1.0.0"
|
||||
|
||||
minimist@0.0.8, minimist@~0.0.1:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
minimist@~0.0.1, minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
mkdirp@^0.5.0, mkdirp@^0.5.1, "mkdirp@>=0.5 0", mkdirp@~0.5.0, mkdirp@~0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
@ -3008,14 +3006,14 @@ prr@~0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
|
||||
|
||||
punycode@1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||
|
||||
punycode@^1.2.4, punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
|
||||
punycode@1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||
|
||||
q@^1.1.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e"
|
||||
@ -3160,15 +3158,6 @@ read-pkg@^1.0.0:
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
"readable-stream@>=1.0.33-1 <1.1.0-0":
|
||||
version "1.0.34"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readable-stream@^1.0.27-1, readable-stream@^1.1.13, readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
@ -3201,6 +3190,15 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@~2.0.0:
|
||||
string_decoder "~0.10.x"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
"readable-stream@>=1.0.33-1 <1.1.0-0":
|
||||
version "1.0.34"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
readdirp@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
|
||||
@ -3361,7 +3359,7 @@ right-align@^0.1.1:
|
||||
dependencies:
|
||||
align-text "^0.1.1"
|
||||
|
||||
rimraf@2, rimraf@^2.2.8, rimraf@~2.5.1, rimraf@~2.5.4:
|
||||
rimraf@^2.2.8, rimraf@~2.5.1, rimraf@~2.5.4, rimraf@2:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04"
|
||||
dependencies:
|
||||
@ -3375,7 +3373,7 @@ sax@~1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^4.1.0:
|
||||
semver@^4.1.0, "semver@2 || 3 || 4 || 5":
|
||||
version "4.3.6"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da"
|
||||
|
||||
@ -3495,6 +3493,10 @@ strict-uri-encode@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
|
||||
|
||||
string_decoder@~0.10.25, string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
||||
string-width@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||
@ -3503,10 +3505,6 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
string_decoder@~0.10.25, string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
||||
stringstream@~0.0.4:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
|
||||
@ -3597,6 +3595,10 @@ tar@~2.2.1:
|
||||
fstream "^1.0.2"
|
||||
inherits "2"
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
through2@^0.6.1:
|
||||
version "0.6.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48"
|
||||
@ -3611,10 +3613,6 @@ through2@^2.0.0:
|
||||
readable-stream "~2.0.0"
|
||||
xtend "~4.0.0"
|
||||
|
||||
through@^2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
tildify@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a"
|
||||
@ -3736,7 +3734,7 @@ util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
util@0.10.3, util@~0.10.3:
|
||||
util@~0.10.3, util@0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
|
||||
dependencies:
|
||||
@ -3894,19 +3892,19 @@ window-size@0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
|
||||
|
||||
wordwrap@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
|
||||
wordwrap@~0.0.2:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
|
||||
|
||||
wordwrap@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
||||
"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0:
|
||||
xtend@^4.0.0, "xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
@ -3918,3 +3916,4 @@ yargs@~3.10.0:
|
||||
cliui "^2.1.0"
|
||||
decamelize "^1.0.0"
|
||||
window-size "0.1.0"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user