Flesh out torrent detail view
This commit is contained in:
parent
b6fd35c5f5
commit
5261b475c1
|
@ -88,3 +88,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-very-sm {
|
||||||
|
padding: 0 0.75rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
24
src/reducers/files.js
Normal file
24
src/reducers/files.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import {
|
||||||
|
UPDATE_RESOURCES,
|
||||||
|
RESOURCES_REMOVED
|
||||||
|
} from '../actions/resources';
|
||||||
|
|
||||||
|
export default function files(state = {}, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case UPDATE_RESOURCES:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...action.resources
|
||||||
|
.filter(r => r.type === "file")
|
||||||
|
.reduce((s, r) => ({
|
||||||
|
...s,
|
||||||
|
[r.id]: { ...state[r.id], ...r }
|
||||||
|
}), {})
|
||||||
|
};
|
||||||
|
case RESOURCES_REMOVED:
|
||||||
|
return Object.values(state)
|
||||||
|
.filter(r => action.ids.indexOf(r.id) === -1)
|
||||||
|
.reduce((s, r) => ({ ...s, [r.id]: r }), {});
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -3,11 +3,13 @@ import { routerReducer } from 'react-router-redux'
|
||||||
import subscribe from './subscribe';
|
import subscribe from './subscribe';
|
||||||
import filter_subscribe from './filter_subscribe';
|
import filter_subscribe from './filter_subscribe';
|
||||||
import torrents from './torrents';
|
import torrents from './torrents';
|
||||||
|
import files from './files';
|
||||||
|
|
||||||
const root = combineReducers({
|
const root = combineReducers({
|
||||||
subscribe,
|
subscribe,
|
||||||
filter_subscribe,
|
filter_subscribe,
|
||||||
torrents,
|
torrents,
|
||||||
|
files,
|
||||||
router: routerReducer
|
router: routerReducer
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,140 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { activeTorrents } from '../torrent_state';
|
import { activeTorrents } from '../torrent_state';
|
||||||
|
import FontAwesome from 'react-fontawesome';
|
||||||
import {
|
import {
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
ButtonDropdown,
|
ButtonDropdown,
|
||||||
DropdownToggle,
|
DropdownToggle,
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownItem
|
DropdownItem,
|
||||||
|
Collapse,
|
||||||
|
Card,
|
||||||
|
CardBlock,
|
||||||
|
Progress
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
|
import ws_send from '../socket';
|
||||||
|
|
||||||
// TODO: use component lifecycle functions here to invoke
|
// TODO: use component lifecycle functions here to invoke
|
||||||
// torrent_state.updateSubscriptions
|
// torrent_state.updateSubscriptions
|
||||||
|
// TODO: fix navigating directly to torrent pages
|
||||||
|
|
||||||
function Torrent(props) {
|
function File({ file }) {
|
||||||
|
// TODO: show progress bar
|
||||||
|
// TODO: edit priority
|
||||||
return (
|
return (
|
||||||
<div>
|
<tr>
|
||||||
<h3>{props.torrent.name}</h3>
|
<td>{file.path}</td>
|
||||||
</div>
|
<td>{file.priority}</td>
|
||||||
|
<td>{file.availability}</td>
|
||||||
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to separate component
|
||||||
|
function CollapseToggle({ text, onToggle, open }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-default"
|
||||||
|
onClick={onToggle}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
<FontAwesome
|
||||||
|
name={`chevron-${open ? "up" : "down"}`}
|
||||||
|
style={{marginLeft: "0.25rem"}}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Torrent extends Component {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.state = {
|
||||||
|
infoShown: false,
|
||||||
|
filesShown: false,
|
||||||
|
trackersShown: false,
|
||||||
|
peersShown: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleTorrentState(torrent) {
|
||||||
|
if (torrent.status === "paused") {
|
||||||
|
ws_send("RESUME_TORRENT", { id: torrent.id });
|
||||||
|
} else {
|
||||||
|
ws_send("PAUSE_TORRENT", { id: torrent.id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { torrent, files } = this.props;
|
||||||
|
const status = s => s[0].toUpperCase() + s.slice(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h3>{torrent.name}</h3>
|
||||||
|
<Progress value={torrent.progress * 100} style={{marginBottom: "0.5rem"}}>
|
||||||
|
{(torrent.progress * 100).toFixed(0)}%
|
||||||
|
</Progress>
|
||||||
|
<p>
|
||||||
|
<strong>State:</strong>
|
||||||
|
<span style={{marginLeft: "1rem"}}>
|
||||||
|
{status(torrent.status)}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-very-sm btn-info"
|
||||||
|
style={{marginLeft: "1rem"}}
|
||||||
|
onClick={() => this.toggleTorrentState(torrent)}
|
||||||
|
>
|
||||||
|
{torrent.status === "paused" ? "Resume" : "Pause"}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<ButtonGroup>
|
||||||
|
<CollapseToggle
|
||||||
|
text="Info"
|
||||||
|
onToggle={() => this.setState({ infoShown: !this.state.infoShown })}
|
||||||
|
open={this.state.infoShown}
|
||||||
|
/>
|
||||||
|
<CollapseToggle
|
||||||
|
text="Files"
|
||||||
|
onToggle={() => this.setState({ filesShown: !this.state.filesShown })}
|
||||||
|
open={this.state.filesShown}
|
||||||
|
/>
|
||||||
|
<CollapseToggle
|
||||||
|
text="Trackers"
|
||||||
|
onToggle={() => this.setState({ trackersShown: !this.state.trackersShown })}
|
||||||
|
open={this.state.trackersShown}
|
||||||
|
/>
|
||||||
|
<CollapseToggle
|
||||||
|
text="Peers"
|
||||||
|
onToggle={() => this.setState({ peersShown: !this.state.peersShown })}
|
||||||
|
open={this.state.peersShown}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
<Collapse isOpen={this.state.infoShown}>
|
||||||
|
<dl>
|
||||||
|
<dt>Downloading to</dt>
|
||||||
|
<dd>{torrent.path}</dd>
|
||||||
|
<dt>Created</dt>
|
||||||
|
<dd>{torrent.created}</dd>
|
||||||
|
</dl>
|
||||||
|
</Collapse>
|
||||||
|
<Collapse isOpen={this.state.filesShown}>
|
||||||
|
<Card style={{marginBottom: "1rem"}}>
|
||||||
|
<CardBlock>
|
||||||
|
<table className="table table-striped" style={{marginBottom: "0"}}>
|
||||||
|
<tbody>
|
||||||
|
{files.map(file => <File file={file} />)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</CardBlock>
|
||||||
|
</Card>
|
||||||
|
</Collapse>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TorrentDetails extends Component {
|
class TorrentDetails extends Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -34,13 +148,13 @@ class TorrentDetails extends Component {
|
||||||
<div>
|
<div>
|
||||||
<h3>{active.length} torrents</h3>
|
<h3>{active.length} torrents</h3>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<Button color="info">Pause all</Button>{' '}
|
<button className="btn btn-default btn-sm">Pause all</button>
|
||||||
<Button color="success">Resume all</Button>{' '}
|
<button className="btn btn-default btn-sm">Resume all</button>
|
||||||
<ButtonDropdown
|
<ButtonDropdown
|
||||||
isOpen={this.state.removeDropdown}
|
isOpen={this.state.removeDropdown}
|
||||||
toggle={() => this.setState({ removeDropdown: !this.state.removeDropdown })}
|
toggle={() => this.setState({ removeDropdown: !this.state.removeDropdown })}
|
||||||
>
|
>
|
||||||
<DropdownToggle color="danger" caret>
|
<DropdownToggle color="default" caret>
|
||||||
Remove
|
Remove
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
@ -56,10 +170,17 @@ class TorrentDetails extends Component {
|
||||||
render() {
|
render() {
|
||||||
const active = activeTorrents();
|
const active = activeTorrents();
|
||||||
const { torrents } = this.props;
|
const { torrents } = this.props;
|
||||||
|
const { files } = this.props;
|
||||||
|
const _files = Object.values(files).reduce((s, f) => ({
|
||||||
|
...s, [f.torrent_id]: [...(s[f.torrent_id] || []), f]
|
||||||
|
}), {});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{active.length > 1 ? this.renderHeader(active) : null}
|
{active.length > 1 ? this.renderHeader(active) : null}
|
||||||
{active.map(id => <Torrent torrent={torrents[id]} />)}
|
{active.map(id => <Torrent
|
||||||
|
torrent={torrents[id]}
|
||||||
|
files={_files[id] || []}
|
||||||
|
/>)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,5 +188,6 @@ class TorrentDetails extends Component {
|
||||||
|
|
||||||
export default connect(state => ({
|
export default connect(state => ({
|
||||||
router: state.router,
|
router: state.router,
|
||||||
torrents: state.torrents
|
torrents: state.torrents,
|
||||||
|
files: state.files
|
||||||
}))(TorrentDetails);
|
}))(TorrentDetails);
|
||||||
|
|
|
@ -33,7 +33,7 @@ class TorrentTable extends Component {
|
||||||
{Object.values(torrents).map(t =>
|
{Object.values(torrents).map(t =>
|
||||||
<tr
|
<tr
|
||||||
key={t.id}
|
key={t.id}
|
||||||
className={`torrent ${
|
className={`torrent progress-row ${
|
||||||
t.status
|
t.status
|
||||||
} ${
|
} ${
|
||||||
active.indexOf(t.id) !== -1 ? "selected" : ""
|
active.indexOf(t.id) !== -1 ? "selected" : ""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user