diff --git a/src/public/js/actions/actionCreators.js b/src/public/js/actions/actionCreators.js index 962276e..b0eef51 100644 --- a/src/public/js/actions/actionCreators.js +++ b/src/public/js/actions/actionCreators.js @@ -284,3 +284,10 @@ export function addTorrent(url) { ], ) } + +export function fetchTorrents() { + return request( + 'TORRENTS_FETCH', + configureAxios().get('/torrents') + ) +} diff --git a/src/public/js/app.js b/src/public/js/app.js index 3c2dac6..3324d12 100644 --- a/src/public/js/app.js +++ b/src/public/js/app.js @@ -43,6 +43,7 @@ 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 ( @@ -61,6 +62,7 @@ function mapStateToProps(state) { movieStore: state.movieStore, showStore: state.showStore, userStore: state.userStore, + torrentStore: state.torrentStore, alerts: state.alerts, } } @@ -70,7 +72,14 @@ function mapDispatchToProps(dispatch) { } const App = connect(mapStateToProps, mapDispatchToProps)(Main); +export function startPollingTorrents() { + return request( + 'TORRENTS_FETCH', + configureAxios().get('/torrents') + ) +} +var pollingTorrentsId; const loginCheck = function(nextState, replace, next, f) { const state = store.getState(); const isLogged = state.userStore.isLogged; @@ -91,6 +100,13 @@ const loginCheck = function(nextState, replace, next, f) { replace('/users/login'); } else { f(); + // Poll torrents once logged + if (!pollingTorrentsId) { + // Fetch the torrents every 10s + pollingTorrentsId = setInterval(function() { + store.dispatch(actionCreators.fetchTorrents()); + }, 10000); + } } next(); @@ -208,6 +224,15 @@ const routes = { }); }, }, + { + path: '/torrents', + component: TorrentList, + onEnter: function(nextState, replace, next) { + loginCheck(nextState, replace, next, function() { + store.dispatch(actionCreators.fetchTorrents()); + }); + }, + }, ], } diff --git a/src/public/js/components/navbar.js b/src/public/js/components/navbar.js index 5ed89bd..1c6e8bc 100644 --- a/src/public/js/components/navbar.js +++ b/src/public/js/components/navbar.js @@ -16,6 +16,10 @@ export default function NavBar(props) { + ); } + +function Torrents(props) { + if (props.username === "") { + return null; + } + return( + + ); +} diff --git a/src/public/js/components/torrents/list.js b/src/public/js/components/torrents/list.js new file mode 100644 index 0000000..ddec871 --- /dev/null +++ b/src/public/js/components/torrents/list.js @@ -0,0 +1,133 @@ +import React from 'react' + +export default function TorrentList(props){ + return ( +
+ + +
+ ); +} + +class AddTorrent extends React.Component { + constructor(props) { + super(props); + this.state = { url: '' }; + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + } + handleChange(event) { + this.setState({ url: event.target.value }); + } + handleSubmit() { + if (this.state.url === "") { + return; + } + this.setState({ url: '' }); + this.props.func(this.state.url); + } + render() { + return ( +
+
+
+ + + + +
+
+
+ ); + } +} + +function List(props){ + if (props.torrents.length === 0) { + return ( +
+
+

Torrents

+
+
No torrents
+
+
+
+ ); + } + return ( +
+
+

Torrents

+ {props.torrents.map(function(el, index) { + return ( + + ); + })} +
+
+ ); +} + + +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 ( +
+
{props.data.name}
+
+ {started && +
+
+
+
+ } + {!started && +

Download not yet started

+ } + {started && +
+

{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}

+
+ } +
+
+ ); +} + +function prettySize(fileSizeInBytes) { + var i = -1; + var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB']; + do { + fileSizeInBytes = fileSizeInBytes / 1024; + i++; + } while (fileSizeInBytes > 1024); + + return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i]; +}; diff --git a/src/public/js/reducers/index.js b/src/public/js/reducers/index.js index 9e8f6e6..2079637 100644 --- a/src/public/js/reducers/index.js +++ b/src/public/js/reducers/index.js @@ -5,6 +5,7 @@ import movieStore from './movies' import showStore from './shows' import userStore from './users' import alerts from './alerts' +import torrentStore from './torrents' // Use combine form form react-redux-form, it's a thin wrapper arround the // default combinedReducers provided with React. It allows the forms to be @@ -15,6 +16,7 @@ const rootReducer = combineForms({ showStore, userStore, alerts, + torrentStore, }) export default rootReducer; diff --git a/src/public/js/reducers/torrents.js b/src/public/js/reducers/torrents.js new file mode 100644 index 0000000..f085bbd --- /dev/null +++ b/src/public/js/reducers/torrents.js @@ -0,0 +1,20 @@ +const defaultState = { + fetching: false, + torrents: [], +}; + +export default function showStore(state = defaultState, action) { + switch (action.type) { + case 'TORRENTS_FETCH_PENDING': + return Object.assign({}, state, { + fetching: true, + }) + case 'TORRENTS_FETCH_FULFILLED': + return Object.assign({}, state, { + fetching: false, + torrents: action.payload.data, + }) + default: + return state + } +}