Rearrange app directory structure

This commit is contained in:
John Furrow
2015-11-07 23:13:21 -08:00
parent 072c000236
commit eb1ea0806d
91 changed files with 488 additions and 2975 deletions

3
.gitignore vendored
View File

@@ -2,5 +2,4 @@
node_modules
npm-debug.log
bower_components
public/stylesheets
public/scripts
server/assets

View File

Before

Width:  |  Height:  |  Size: 186 B

After

Width:  |  Height:  |  Size: 186 B

View File

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 158 B

View File

Before

Width:  |  Height:  |  Size: 150 B

After

Width:  |  Height:  |  Size: 150 B

View File

Before

Width:  |  Height:  |  Size: 147 B

After

Width:  |  Height:  |  Size: 147 B

View File

@@ -21,7 +21,7 @@ body {
max-width: 240px;
}
.main {
.content {
display: flex;
flex: 5;
flex-direction: column;
@@ -43,6 +43,7 @@ body {
&__list {
flex: 5;
margin: 0 10px;
}
}

View File

@@ -63,8 +63,21 @@
&__group {
display: inline-block;
box-shadow: inset 1px 0 $action-bar--group--border;
padding: 0 15px;
&--has-divider {
position: relative;
&:before {
background: $action-bar--group--border;
content: '';
position: absolute;
height: 80%;
left: 0;
top: 10%;
width: 1px;
}
}
}
}
@@ -77,16 +90,7 @@
max-width: 225px;
.dropdown {
display: inline-block;
&__button {
cursor: pointer;
display: block;
padding: 15px 30px 11px 30px;
text-align: left;
width: auto;
word-wrap: none;
}
margin: 5px 0 0 15px;
&__label {
color: $dropdown--label;
@@ -114,7 +118,6 @@
}
&__content {
left: 30px;
min-width: 250px;
}
}

View File

@@ -0,0 +1,3 @@
.content {
background: $main-content--background;
}

View File

@@ -0,0 +1,93 @@
.dropdown {
display: inline-block;
position: relative;
z-index: 2;
&__button {
cursor: pointer;
display: block;
padding: 12px 15px 7px 15px;
text-align: left;
width: auto;
word-wrap: none;
}
&__content {
background: $dropdown--background;
border-radius: 3px;
box-shadow:
0 0 0 1px $dropdown--container--border,
0 0 35px $dropdown--container--shadow;
color: $dropdown--foreground;
left: 0;
position: absolute;
text-align: left;
top: 0;
z-index: 2;
}
&__header {
position: relative;
&:after {
background: $dropdown--header--border;
bottom: 0;
content: '';
display: block;
height: 1px;
left: 5%;
position: absolute;
width: 90%;
}
}
&__items {
padding: 10px 0;
}
&__item {
cursor: pointer;
display: block;
font-size: 0.9em;
padding: 5px 15px;
transition: background 0.25s, color 0.25s;
&:hover {
background: $dropdown--item--background--hover;
color: $dropdown--item--foreground--hover;
}
&.is-selected {
color: $dropdown--item--foreground--active;
}
}
&--align-right & {
left: auto;
right: 0;
}
}
.dropdown {
&__content {
&-enter {
animation: fade-in 0.25s both;
}
&-leave {
animation: fade-out 0.25s both;
}
}
}
.dropdown {
&__content {
&__container {
padding: 25px 30px;
}
}
}

View File

@@ -1,8 +1,6 @@
.sidebar {
box-shadow: inset -1px 0 $sidebar--border;
color: $sidebar--foreground;
}
.sidebar {
&__item {

View File

@@ -2,7 +2,8 @@
&__list {
background: $torrent-list--background;
box-shadow: -1px -1px $torrent-list--border;
border-radius: 4px 4px 0 0;
box-shadow: 0 0 0 1px $torrent-list--border;
list-style: none;
opacity: 1;
overflow: auto;

View File

@@ -0,0 +1,25 @@
@import "../../../node_modules/inuit-defaults/settings.defaults";
@import "../../../node_modules/inuit-functions/tools.functions";
@import "../../../node_modules/inuit-mixins/tools.mixins";
@import "../../../node_modules/inuit-normalize/generic.normalize";
@import "../../../node_modules/inuit-reset/generic.reset";
@import "../../../node_modules/inuit-box-sizing/generic.box-sizing";
@import "../../../node_modules/inuit-page/base.page";
@import "tools/variables";
@import "base/main";
@import "base/layout";
@import "base/typography";
@import "base/animations";
@import "base/form-elements";
@import "objects/action-bar";
@import "objects/client-stats";
@import "objects/content";
@import "objects/dropdown";
@import "objects/sidebar";
@import "objects/modals";
@import "objects/progress-bar";
@import "objects/status-filter";
@import "objects/torrents";

View File

@@ -1,8 +1,10 @@
$blue: #258de5;
$green: #39ce83;
$background: #e3e5e5;
$foreground: #686469;
$background: #1a2f3d;
$foreground: #53718a;
$main-content--background: #e9eef2;
// form elements
$form--label--foreground: #979999;
@@ -20,7 +22,7 @@ $button--primary--foreground: #fff;
$button--primary--background: $green;
// action bar
$action-bar--background: rgba(#fff, 0.75);
$action-bar--background: transparent;
$action-bar--foreground: #1b1a1c;
$action-bar--group--border: rgba(#7a8080, 0.15);
@@ -30,12 +32,8 @@ $action--background--hover: rgba(#333e4a, 0.05);
$action--border--hover: rgba(#333e4a, 0.15);
// filter bar
$sidebar--foreground: #626466;
$sidebar--border: #161316;
$client-stats--primary: #333332;
$client-stats--secondary: #999997;
$client-stats--icon: #8c8c8a;
$sidebar--foreground: #53718a;
$sidebar--border: rgba(darken($sidebar--foreground, 40%), 0.3);
$client-stats--download--primary--foreground: #2bae6c;
$client-stats--download--secondary--foreground: rgba($client-stats--download--primary--foreground, 0.75);
@@ -49,30 +47,30 @@ $client-stats--upload--graph--stroke: rgba(#2387d9, 0.4);
$client-stats--upload--graph--fill--top: rgba(#2387d9, 0.2);
$client-stats--upload--graph--fill--bottom: rgba(#2387d9, 0);
$client-stats--limits--foreground: #999997;
$client-stats--limits--foreground--hover: darken($client-stats--limits--foreground, 20%);
$client-stats--limits--foreground: $foreground;
$client-stats--limits--icon--hover: $blue;
$search-torrents--background: #d9ddde;
$search-torrents--background--active: #39ce83;
$search-torrents--border: rgba(#babfc2, 0.5);
$search-torrents--border--active: #33b574;
$search-torrents--foreground: #625e66;
$search-torrents--base: #091824;
$search-torrents--background: rgba($search-torrents--base, 0.3);
$search-torrents--background--active: $green;
$search-torrents--border: rgba($search-torrents--background, 0.4);
$search-torrents--border--active: $search-torrents--background--active;
$search-torrents--foreground: $sidebar--foreground;
$search-torrents--foreground--active: #1e8954;
$search-torrents--placeholder: rgba(#909799, 0.7);
$search-torrents--placeholder: rgba($sidebar--foreground, 0.4);
$search-torrents--placeholder--active: #2cad6d;
$search-torrents--icon--foreground: #8a9ca6;
$search-torrents--icon--foreground: $sidebar--foreground;
$search-torrents--icon--foreground--active: #2c9e65;
$status-filter--foreground: #626466;
$status-filter--foreground--header: #b5b5b3;
$status-filter--foreground: $sidebar--foreground;
$status-filter--foreground--header: rgba($status-filter--foreground, 0.5);
$status-filter--foreground--active: $blue;
$status-filter--foreground--hover: darken($status-filter--foreground, 15%);
$status-filter--foreground--hover: lighten($status-filter--foreground, 15%);
// torrents list
$torrent-list--background: #fff;
$torrent-list--border: rgba(#000, 0.1);
$torrent-list--border: rgba($background, 0.1);
$torrent--primary--foreground: #333332;
$torrent--primary--foreground--stopped: rgba(#333332, 0.5);
@@ -93,23 +91,23 @@ $torrent--background--selected: $blue;
$progress-bar--background: #e3e5e5;
$progress-bar--background--selected: rgba(#fff, 0.5);
$progress-bar--background--selected--stopped: rgba(#fff, 0.5);
$progress-bar--fill: #39ce83;
$progress-bar--fill: $green;
$progress-bar--fill--stopped: #e3e5e5;
$progress-bar--fill--completed: $blue;
$progress-bar--fill--selected: #fff;
// dropdown menu
$dropdown--background: #2c2d2e;
$dropdown--foreground: #979899;
$dropdown--container--border: #d4d3d1;
$dropdown--label: $status-filter--foreground--header;
$dropdown--value: #807f7e;
$dropdown--header--background: #282929;
$dropdown--header--foreground: #e3e5e5;
$dropdown--header--border: #383b3f;
$dropdown--item--background--hover: #3d3f42;
$dropdown--item--foreground--hover: #bdbebf;
$dropdown--background: rgba(#fff, 0.98);;
$dropdown--foreground: #95a2ad;
$dropdown--container--border: rgba($background, 0.1);
$dropdown--container--shadow: rgba($background, 0.3);
$dropdown--label: #abbac7;
$dropdown--value: #8899a8;
$dropdown--header--border: rgba($background, 0.05);
$dropdown--item--background--hover: rgba($main-content--background, 0.4);
$dropdown--item--foreground--hover: darken($dropdown--foreground, 10%);
$dropdown--item--foreground--active: $blue;
// modal windows
$modal--background: #2f2c30;

View File

@@ -22,6 +22,26 @@ export function addTorrent(hashes) {
}
};
// CLIENT_RECEIVE_TRANSFER_DATA
export function fetchTransferData() {
return function(dispatch) {
return axios.get('/client/stats')
.then((json = {}) => {
return json.data;
})
.then(transferData => {
dispatch({
type: 'CLIENT_RECEIVE_TRANSFER_DATA',
payload: transferData
});
})
.catch((error) => {
console.error('error', error);
});
}
}
export function fetchTorrents() {
return function(dispatch) {
dispatch({
@@ -34,7 +54,7 @@ export function fetchTorrents() {
.then((json = {}) => {
return json.data;
})
.then((torrents) => {
.then(torrents => {
dispatch({
type: 'RECEIVE_TORRENTS',
payload: {

View File

@@ -7,6 +7,7 @@ import UIActions from '../../actions/UIActions';
const methodsToBind = [
'componentDidMount',
'componentWillUnmount',
'getHeader',
'getMenu',
'onItemSelect',
'onDropdownClick',
@@ -19,7 +20,7 @@ export default class SortDropdown extends React.Component {
super();
this.state = {
isExpanded: false
isExpanded: true
};
methodsToBind.forEach((method) => {
@@ -35,6 +36,15 @@ export default class SortDropdown extends React.Component {
window.removeEventListener('click', this.onExternalClick);
}
getHeader() {
return (
<a className="dropdown__button" onClick={this.onDropdownClick}>
<label className="dropdown__label">Sort By</label>
<span className="dropdown__value">{this.props.selectedItem.displayName}</span>
</a>
);
}
getMenu() {
let sortableProperties = [
{
@@ -80,18 +90,26 @@ export default class SortDropdown extends React.Component {
];
let menuItems = sortableProperties.map(function(property, index) {
let classes = classnames({
'dropdown__item': true,
'is-selected': this.props.selectedItem.property === property.property
})
return (
<li className="dropdown__content__item" key={index} onClick={this.onItemSelect.bind(this, property)}>
<li className={classes} key={index} onClick={this.onItemSelect.bind(this, property)}>
{property.displayName}
</li>
);
}, this);
return (
<ul className="dropdown__content">
<li className="dropdown__content__header">Sort Torrents</li>
{menuItems}
</ul>
<div className="dropdown__content">
<div className="dropdown__header">
{this.getHeader()}
</div>
<ul className="dropdown__items">
{menuItems}
</ul>
</div>
);
}
@@ -132,10 +150,7 @@ export default class SortDropdown extends React.Component {
return (
<div className={classes}>
<a className="dropdown__button" onClick={this.onDropdownClick}>
<label className="dropdown__label">Sort By</label>
<span className="dropdown__value">{this.props.selectedItem.displayName}</span>
</a>
{this.getHeader()}
<CSSTransitionGroup
transitionName="dropdown__content"
transitionEnterTimeout={250}

View File

@@ -0,0 +1,144 @@
import React from 'react';
import ReactDOM from 'react-dom';
import format from '../../helpers/formatData';
import Icon from '../icons/Icon';
import LineChart from './LineChart';
const methodsToBind = [
'componentDidMount',
'componentWillReceiveProps',
'shouldComponentUpdate'
];
export default class ClientStats extends React.Component {
constructor() {
super();
this.state = {
sidebarWidth: 0,
transferData: {
download: [],
upload: []
}
};
methodsToBind.forEach((method) => {
this[method] = this[method].bind(this);
});
}
componentDidMount() {
this.setState({
sidebarWidth: ReactDOM.findDOMNode(this).offsetWidth
});
}
componentWillReceiveProps(nextProps) {
// check that the transferData was actually updated since the last component
// update. if it was updated, add the latest download & upload rates to the
// end of the array and remove the first element in the array. if the arrays
// are empty, fill in zeros for the first n entries.
if (nextProps.transferData.updatedAt !== this.props.transferData.updatedAt) {
let index = 0;
let uploadRateHistory = Object.assign([], this.state.transferData.upload);
let downloadRateHistory = Object.assign([], this.state.transferData.download);
if (uploadRateHistory.length === this.props.historyLength) {
uploadRateHistory.shift();
downloadRateHistory.shift();
uploadRateHistory.push(parseInt(nextProps.transferData.upload.rate));
downloadRateHistory.push(parseInt(nextProps.transferData.download.rate));
} else {
while (index < this.props.historyLength) {
if (index < this.props.historyLength - 1) {
uploadRateHistory[index] = 0;
downloadRateHistory[index] = 0;
} else {
uploadRateHistory[index] = parseInt(nextProps.transferData.upload.rate);
downloadRateHistory[index] = parseInt(nextProps.transferData.download.rate);
}
index++;
}
}
this.setState({
transferData: {
download: downloadRateHistory,
upload: uploadRateHistory
}
});
}
}
shouldComponentUpdate(nextProps) {
if (nextProps.transferData.updatedAt !== this.props.transferData.updatedAt) {
return true;
} else {
return true;
}
}
render() {
let uploadRate = format.data(this.props.transferData.upload.rate, '/s');
let uploadTotal = format.data(this.props.transferData.upload.total);
let downloadRate = format.data(this.props.transferData.download.rate, '/s');
let downloadTotal = format.data(this.props.transferData.download.total);
return (
<div className="client-stats sidebar__item">
<div className="client-stat client-stat--download">
<span className="client-stat__icon">
<Icon icon="download" />
</span>
<div className="client-stat__data">
<div className="client-stat__data--primary">
{downloadRate.value}
<em className="unit">{downloadRate.unit}</em>
</div>
<div className="client-stat__data--secondary">
{downloadTotal.value}
<em className="unit">{downloadTotal.unit}</em> Downloaded
</div>
</div>
<LineChart
data={this.state.transferData.download}
height={100}
id="graph--download"
slug="graph--download"
width={this.state.sidebarWidth} />
</div>
<div className="client-stat client-stat--upload">
<span className="client-stat__icon">
<Icon icon="upload" />
</span>
<div className="client-stat__data">
<div className="client-stat__data--primary">
{uploadRate.value}
<em className="unit">{uploadRate.unit}</em>
</div>
<div className="client-stat__data--secondary">
{uploadTotal.value}
<em className="unit">{uploadTotal.unit}</em> Uploaded
</div>
</div>
<LineChart
data={this.state.transferData.upload}
height={100}
id="graph--upload"
slug="graph--upload"
width={this.state.sidebarWidth} />
</div>
<button className="client-stats client-stat--limits">
<Icon icon="limits" /> Limits
</button>
</div>
);
}
}
ClientStats.defaultProps = {
historyLength: 20
};

View File

@@ -7,7 +7,7 @@ export default class LineChart extends React.Component {
super();
}
componentWillUpdate() {
componentDidUpdate() {
let graph = d3.select('#' + this.props.id);
let lineData = this.props.data;
let margin = {
@@ -22,11 +22,11 @@ export default class LineChart extends React.Component {
.linear()
.range([0, width])
.domain([
d3.min(lineData, function(d) {
return d.x;
d3.min(lineData, function(d,i) {
return i;
}),
d3.max(lineData, function(d) {
return d.x;
d3.max(lineData, function(d,i) {
return i;
})
]);
@@ -36,33 +36,33 @@ export default class LineChart extends React.Component {
.range([height - margin.bottom - margin.top, 0])
.domain([
d3.min(lineData, function(d) {
return d.y;
return d;
}),
d3.max(lineData, function(d) {
return d.y;
return d;
})
]);
let lineFunc = d3
.svg
.line()
.x(function(d) {
return xRange(d.x);
.x(function(d,i) {
return xRange(i);
})
.y(function(d) {
return yRange(d.y);
return yRange(d);
})
.interpolate('basis');
let areaFunc = d3
.svg
.area()
.x(function(d) {
return xRange(d.x);
.x(function(d,i) {
return xRange(i);
})
.y0(height)
.y1(function(d) {
return yRange(d.y);
return yRange(d);
})
.interpolate('basis');

View File

@@ -57,7 +57,7 @@ export default class FilterBar extends React.Component {
<Action label="Pause Torrent" slug="pause-torrent" icon="pause"
clickHandler={this.handlePause} />
</div>
<div className="action-bar__group">
<div className="action-bar__group action-bar__group--has-divider">
<AddTorrent />
</div>
</div>

View File

@@ -2,8 +2,8 @@ import { connect } from 'react-redux';
import React from 'react';
import ActionBar from '../containers/ActionBar';
import { fetchTorrents } from '../actions/ClientActions';
import FilterBar from './FilterBar';
import { fetchTorrents, fetchTransferData } from '../actions/ClientActions';
import Sidebar from './Sidebar';
import rootSelector from '../selectors/rootSelector';
import TorrentList from '../containers/TorrentList';
import TorrentListHeader from '../components/torrent-list/TorrentListHeader';
@@ -11,7 +11,8 @@ import TorrentListHeader from '../components/torrent-list/TorrentListHeader';
const methodsToBind = [
'componentWillMount',
'componentWillUnmount',
'getClientData'
'getTransferData',
'getTorrents'
];
class FloodApp extends React.Component {
@@ -20,8 +21,9 @@ class FloodApp extends React.Component {
super();
this.state = {
clientDataFetchInterval: null,
count: 0,
dataFetchInterval: null
torrentFetchInterval: null
};
methodsToBind.forEach((method) => {
@@ -30,33 +32,45 @@ class FloodApp extends React.Component {
}
componentWillMount() {
let getClientData = this.getClientData;
let getTorrents = this.getTorrents;
let getTransferData = this.getTransferData;
this.state.dataFetchInterval = setInterval(function() {
getClientData();
this.state.torrentFetchInterval = setInterval(function() {
getTorrents();
}, 5000);
getClientData();
this.state.clientDataFetchInterval = setInterval(function() {
getTransferData();
}, 5000);
this.getTorrents();
this.getTransferData();
}
componentWillUnmount() {
clearInterval(this.state.dataFetchInterval);
clearInterval(this.state.torrentFetchInterval);
clearInterval(this.state.clientDataFetchInterval);
}
getClientData() {
getTransferData() {
this.props.dispatch(fetchTransferData());
}
getTorrents() {
this.props.dispatch(fetchTorrents());
}
render() {
return (
<div className="flood">
<FilterBar dispatch={this.props.dispatch} uiStore={this.props.ui} />
<main className="main">
<Sidebar dispatch={this.props.dispatch}
filterBy={this.props.ui.torrentList.filterBy}
transferData={this.props.client.transfers}/>
<main className="content">
<ActionBar dispatch={this.props.dispatch} uiStore={this.props.ui} />
<TorrentList dispatch={this.props.dispatch}
selectedTorrents={this.props.ui.torrentList.selected}
torrents={this.props.torrents}
uiStore={this.props.ui}
isFetching={this.props.ui.fetchingData} />
</main>
</div>

View File

@@ -31,12 +31,12 @@ export default class Sidebar extends React.Component {
render() {
return (
<nav className="sidebar">
<ClientStats />
<aside className="sidebar">
<ClientStats transferData={this.props.transferData} />
<SearchBox handleSearchChange={this.handleSearchChange} />
<StatusFilters handleFilterChange={this.handleFilterChange}
activeFilter={this.props.filterBy} />
</nav>
</aside>
);
}

View File

@@ -0,0 +1,42 @@
const initialState = {
transfers: {
updatedAt: 0,
download: {
rate: 0,
total: 0
},
upload: {
rate: 0,
total: 0
}
}
};
export default function clientReducer(state = initialState, action) {
switch (action.type) {
case 'CLIENT_RECEIVE_TRANSFER_DATA':
return Object.assign(
{},
state,
{
...state,
transfers: {
...state.transfers,
updatedAt: Date.now(),
download: {
rate: action.payload.downloadRate,
total: action.payload.downloadTotal
},
upload: {
rate: action.payload.uploadRate,
total: action.payload.uploadTotal
}
}
}
);
default:
return state;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
doctype html
html
head
title= title
link(rel='stylesheet' href='/stylesheets/style.css')
body
block content
script(src='/scripts/app.js')

View File

@@ -17,12 +17,12 @@ var packageInfo = require('./package');
var development = process.env.NODE_ENV === 'development';
var dirs = {
src: 'source',
dist: 'dist',
src: 'client/source',
dist: 'server/assets',
js: 'scripts',
jsDist: 'public/scripts',
jsDist: '',
styles: 'sass',
stylesDist: 'public/stylesheets',
stylesDist: '',
img: 'images',
imgDist: 'images'
};
@@ -92,7 +92,7 @@ function eslintFn () {
gulp.task('eslint', eslintFn);
gulp.task('images', function () {
return gulp.src([dirs.img + '/**/*.*', '!' + dirs.img + '/**/_exports/**/*.*'])
return gulp.src(dirs.src + '/' + dirs.img + '/**/*.*')
.pipe(imagemin({
progressive: true,
svgoPlugins: [{removeViewBox: false}]
@@ -103,7 +103,15 @@ gulp.task('images', function () {
gulp.task('sass', function () {
return gulp.src(dirs.src + '/' + dirs.styles + '/' + files.mainStyles + '.scss')
.pipe(gulpif(development, sourcemaps.init()))
.pipe(sass())
.pipe(sass().on('error', function(error) {
gutil.log(
gutil.colors.green('Sass Error!\n'),
'\n',
error.messageFormatted,
'\n'
);
this.emit('end');
}))
.pipe(autoprefixer())
.pipe(gulpif(development, sourcemaps.write('.')))
.pipe(gulp.dest(dirs.dist + '/' + dirs.stylesDist))
@@ -160,6 +168,7 @@ gulp.task('webpack', function (callback) {
// This runs after webpack's internal watch rebuild.
// eslintFn();
if (development) {
console.log('reloading from webpack');
browserSync.reload();
}
}

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"livereload": "NODE_ENV='development' ./node_modules/.bin/gulp livereload",
"start": "node ./dist/bin/www"
"start": "node ./server/bin/www"
},
"dependencies": {
"axios": "^0.7.0",

View File

@@ -12,6 +12,8 @@ var client = require('./routes/client');
var app = express();
// view engine setup
console.log(__dirname);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
@@ -21,7 +23,7 @@ app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'assets')));
app.use(function(req, res, next) {
req.socket.on("error", function(err) {

View File

@@ -78,6 +78,7 @@ client.prototype.getTorrentList = function(callback) {
callback(null, torrents);
}, function(error) {
console.log(error);
callback(error, null)
});
};

View File

@@ -7,11 +7,18 @@ var Serializer = require('./serializer');
var rtorrent = {};
rtorrent.get = function(api, array) {
var stream = net.connect(5000, 'localhost');
var stream = net.connect({
port: 5000,
host: 'localhost'
});
var deferred = Q.defer();
var xml;
var length = 0;
stream.on('error', function(error) {
console.log(error);
});
stream.setEncoding('UTF8');
try {

View File

@@ -9,9 +9,9 @@ router.get('/', function(req, res, next) {
router.get('/stats', function(req, res, next) {
clientStats.getStats(function(error, results) {
res.json(results);
});
clientStats.getStats(function(error, results) {
res.json(results);
});
});

8
server/views/layout.jade Normal file
View File

@@ -0,0 +1,8 @@
doctype html
html
head
title= title
link(rel='stylesheet' href='/style.css')
body
block content
script(src='/app.js')

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 59.8"><path fill-rule="evenodd" clip-rule="evenodd" fill="#010101" d="M53.7 25.3h-19v-19h-9.4v19h-19v9.4h19v19h9.4v-19h19"/></svg>

Before

Width:  |  Height:  |  Size: 186 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"><path d="M13.5 51h11V9h-11v42zm22-42v42h11V9h-11z" fill-rule="evenodd" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 158 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"><path d="M13.1 9.5L46.9 30 13.1 50.5v-41z" fill-rule="evenodd" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 150 B

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60"><path d="M11.9 11.9H48v36.2H11.9V11.9z" fill-rule="evenodd" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 147 B

View File

@@ -1,85 +0,0 @@
.dropdown {
position: relative;
z-index: 2;
&__content {
background: $dropdown--background;
color: $dropdown--foreground;
left: 0;
margin-top: -5px;
position: absolute;
text-align: left;
top: 100%;
z-index: 2;
&__header {
background: $dropdown--header--background;
border-bottom: 1px solid $dropdown--header--border;
font-size: 1.15em;
margin-bottom: 5px;
padding: 18px 30px 15px 30px;
}
&:after {
border-bottom: 6px solid $dropdown--header--background;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
content: '';
display: block;
left: 20px;
position: absolute;
top: -6px;
.dropdown--align-right & {
left: auto;
right: 20px;
}
}
.dropdown--align-right & {
left: auto;
right: 0;
}
}
}
.dropdown {
&__content {
&-enter {
animation: fade-in 0.25s both;
}
&-leave {
animation: fade-out 0.25s both;
}
&__item {
cursor: pointer;
display: block;
font-size: 0.9em;
padding: 5px 30px;
transition: background 0.25s, color 0.25s;
&:last-child {
margin-bottom: 18px;
}
&:hover {
background: $dropdown--item--background--hover;
color: $dropdown--item--foreground--hover;
}
}
}
}
.dropdown {
&__content {
&__container {
padding: 25px 30px;
}
}
}

View File

@@ -1,24 +0,0 @@
@import "../../node_modules/inuit-defaults/settings.defaults";
@import "../../node_modules/inuit-functions/tools.functions";
@import "../../node_modules/inuit-mixins/tools.mixins";
@import "../../node_modules/inuit-normalize/generic.normalize";
@import "../../node_modules/inuit-reset/generic.reset";
@import "../../node_modules/inuit-box-sizing/generic.box-sizing";
@import "../../node_modules/inuit-page/base.page";
@import "tools/variables";
@import "base/main";
@import "base/layout";
@import "base/typography";
@import "base/animations";
@import "base/form-elements";
@import "objects/action-bar";
@import "objects/client-stats";
@import "objects/dropdown";
@import "objects/sidebar";
@import "objects/modals";
@import "objects/progress-bar";
@import "objects/status-filter";
@import "objects/torrents";

View File

@@ -1,108 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import format from '../../helpers/formatData';
import Icon from '../icons/Icon';
import LineChart from './LineChart';
const methodsToBind = [
'componentDidMount',
'_onChange'
];
export default class ClientStats extends React.Component {
constructor() {
super();
this.state = {
clientStats: {
currentSpeed: {
upload: 0,
download: 0
},
historicalSpeed: {
download: [],
upload: []
},
transferred: {
upload: 0,
download: 0
}
},
sidebarWidth: 0
};
methodsToBind.forEach((method) => {
this[method] = this[method].bind(this);
});
}
componentDidMount() {
this.setState({
sidebarWidth: ReactDOM.findDOMNode(this).offsetWidth
});
}
render() {
let uploadSpeed = format.data(this.state.clientStats.currentSpeed.upload, '/s');
let uploadTotal = format.data(this.state.clientStats.transferred.upload);
let downloadSpeed = format.data(this.state.clientStats.currentSpeed.download, '/s');
let downloadTotal = format.data(this.state.clientStats.transferred.download);
return (
<div className="client-stats sidebar__item">
<div className="client-stat client-stat--download">
<span className="client-stat__icon">
<Icon icon="download" />
</span>
<div className="client-stat__data">
<div className="client-stat__data--primary">
{downloadSpeed.value}
<em className="unit">{downloadSpeed.unit}</em>
</div>
<div className="client-stat__data--secondary">
{downloadTotal.value}
<em className="unit">{downloadTotal.unit}</em> Downloaded
</div>
</div>
<LineChart
data={this.state.clientStats.historicalSpeed.download}
height={100}
id="graph--download"
slug="graph--download"
width={this.state.sidebarWidth} />
</div>
<div className="client-stat client-stat--upload">
<span className="client-stat__icon">
<Icon icon="upload" />
</span>
<div className="client-stat__data">
<div className="client-stat__data--primary">
{uploadSpeed.value}
<em className="unit">{uploadSpeed.unit}</em>
</div>
<div className="client-stat__data--secondary">
{uploadTotal.value}
<em className="unit">{uploadTotal.unit}</em> Uploaded
</div>
</div>
<LineChart
data={this.state.clientStats.historicalSpeed.upload}
height={100}
id="graph--upload"
slug="graph--upload"
width={this.state.sidebarWidth} />
</div>
<button className="client-stats client-stat--limits">
<Icon icon="limits" /> Limits
</button>
</div>
);
}
_onChange() {
this.setState(getClientStats);
}
}

View File

@@ -1,6 +0,0 @@
export default function client(state = {}, action) {
switch (action.type) {
default:
return state;
}
}