diff --git a/package.json b/package.json index 1ab5b58..cd0933a 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dependencies": { "babel-plugin-transform-react-jsx": "^6.24.1", "babel-polyfill": "^6.26.0", + "bencode": "^1.0.0", "bootstrap": "^4.0.0-alpha.6", "express": "^4.15.3", "file-loader": "^0.11.2", diff --git a/scss/main.scss b/scss/main.scss index f7d2557..22f7f7f 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -49,6 +49,12 @@ background-position: left bottom; } +input[type="text"], input[type="number"] { + padding: 0.05rem 0.25rem; + border-radius: 0; + border-color: #888; +} + .navbar { input[type="text"] { margin-top: 7px; @@ -103,3 +109,28 @@ margin-bottom: 0; } } + +.card { + .card { + border-left: none; + border-right: none; + } + + .form-group { + margin-bottom: 0.25rem; + } +} + +select.form-control { + height: 1.5rem !important; + padding: 0; + font-size: 0.9rem; +} + +.form-check-input:only-child { + position: absolute; +} + +.form-check-inline { + margin-bottom: 0; +} diff --git a/src/bitrate.js b/src/bitrate.js index 33e750d..b30d94d 100644 --- a/src/bitrate.js +++ b/src/bitrate.js @@ -1,10 +1,22 @@ +export const Rates = { + "b/s": Math.pow(1024, 0), + "KiB/s": Math.pow(1024, 1), + "MiB/s": Math.pow(1024, 2), + "GiB/s": Math.pow(1024, 3), + "TiB/s": Math.pow(1024, 4), +}; + +export function convertToBitrate(value, unit) { + return value * Rates[unit]; +} + export function formatBitrate(bitrate) { - if (bitrate > 1000000000) { - return `${(bitrate / 1000000000).toFixed(2)} Gb/s`; - } else if (bitrate > 1000000) { - return `${(bitrate / 1000000).toFixed(2)} Mb/s`; - } else if (bitrate > 1000) { - return `${(bitrate / 1000).toFixed(2)} Kb/s`; + if (bitrate > Rates["GiB/s"]) { + return `${(bitrate / Rates["GiB/s"]).toFixed(2)} GiB/s`; + } else if (bitrate > Rates["MiB/s"]) { + return `${(bitrate / Rates["MiB/s"]).toFixed(2)} MiB/s`; + } else if (bitrate > Rates["KiB/s"]) { + return `${(bitrate / Rates["KiB/s"]).toFixed(2)} KiB/s`; } else { return `${bitrate} b/s`; } diff --git a/src/ui/add_torrent.js b/src/ui/add_torrent.js index 7dbb198..75b386d 100644 --- a/src/ui/add_torrent.js +++ b/src/ui/add_torrent.js @@ -2,10 +2,126 @@ import React, { Component } from 'react'; import { findDOMNode } from 'react-dom'; import { connect } from 'react-redux'; import { push } from 'react-router-redux'; -import { Progress, Collapse, Card, CardBlock } from 'reactstrap'; -import FontAwesome from 'react-fontawesome'; +import { + Progress, + Card, + CardBlock, + CardTitle, + CardText, + FormGroup, + Label, + Input +} from 'reactstrap'; +import ToggleContainer from './toggle_container'; import fetch from 'isomorphic-fetch'; +import bencode from 'bencode'; import ws_send from '../socket'; +import { convertToBitrate } from '../bitrate'; + +class Throttle extends Component { + constructor() { + super(); + this.state = { + strategy: "global", + unit: "MiB/s", + limit: 1 + }; + this.setStrategy = this.setStrategy.bind(this); + } + + invokeChange() { + const { onChange } = this.props; + const { strategy, limit, unit } = this.state; + this.onChange && this.onChange(strategy, convertToBitrate(limit, unit)); + } + + setStrategy(strategy) { + this.setState({ strategy }); + this.invokeChange(); + } + + setLimit(limit) { + if (limit <= 0) { + this.setState({ limit: this.state.limit }); + return; + } + this.setState({ limit }); + this.invokeChange(); + } + + setUnit(unit) { + this.setState({ unit }); + this.invokeChange(); + } + + render() { + const { legend, prop } = this.props; + return ( +
+ + {legend} + + + + + + + + + + + {this.state.strategy === "custom" && +
+ + this.setLimit(parseFloat(e.target.value))} + /> + + + this.setUnit(e.target.value)} + > + + + + + + +
+ } +
+ ); + } +} class AddTorrent extends Component { constructor() { @@ -13,7 +129,10 @@ class AddTorrent extends Component { this.state = { loading: false, customize: false, - file: null + file: null, + torrent: null, + files: [], + startImmediately: true }; } @@ -27,7 +146,7 @@ class AddTorrent extends Component { headers: headers }); } catch (ex) { - // TODO: synapse borks up this response + // TODO: something more useful console.log(ex); } } @@ -36,32 +155,143 @@ class AddTorrent extends Component { this.setState({ loading: true }); const { file } = this.state; const { dispatch } = this.props; - const reader = new FileReader(); - reader.onload = e => { - ws_send("UPLOAD_TORRENT", { size: file.size }, async offer => { - switch (offer.type) { - case "TRANSFER_OFFER": - return await this.handleTransferOffer(offer, file); - case "RESOURCES_EXTANT": - const [id] = offer.ids; - dispatch(push(`/torrents/${id}`)); - break; - } + ws_send("UPLOAD_TORRENT", { size: file.size }, async offer => { + switch (offer.type) { + case "TRANSFER_OFFER": + return await this.handleTransferOffer(offer, file); + case "RESOURCES_EXTANT": + const [id] = offer.ids; + dispatch(push(`/torrents/${id}`)); + break; + } + }); + } + + processTorrent(torrent) { + const { info } = torrent; + if (!info.files) { + this.setState({ + files: [ + { + name: info.name, + length: info.length + } + ], + torrent }); - }; - reader.readAsArrayBuffer(file); + } else { + // TODO + } } handleFile(e) { const file = e.target.files[0]; + const reader = new FileReader(); + reader.onload = e => { + try { + const torrent = bencode.decode(reader.result, 'utf8'); + this.processTorrent.bind(this)(torrent); + } catch (ex) { + // TODO: something meaningful + console.log(ex); + } + }; + reader.readAsArrayBuffer(file); this.setState({ file }); } + renderOptions() { + return ( + + + + + + + + + + + + + + + + + + + + ); + } + + renderTorrent() { + const { torrent, file, files, loading } = this.state; + + const details = { + "comment": d => d, + "creation date": d => new Date(d * 1000).toDateString(), + "created by": d => d + }; + + return ( + + + {file.name} + +
+ {Object.keys(details).map(key => + torrent[key] && ( +
+
{key}
+
{details[key](torrent[key])}
+
+ ) + )} +
+
+
+ + {this.renderOptions.bind(this)()} + + + + + TODO + + + + + {loading ? + : null} +
+ ); + } + render() { - const { file, loading } = this.state; return (

Add torrent

+ {this.state.torrent && this.renderTorrent.bind(this)()}
- -

{file ? file.name : ""}

-
-
-

- -

- - - - TODO - - - -
-
-
-
+ {this.state.torrent ? +
+ className="btn btn-default" + onClick={() => findDOMNode(this).querySelector("input[type='file']").click()} + >Select a different torrent?
-
- {loading ? - : null} -
-
+ : + + }
); diff --git a/src/ui/toggle_container.js b/src/ui/toggle_container.js new file mode 100644 index 0000000..6e46af7 --- /dev/null +++ b/src/ui/toggle_container.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; +import FontAwesome from 'react-fontawesome'; +import { Collapse } from 'reactstrap'; + +export default class ToggleContainer extends Component { + constructor() { + super(); + this.state = { open: false }; + } + + render() { + return ( +
+ + + {this.props.children} + +
+ ); + } +}