mirror of
https://github.com/transmission/transmission
synced 2024-12-24 08:43:27 +00:00
(trunk web) #5013 "WebUI Inspector file tree" -- possible reimplementation.
This commit is contained in:
parent
7b7da2d315
commit
53ef843cbd
2 changed files with 187 additions and 238 deletions
|
@ -5,14 +5,15 @@
|
|||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
*/
|
||||
|
||||
function FileRow(torrent, i)
|
||||
function FileRow(torrent, depth, name, indices, even)
|
||||
{
|
||||
var fields = {
|
||||
have: 0,
|
||||
index: 0,
|
||||
isDirty: false,
|
||||
indices: [],
|
||||
isWanted: true,
|
||||
priority: 0,
|
||||
priorityLow: false,
|
||||
priorityNormal: true,
|
||||
priorityHigh: false,
|
||||
me: this,
|
||||
size: 0,
|
||||
torrent: null
|
||||
|
@ -26,29 +27,10 @@ function FileRow(torrent, i)
|
|||
root: null
|
||||
},
|
||||
|
||||
initialize = function(torrent, i) {
|
||||
initialize = function(torrent, depth, name, indices, even) {
|
||||
fields.torrent = torrent;
|
||||
fields.index = i;
|
||||
createRow(torrent, i);
|
||||
},
|
||||
|
||||
readAttributes = function(file) {
|
||||
if (fields.have !== file.bytesCompleted) {
|
||||
fields.have = file.bytesCompleted;
|
||||
fields.isDirty = true;
|
||||
}
|
||||
if (fields.size !== file.length) {
|
||||
fields.size = file.length;
|
||||
fields.isDirty = true;
|
||||
}
|
||||
if (fields.priority !== file.priority) {
|
||||
fields.priority = file.priority;
|
||||
fields.isDirty = true;
|
||||
}
|
||||
if (fields.isWanted !== file.wanted) {
|
||||
fields.isWanted = file.wanted;
|
||||
fields.isDirty = true;
|
||||
}
|
||||
fields.indices = indices;
|
||||
createRow(torrent, depth, name, even);
|
||||
},
|
||||
|
||||
refreshWantedHTML = function()
|
||||
|
@ -58,12 +40,6 @@ function FileRow(torrent, i)
|
|||
e.toggleClass('complete', isDone());
|
||||
$(e[0].checkbox).prop('checked', fields.isWanted);
|
||||
},
|
||||
refreshPriorityHTML = function()
|
||||
{
|
||||
$(elements.priority_high_button ).toggleClass('selected', fields.priority === 1 );
|
||||
$(elements.priority_normal_button).toggleClass('selected', fields.priority === 0 );
|
||||
$(elements.priority_low_button ).toggleClass('selected', fields.priority === -1 );
|
||||
},
|
||||
refreshProgressHTML = function()
|
||||
{
|
||||
var pct = 100 * (fields.size ? (fields.have / fields.size) : 1.0),
|
||||
|
@ -75,29 +51,65 @@ function FileRow(torrent, i)
|
|||
'%)' ].join('');
|
||||
setTextContent(elements.progress, c);
|
||||
},
|
||||
refreshHTML = function() {
|
||||
if (fields.isDirty) {
|
||||
fields.isDirty = false;
|
||||
refreshProgressHTML();
|
||||
refreshWantedHTML();
|
||||
refreshPriorityHTML();
|
||||
refreshImpl = function() {
|
||||
var i,
|
||||
file,
|
||||
have = 0,
|
||||
size = 0,
|
||||
wanted = false,
|
||||
low = false,
|
||||
normal = false,
|
||||
high = false;
|
||||
|
||||
// loop through the file_indices that affect this row
|
||||
for (i=0; i<fields.indices.length; ++i) {
|
||||
file = fields.torrent.getFile (fields.indices[i]);
|
||||
have += file.bytesCompleted;
|
||||
size += file.length;
|
||||
wanted |= file.wanted;
|
||||
switch (file.priority) {
|
||||
case -1: low = true; break;
|
||||
case 0: normal = true; break;
|
||||
case 1: high = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((fields.have != have) || (fields.size != size)) {
|
||||
fields.have = have;
|
||||
fields.size = size;
|
||||
refreshProgressHTML();
|
||||
}
|
||||
|
||||
if (fields.isWanted !== wanted) {
|
||||
fields.isWanted = wanted;
|
||||
refreshWantedHTML();
|
||||
}
|
||||
|
||||
if (fields.priorityLow !== low) {
|
||||
fields.priorityLow = low;
|
||||
$(elements.priority_low_button).toggleClass('selected', low);
|
||||
}
|
||||
|
||||
if (fields.priorityNormal !== normal) {
|
||||
fields.priorityNormal = normal;
|
||||
$(elements.priority_normal_button).toggleClass('selected', normal);
|
||||
}
|
||||
|
||||
if (fields.priorityHigh !== high) {
|
||||
fields.priorityHigh = high;
|
||||
$(elements.priority_high_button).toggleClass('selected', high);
|
||||
}
|
||||
},
|
||||
refresh = function() {
|
||||
readAttributes(fields.torrent.getFile(fields.index));
|
||||
refreshHTML();
|
||||
},
|
||||
|
||||
isDone = function () {
|
||||
return fields.have >= fields.size;
|
||||
},
|
||||
|
||||
createRow = function(torrent, i) {
|
||||
var file = torrent.getFile(i), e, name, root, box;
|
||||
createRow = function(torrent, depth, name, even) {
|
||||
var e, root, box;
|
||||
|
||||
root = document.createElement('li');
|
||||
root.id = 't' + fields.torrent.getId() + 'f' + fields.index;
|
||||
root.className = 'inspector_torrent_file_list_entry ' + ((i%2)?'odd':'even');
|
||||
root.className = 'inspector_torrent_file_list_entry' + (even?'even':'odd');
|
||||
elements.root = root;
|
||||
|
||||
e = document.createElement('input');
|
||||
|
@ -135,28 +147,32 @@ function FileRow(torrent, i)
|
|||
|
||||
root.appendChild(box);
|
||||
|
||||
name = file.name || 'Unknown';
|
||||
name = name.substring(name.lastIndexOf('/')+1);
|
||||
name = name.replace(/([\/_\.])/g, "$1");
|
||||
e = document.createElement('div');
|
||||
e.className = "inspector_torrent_file_list_entry_name";
|
||||
setTextContent(e, name);
|
||||
$(e).click(function(){ fireNameClicked(-1); });
|
||||
root.appendChild(e);
|
||||
|
||||
e = document.createElement('div');
|
||||
e.className = "inspector_torrent_file_list_entry_progress";
|
||||
root.appendChild(e);
|
||||
$(e).click(function(){ fireNameClicked(-1); });
|
||||
elements.progress = e;
|
||||
|
||||
refresh();
|
||||
$(root).css('margin-left', '' + (depth*16) + 'px');
|
||||
|
||||
refreshImpl();
|
||||
return root;
|
||||
},
|
||||
|
||||
fireWantedChanged = function(do_want) {
|
||||
$(fields.me).trigger('wantedToggled',[ fields.me, do_want ]);
|
||||
$(fields.me).trigger('wantedToggled',[ fields.indices, do_want ]);
|
||||
},
|
||||
firePriorityChanged = function(priority) {
|
||||
$(fields.me).trigger('priorityToggled',[ fields.me, priority ]);
|
||||
$(fields.me).trigger('priorityToggled',[ fields.indices, priority ]);
|
||||
},
|
||||
fireNameClicked = function() {
|
||||
$(fields.me).trigger('nameClicked',[ fields.me, fields.indices ]);
|
||||
};
|
||||
|
||||
/***
|
||||
|
@ -166,19 +182,12 @@ function FileRow(torrent, i)
|
|||
this.getElement = function() {
|
||||
return elements.root;
|
||||
};
|
||||
this.getIndex = function() {
|
||||
return fields.index;
|
||||
};
|
||||
this.isEditable = function () {
|
||||
return (fields.torrent.getFileCount()>1) && !isDone();
|
||||
};
|
||||
this.getPath = function () {
|
||||
var file = torrent.getFile(i);
|
||||
var path = file.name.replace(/\/\/+/g,'/');
|
||||
path = path.split('/').slice(0,-1);
|
||||
path.push('t' + fields.torrent.getId() + 'f' + fields.index);
|
||||
return path;
|
||||
this.refresh = function() {
|
||||
refreshImpl();
|
||||
};
|
||||
|
||||
initialize(torrent, i);
|
||||
initialize(torrent, depth, name, indices, even);
|
||||
};
|
||||
|
|
|
@ -222,18 +222,18 @@ function Inspector(controller) {
|
|||
else {
|
||||
d = u = 0;
|
||||
if(torrents.length == 1) {
|
||||
d = torrents[0].getDownloadedEver();
|
||||
u = torrents[0].getUploadedEver();
|
||||
|
||||
if (d == 0)
|
||||
d = torrents[0].getHaveValid();
|
||||
d = torrents[0].getDownloadedEver();
|
||||
u = torrents[0].getUploadedEver();
|
||||
|
||||
if (d == 0)
|
||||
d = torrents[0].getHaveValid();
|
||||
}
|
||||
else {
|
||||
for(i=0; t=torrents[i]; ++i) {
|
||||
d += t.getDownloadedEver();
|
||||
u += t.getUploadedEver();
|
||||
}
|
||||
}
|
||||
for(i=0; t=torrents[i]; ++i) {
|
||||
d += t.getDownloadedEver();
|
||||
u += t.getUploadedEver();
|
||||
}
|
||||
}
|
||||
str = fmt.size(u) + ' (Ratio: ' + fmt.ratioString( Math.ratio(u,d))+')';
|
||||
}
|
||||
setTextContent(e.uploaded_lb, str);
|
||||
|
@ -456,183 +456,127 @@ function Inspector(controller) {
|
|||
***** FILES PAGE
|
||||
****/
|
||||
|
||||
changeFileCommand = function(rows, command) {
|
||||
changeFileCommand = function(fileIndices, command) {
|
||||
var torrentId = data.file_torrent.getId();
|
||||
var rowIndices = $.map(rows.slice(0),function (row) {return row.getIndex();});
|
||||
data.controller.changeFileCommand(torrentId, rowIndices, command);
|
||||
data.controller.changeFileCommand(torrentId, fileIndices, command);
|
||||
},
|
||||
|
||||
selectAllFiles = function() {
|
||||
changeFileCommand([], 'files-wanted');
|
||||
onFileWantedToggled = function(ev, fileIndices, want) {
|
||||
changeFileCommand(fileIndices, want?'files-wanted':'files-unwanted');
|
||||
},
|
||||
|
||||
deselectAllFiles = function() {
|
||||
changeFileCommand([], 'files-unwanted');
|
||||
},
|
||||
|
||||
onFileWantedToggled = function(ev, row, want) {
|
||||
changeFileCommand([row], want?'files-wanted':'files-unwanted');
|
||||
},
|
||||
|
||||
onFilePriorityToggled = function(ev, row, priority) {
|
||||
onFilePriorityToggled = function(ev, fileIndices, priority) {
|
||||
var command;
|
||||
switch(priority) {
|
||||
case -1: command = 'priority-low'; break;
|
||||
case 1: command = 'priority-high'; break;
|
||||
default: command = 'priority-normal'; break;
|
||||
}
|
||||
changeFileCommand([row], command);
|
||||
changeFileCommand(fileIndices, command);
|
||||
},
|
||||
|
||||
onNameClicked = function(ev, fileRow, fileIndices) {
|
||||
$(fileRow.getElement()).siblings().slideToggle();
|
||||
},
|
||||
|
||||
clearFileList = function() {
|
||||
$(data.elements.file_list).empty();
|
||||
delete data.file_torrent;
|
||||
delete data.file_torrent_n;
|
||||
delete data.file_rows;
|
||||
},
|
||||
|
||||
createFileTreeModel = function (tor) {
|
||||
var i, j, n, name, tokens, walk, tree, token, sub,
|
||||
leaves = [ ],
|
||||
tree = { children: { }, file_indices: [ ] };
|
||||
|
||||
n = tor.getFileCount();
|
||||
for (i=0; i<n; ++i) {
|
||||
name = tor.getFile(i).name;
|
||||
tokens = name.split('/');
|
||||
walk = tree;
|
||||
for (j=0; j<tokens.length; ++j) {
|
||||
token = tokens[j];
|
||||
sub = walk.children[token];
|
||||
if (!sub) {
|
||||
walk.children[token] = sub = {
|
||||
name: token,
|
||||
parent: walk,
|
||||
children: { },
|
||||
file_indices: [ ],
|
||||
depth: j
|
||||
};
|
||||
}
|
||||
walk = sub;
|
||||
}
|
||||
walk.file_index = i;
|
||||
delete walk.children;
|
||||
leaves.push (walk);
|
||||
}
|
||||
|
||||
for (i=0; i<leaves.length; ++i) {
|
||||
walk = leaves[i];
|
||||
j = walk.file_index;
|
||||
do {
|
||||
walk.file_indices.push (j);
|
||||
walk = walk.parent;
|
||||
} while (walk);
|
||||
}
|
||||
|
||||
return tree;
|
||||
},
|
||||
|
||||
addNodeToView = function (tor, parent, sub, i) {
|
||||
var row;
|
||||
row = new FileRow(tor, sub.depth, sub.name, sub.file_indices, i%2);
|
||||
data.file_rows.push(row);
|
||||
parent.appendChild(row.getElement());
|
||||
$(row).bind('wantedToggled',onFileWantedToggled);
|
||||
$(row).bind('priorityToggled',onFilePriorityToggled);
|
||||
$(row).bind('nameClicked',onNameClicked);
|
||||
}
|
||||
|
||||
addSubtreeToView = function (tor, parent, sub, i) {
|
||||
var key, div;
|
||||
div = document.createElement('div');
|
||||
if (sub.parent)
|
||||
addNodeToView (tor, div, sub, i++);
|
||||
if (sub.children)
|
||||
for (key in sub.children)
|
||||
i = addSubtreeToView (tor, div, sub.children[key]);
|
||||
parent.appendChild(div);
|
||||
return i;
|
||||
},
|
||||
|
||||
updateFilesPage = function() {
|
||||
var i, j, n, sel, row, tor, fragment,
|
||||
box, complete, conn, connections, e, from, heirarchy, item,
|
||||
matches, parents, parentid, path, sum, subheirarchy, to, inner,
|
||||
var i, n, tor, fragment, tree,
|
||||
file_list = data.elements.file_list,
|
||||
torrents = data.torrents;
|
||||
|
||||
// only show one torrent at a time
|
||||
if (torrents.length !== 1) {
|
||||
clearFileList();
|
||||
return;
|
||||
}
|
||||
|
||||
// build the file list
|
||||
tor = torrents[0];
|
||||
|
||||
for (parentid in data.parents) {
|
||||
data.parents[parentid] = $('#'+parentid).children('li').css('display')
|
||||
n = tor ? tor.getFileCount() : 0;
|
||||
if (tor!=data.file_torrent || n==data.file_torrent_n) {
|
||||
// rebuild the file list...
|
||||
clearFileList();
|
||||
data.file_torrent = tor;
|
||||
data.file_torrent_n = n;
|
||||
data.file_rows = [ ];
|
||||
fragment = document.createDocumentFragment();
|
||||
tree = createFileTreeModel (tor);
|
||||
addSubtreeToView (tor, fragment, tree, 0);
|
||||
file_list.appendChild (fragment);
|
||||
} else {
|
||||
// ...refresh the already-existing file list
|
||||
for (i=0, n=data.file_rows.length; i<n; ++i)
|
||||
data.file_rows[i].refresh();
|
||||
}
|
||||
clearFileList();
|
||||
data.file_torrent = tor;
|
||||
n = tor.getFileCount();
|
||||
data.file_rows = [];
|
||||
fragment = document.createDocumentFragment();
|
||||
heirarchy = {'/':[]}
|
||||
|
||||
for (i=0; i<n; ++i) {
|
||||
row = data.file_rows[i] = new FileRow(tor, i);
|
||||
fragment.appendChild(row.getElement());
|
||||
$(row).bind('wantedToggled',onFileWantedToggled);
|
||||
$(row).bind('priorityToggled',onFilePriorityToggled);
|
||||
path = row.getPath();
|
||||
if (path.length == 1) {
|
||||
heirarchy['/'].push(path[0]);
|
||||
} else {
|
||||
subheirarchy = heirarchy;
|
||||
for (j=0; j<path.length; j++) {
|
||||
if (j<(path.length-1)) {
|
||||
subheirarchy[path[j]] = (subheirarchy[path[j]] == undefined) ? {'/':[]} : subheirarchy[path[j]];
|
||||
subheirarchy[path[j]]['PARENT'] = (path[j-1] == undefined) ? [undefined] : [path[j-1].replace(/[\[\] ().]/g,'_')]
|
||||
subheirarchy = subheirarchy[path[j]];
|
||||
} else {
|
||||
parentid = (path[j-1] == undefined) ? [undefined] : [path[j-1].replace(/[\[\] ().]/g,'_')]
|
||||
subheirarchy['/'].push([path[j],parentid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(heirarchy);
|
||||
/*
|
||||
At this point we have a single object containing the whole
|
||||
torrent heirarchy. The '/' key is file element ids as assigned above
|
||||
for the current level. Any other keys are array objects which are
|
||||
directories in the torrent heirarchy, this will also contain a '/'
|
||||
key and potentially more keys for nested directories.
|
||||
*/
|
||||
connections = [];
|
||||
parents = {};
|
||||
var recursiveCreate = function(key,val) {
|
||||
if (key == "PARENT") { return; }
|
||||
if (typeof(val) == "object" &&! Array.isArray(val)) {
|
||||
row = document.createElement('li');
|
||||
row.id = key.replace(/[\[\] ().]/g,'_');
|
||||
row.classname = 'inspector_torrent_file_list_entry odd';
|
||||
|
||||
e = document.createElement('input');
|
||||
e.type = 'checkbox';
|
||||
e.className = "file_wanted_control";
|
||||
e.title = 'Download file';
|
||||
e.checked = true;
|
||||
$(e).change(function() { $($(this).parent()).children('li').children('input').click() });
|
||||
row.appendChild(e);
|
||||
|
||||
e = document.createElement('div');
|
||||
e.className = 'file-priority-radiobox';
|
||||
box = e;
|
||||
|
||||
e = document.createElement('div');
|
||||
e.className = 'low';
|
||||
e.title = 'Low Priority';
|
||||
$(e).click(function(){ $($(this).parent().parent()).children('li').children('div').children('.low').click() });
|
||||
box.appendChild(e);
|
||||
|
||||
e = document.createElement('div');
|
||||
e.className = 'normal';
|
||||
e.title = 'Normal Priority';
|
||||
$(e).click(function(){ $($(this).parent().parent()).children('li').children('div').children('.normal').click() });
|
||||
box.appendChild(e);
|
||||
|
||||
e = document.createElement('div');
|
||||
e.title = 'High Priority';
|
||||
e.className = 'high';
|
||||
$(e).click(function(){ $($(this).parent().parent()).children('li').children('div').children('.high').click() });
|
||||
box.appendChild(e);
|
||||
|
||||
row.appendChild(box);
|
||||
|
||||
inner = document.createElement('div');
|
||||
inner.className = "inspector_torrent_file_list_entry_name";
|
||||
inner.innerHTML = key;
|
||||
row.appendChild(inner);
|
||||
|
||||
complete = document.createElement('div');
|
||||
complete.className = "inspector_torrent_file_list_entry_progress";
|
||||
complete.innerHTML = " "
|
||||
row.appendChild(complete)
|
||||
|
||||
fragment.appendChild(row);
|
||||
connections.push([key.replace(/[\[\] ().]/g,'_'),val['PARENT'][0]])
|
||||
} else {
|
||||
for (item in val) {
|
||||
parents[val[item][1][0]] = ""
|
||||
}
|
||||
connections.push.apply(connections,val)
|
||||
}
|
||||
if (!Array.isArray(val)) {
|
||||
$.each(val,function(key,val) { recursiveCreate(key,val) })
|
||||
}
|
||||
}
|
||||
$.each(heirarchy,function(key,val) { recursiveCreate(key,val) })
|
||||
file_list.appendChild(fragment);
|
||||
for (conn in connections) {
|
||||
from = connections[conn][0];
|
||||
to = connections[conn][1];
|
||||
if (to == undefined) { continue; }
|
||||
$('#'+from).appendTo($('#'+to));
|
||||
$('#'+from).css('margin-left','20px')
|
||||
}
|
||||
for (parentid in parents) {
|
||||
$($('.inspector_torrent_file_list_entry_name',$('#'+parentid))[0]).click(function() { $($(this).parent()).children('li').toggle(); })
|
||||
|
||||
sum = 0;
|
||||
matches = $('#'+parentid).children('li').text().match(/\([^\.]+\)/g)
|
||||
if (matches == null) { continue; }
|
||||
matches.map(function(word) {return parseFloat(word.slice(1,-2)) }).map(function(perc) {sum+=perc})
|
||||
count = $('#'+parentid).children('li').text().match(/\([^\.]+\)/g).length
|
||||
totalcomplete = (sum/count).toFixed(1)
|
||||
|
||||
$($('.inspector_torrent_file_list_entry_progress',$('#'+parentid))[0]).text('('+totalcomplete+'%)')
|
||||
}
|
||||
for (parentid in data.parents) {
|
||||
$('#'+parentid).children('li').css('display',data.parents[parentid])
|
||||
}
|
||||
data.parents = parents;
|
||||
},
|
||||
|
||||
/****
|
||||
|
@ -830,27 +774,23 @@ function Inspector(controller) {
|
|||
data.elements.peers_list = $('#inspector_peers_list')[0];
|
||||
data.elements.trackers_list = $('#inspector_trackers_list')[0];
|
||||
|
||||
data.elements.have_lb = $('#inspector-info-have')[0];
|
||||
data.elements.availability_lb = $('#inspector-info-availability')[0];
|
||||
data.elements.downloaded_lb = $('#inspector-info-downloaded')[0];
|
||||
data.elements.uploaded_lb = $('#inspector-info-uploaded')[0];
|
||||
data.elements.state_lb = $('#inspector-info-state')[0];
|
||||
data.elements.running_time_lb = $('#inspector-info-running-time')[0];
|
||||
data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
|
||||
data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
|
||||
data.elements.error_lb = $('#inspector-info-error')[0];
|
||||
data.elements.size_lb = $('#inspector-info-size')[0];
|
||||
data.elements.foldername_lb = $('#inspector-info-location')[0];
|
||||
data.elements.hash_lb = $('#inspector-info-hash')[0];
|
||||
data.elements.privacy_lb = $('#inspector-info-privacy')[0];
|
||||
data.elements.origin_lb = $('#inspector-info-origin')[0];
|
||||
data.elements.comment_lb = $('#inspector-info-comment')[0];
|
||||
data.elements.have_lb = $('#inspector-info-have')[0];
|
||||
data.elements.availability_lb = $('#inspector-info-availability')[0];
|
||||
data.elements.downloaded_lb = $('#inspector-info-downloaded')[0];
|
||||
data.elements.uploaded_lb = $('#inspector-info-uploaded')[0];
|
||||
data.elements.state_lb = $('#inspector-info-state')[0];
|
||||
data.elements.running_time_lb = $('#inspector-info-running-time')[0];
|
||||
data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
|
||||
data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
|
||||
data.elements.error_lb = $('#inspector-info-error')[0];
|
||||
data.elements.size_lb = $('#inspector-info-size')[0];
|
||||
data.elements.foldername_lb = $('#inspector-info-location')[0];
|
||||
data.elements.hash_lb = $('#inspector-info-hash')[0];
|
||||
data.elements.privacy_lb = $('#inspector-info-privacy')[0];
|
||||
data.elements.origin_lb = $('#inspector-info-origin')[0];
|
||||
data.elements.comment_lb = $('#inspector-info-comment')[0];
|
||||
data.elements.name_lb = $('#torrent_inspector_name')[0];
|
||||
|
||||
// file page's buttons
|
||||
$('#select-all-files').click(selectAllFiles);
|
||||
$('#deselect-all-files').click(deselectAllFiles);
|
||||
|
||||
// force initial 'N/A' updates on all the pages
|
||||
updateInspector();
|
||||
updateInfoPage();
|
||||
|
|
Loading…
Reference in a new issue