Add priority meter to files

This commit is contained in:
John Furrow
2016-02-10 23:41:00 -08:00
parent 778e5a3b41
commit 0fd6fd7d11
17 changed files with 497 additions and 164 deletions

View File

@@ -58,9 +58,12 @@
.file {
&__size,
&__priority {
color: $directory-tree--file-details--hover--foreground;
&__detail {
&--size,
&--priority {
color: $directory-tree--file-details--hover--foreground;
}
}
}
}
@@ -69,21 +72,33 @@
fill: $directory-tree--icon--file;
}
&__name {
&__detail {
flex: 1 1 auto;
}
&__size,
&__priority {
color: $directory-tree--file-details--foreground;
flex: 0 0 auto;
font-size: 0.95em;
text-align: right;
transition: color 0.25s;
}
&__size {
overflow: hidden;
padding-right: $spacing-unit * 1/4;
text-overflow: ellipsis;
&--size,
&--priority {
color: $directory-tree--file-details--foreground;
flex: 0 0 auto;
font-size: 0.95em;
text-align: right;
transition: color 0.25s;
}
&--priority {
.icon {
height: auto;
margin-right: 0;
width: 16px;
}
}
&:last-child {
padding-right: 0;
}
}
}
@@ -94,6 +109,8 @@
&__parent-directory {
margin-left: -1px;
overflow: hidden;
text-overflow: ellipsis;
.icon {
fill: $torrent-details--directory-tree--parent-directory--icon--fill;

View File

@@ -0,0 +1,69 @@
.priority-meter {
height: 8px;
position: relative;
width: 17px;
&__wrapper {
display: inline-block;
padding: 5px;
vertical-align: middle;
}
&:before,
&:after {
content: '';
display: block;
position: absolute;
}
&:before {
height: 2px;
left: 0;
top: 3px;
transition: background 0.25s;
width: 100%;
}
&:after {
height: 100%;
top: 0;
transition: background 0.25s, left 0.25s;
width: 2px;
}
&--level-0 {
&:before {
background: $priority-meter--track--level-0--background;
}
&:after {
left: 0;
background: $priority-meter--bar--level-0--background;
}
}
&--level-1 {
&:before {
background: $priority-meter--track--level-1--background;
}
&:after {
left: 8px;
background: $priority-meter--bar--level-1--background;
}
}
&--level-2 {
&:before {
background: $priority-meter--track--level-2--background;
}
&:after {
background: $priority-meter--bar--level-2--background;
left: 15px;
}
}
}

View File

@@ -25,6 +25,7 @@
@import "components/icons";
@import "components/loading-indicator";
@import "components/modals";
@import "components/priority-meter";
@import "components/progress-bar";
@import "components/scrollbars";
@import "components/sidebar";

View File

@@ -201,3 +201,11 @@ $loading-indicator--bar--background: #e9eef2;
$loading-indicator--bar--background--inverse: rgba(#38586d, 0.7);
$loading-indicator--tick--background: rgba($blue, 0.75);
$loading-indicator--tick--background--inverse: rgba($blue, 0.75);
// priority meter
$priority-meter--track--level-0--background: rgba(#436076, 0.2);
$priority-meter--bar--level-0--background: #436076;
$priority-meter--track--level-1--background: rgba($blue, 0.2);
$priority-meter--bar--level-1--background: $blue;
$priority-meter--track--level-2--background: rgba($green, 0.2);
$priority-meter--bar--level-2--background: $green;

View File

@@ -209,6 +209,31 @@ const TorrentActions = {
}
});
});
},
setFilePriority: function(hash, fileIndices, priority) {
console.log(hash, fileIndices, priority);
return axios.patch(`/client/torrents/${hash}/priority`, {
hash,
fileIndices,
priority
})
.then((json = {}) => {
return json.data;
})
.then((data) => {
console.log(data);
AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_STOP_TORRENT_SUCCESS,
data
});
})
.catch((error) => {
AppDispatcher.dispatchServerAction({
type: ActionTypes.CLIENT_STOP_TORRENT_ERROR,
error
});
});
}
};

View File

@@ -2,8 +2,61 @@ import React from 'react';
import File from '../icons/File';
import format from '../../util/formatData';
import PriorityMeter from './PriorityMeter';
import TorrentActions from '../../actions/TorrentActions';
const MAX_LEVEL = 2;
const METHODS_TO_BIND = ['handlePriorityChange', 'processPriorities'];
export default class DirectoryFiles extends React.Component {
constructor() {
super();
this.state = {
priorities: {},
files: null
};
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
componentWillMount() {
this.processPriorities();
}
handlePriorityChange(fileIndex) {
let priorities = this.state.priorities;
let priorityLevel = priorities[fileIndex];
if (priorityLevel == null) {
return;
}
if (priorityLevel++ >= MAX_LEVEL) {
priorityLevel = 0;
}
priorities[fileIndex] = priorityLevel;
this.setState({priorities});
TorrentActions.setFilePriority(this.props.hash, [fileIndex], priorityLevel);
}
processPriorities() {
let priorities = this.state.priorities;
this.props.branch.forEach(function (file, index) {
if (priorities[file.index] == null) {
priorities[file.index] = Number(file.priority);
}
});
this.setState({priorities});
}
render() {
let branch = Object.assign([], this.props.branch);
@@ -11,22 +64,26 @@ export default class DirectoryFiles extends React.Component {
return a.filename.localeCompare(b.filename);
});
let files = branch.map((file, fileIndex) => {
let files = branch.map((file, index) => {
let fileSize = format.data(file.sizeBytes, '', 1);
return (
<div className="directory-tree__node directory-tree__node--file file"
key={`${fileIndex}`} title={file.filename}>
key={`${index}`} title={file.filename}>
<div className="file__detail file__name">
<File />
{file.filename}
</div>
<div className="file__detail file__size">
<div className="file__detail file__detail--size">
{fileSize.value}
<em className="unit">{fileSize.unit}</em>
</div>
<div className="file__detail file__priority">
{file.priority}
<div className="file__detail file__detail--size">
{file.percentComplete}%
</div>
<div className="file__detail file__detail--priority">
<PriorityMeter level={this.state.priorities[file.index]}
fileIndex={file.index} onChange={this.handlePriorityChange} />
</div>
</div>
);

View File

@@ -3,9 +3,20 @@ import React from 'react';
import DirectoryFileList from './DirectoryFileList';
import DirectoryTreeNode from './DirectoryTreeNode';
const METHODS_TO_BIND = ['getDirectoryTreeDomNodes'];
export default class DirectoryTree extends React.Component {
constructor() {
super();
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
getDirectoryTreeDomNodes(tree, depth = 0) {
let index = 0;
let hash = this.props.hash;
depth++;
return Object.keys(tree).map((branchName) => {
@@ -14,12 +25,13 @@ export default class DirectoryTree extends React.Component {
if (branchName === 'files') {
return (
<DirectoryFileList branch={branch} key={`${index}${depth}`} />
<DirectoryFileList branch={branch} hash={hash}
key={`${index}${depth}`} />
);
} else {
return (
<DirectoryTreeNode depth={depth} directoryName={branchName}
subTree={branch} key={`${index}${depth}`} />
hash={hash} subTree={branch} key={`${index}${depth}`} />
);
}
});

View File

@@ -24,7 +24,8 @@ export default class DirectoryTreeNode extends React.Component {
if (this.state.expanded) {
return (
<div className="directory-tree__node directory-tree__node--group">
<DirectoryTree tree={this.props.subTree} depth={this.props.depth} />
<DirectoryTree tree={this.props.subTree} depth={this.props.depth}
hash={this.props.hash} />
</div>
);
} else {

View File

@@ -0,0 +1,42 @@
import classnames from 'classnames';
import React from 'react';
const MAX_LEVEL = 2;
const METHODS_TO_BIND = ['handleClick'];
export default class PriorityMeter extends React.Component {
constructor() {
super();
this.state = {
level: null
};
METHODS_TO_BIND.forEach((method) => {
this[method] = this[method].bind(this);
});
}
getLevel() {
if (this.state.level == null) {
return this.props.level;
} else {
return this.state.level;
}
}
handleClick() {
this.props.onChange(this.props.fileIndex);
}
render() {
let level = this.props.level;
return (
<div className="priority-meter__wrapper" onClick={this.handleClick}>
<div className={`priority-meter priority-meter--level-${level}`}/>
</div>
);
}
}

View File

@@ -18,7 +18,6 @@ export default class BaseIcon extends React.Component {
BaseIcon.defaultProps = {
className: '',
what: 'hello',
viewBox: '0 0 60 60'
};

View File

@@ -39,7 +39,9 @@ export default class TorrentFiles extends React.Component {
this.constructDirectoryTree(tree, file.pathComponents[0], file);
});
return <DirectoryTree tree={tree} depth={0} />;
return (
<DirectoryTree tree={tree} depth={0} hash={this.props.torrent.hash} />
);
}
isLoaded() {

File diff suppressed because one or more lines are too long

View File

@@ -974,32 +974,41 @@ body {
display: flex;
line-height: 1.7;
width: 100%; }
.directory-tree__node .file:hover .file__size, .directory-tree__node .file:hover .file__priority {
.directory-tree__node .file:hover .file__detail--size, .directory-tree__node .file:hover .file__detail--priority {
color: #527893; }
.directory-tree__node .file .icon {
fill: #344b5b; }
.directory-tree__node .file__name {
.directory-tree__node .file__detail {
-webkit-box-flex: 1;
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto; }
.directory-tree__node .file__size, .directory-tree__node .file__priority {
color: #2b4456;
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
font-size: 0.95em;
text-align: right;
-webkit-transition: color 0.25s;
transition: color 0.25s; }
.directory-tree__node .file__size {
padding-right: 6.25px; }
flex: 1 1 auto;
overflow: hidden;
padding-right: 6.25px;
text-overflow: ellipsis; }
.directory-tree__node .file__detail--size, .directory-tree__node .file__detail--priority {
color: #2b4456;
-webkit-box-flex: 0;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
font-size: 0.95em;
text-align: right;
-webkit-transition: color 0.25s;
transition: color 0.25s; }
.directory-tree__node .file__detail--priority .icon {
height: auto;
margin-right: 0;
width: 16px; }
.directory-tree__node .file__detail:last-child {
padding-right: 0; }
.directory-tree__node--file-list {
margin-bottom: 3px; }
.directory-tree__parent-directory {
margin-left: -1px; }
margin-left: -1px;
overflow: hidden;
text-overflow: ellipsis; }
.directory-tree__parent-directory .icon {
fill: rgba(58, 92, 116, 0.5);
margin-right: 8px;
@@ -1277,6 +1286,47 @@ body {
.modal__animation-leave-active {
opacity: 0; }
.priority-meter {
height: 8px;
position: relative;
width: 17px; }
.priority-meter__wrapper {
display: inline-block;
padding: 5px;
vertical-align: middle; }
.priority-meter:before, .priority-meter:after {
content: '';
display: block;
position: absolute; }
.priority-meter:before {
height: 2px;
left: 0;
top: 3px;
-webkit-transition: background 0.25s;
transition: background 0.25s;
width: 100%; }
.priority-meter:after {
height: 100%;
top: 0;
-webkit-transition: background 0.25s, left 0.25s;
transition: background 0.25s, left 0.25s;
width: 2px; }
.priority-meter--level-0:before {
background: rgba(67, 96, 118, 0.2); }
.priority-meter--level-0:after {
left: 0;
background: #436076; }
.priority-meter--level-1:before {
background: rgba(37, 141, 229, 0.2); }
.priority-meter--level-1:after {
left: 8px;
background: #258de5; }
.priority-meter--level-2:before {
background: rgba(57, 206, 131, 0.2); }
.priority-meter--level-2:after {
background: #39ce83;
left: 15px; }
.progress-bar {
height: 3px;
position: relative;

File diff suppressed because one or more lines are too long

View File

@@ -130,8 +130,12 @@ var client = {
clientUtil.defaults.fileProperties,
filesData
);
files = files.map(function (file) {
file.filename = file.pathComponents[file.pathComponents.length - 1];
file.percentComplete = (file.completedChunks / file.sizeChunks * 100).toFixed(0);
delete(file.completedChunks);
delete(file.sizeChunks);
return file;
});
}
@@ -180,6 +184,36 @@ var client = {
});
},
setPriority: function (hash, data, callback) {
// TODO Add support for multiple hashes.
var fileIndex = data.fileIndices[0];
var multicall = [
[
{
methodName: 'f.priority.set',
params: [
hash + ':f' + fileIndex,
data.priority
]
},
{
methodName: 'd.update_priorities',
params: [
hash
]
}
]
];
rTorrent.get('system.multicall', multicall)
.then(function(data) {
callback(null, data);
}, function(error) {
callback(error, null);
});
},
setSpeedLimits: function(data, callback) {
var methodName = 'throttle.global_down.max_rate.set';

View File

@@ -43,6 +43,10 @@ router.get('/torrents', function(req, res, next) {
client.getTorrentList(ajaxUtil.getResponseFn(res));
});
router.patch('/torrents/:hash/priority', function(req, res, next) {
client.setPriority(req.params.hash, req.body, ajaxUtil.getResponseFn(res));
});
router.get('/torrents/status-count', function(req, res, next) {
client.getTorrentStatusCount(ajaxUtil.getResponseFn(res));
});

View File

@@ -138,16 +138,18 @@ var clientUtil = {
fileProperties: [
'path',
'pathComponents',
'pathDepth',
'priority',
'sizeBytes'
'sizeBytes',
'sizeChunks',
'completedChunks'
],
filePropertyMethods: [
'f.get_path=',
'f.get_path_components=',
'f.get_path_depth=',
'f.get_priority=',
'f.get_size_bytes='
'f.get_size_bytes=',
'f.get_size_chunks=',
'f.completed_chunks='
],
trackerProperties: [
'group',
@@ -213,7 +215,9 @@ var clientUtil = {
]
},
mapClientProps: function(props, data) {
// TODO clean this up, write comments...
mapClientProps: function(props, data, includeIndex) {
var index = 0;
var mappedObject = [];
if (data[0].length === 1) {
@@ -227,6 +231,8 @@ var clientUtil = {
for (a = 0, lenA = props.length; a < lenA; a++) {
mappedObject[i][props[a]] = data[i][a];
}
// Index is needed for setting a file's priorities.
mappedObject[i].index = index++;
}
}