WebUI: Change torrent model

Improvements:
- added functions, which may be used to reorder and hiding columns in torrents table
- new columns can be easily added to torrents table
This commit is contained in:
buinsky 2014-12-24 04:39:18 +03:00
parent 91ec84b317
commit 04f1c0c238
6 changed files with 593 additions and 341 deletions

View file

@ -35,27 +35,148 @@ var dynamicTable = new Class({
initialize : function () {},
setup : function (table, progressIndex, context_menu) {
setup : function (table, context_menu) {
this.table = $(table);
this.rows = new Hash();
this.cur = new Array();
this.priority_hidden = false;
this.progressIndex = progressIndex;
this.columns = new Array();
this.context_menu = context_menu;
this.table.sortedColumn = getLocalStorageItem('sorted_column', 'name');
this.table.reverseSort = getLocalStorageItem('reverse_sort', 'false');;
this.sortedColumn = getLocalStorageItem('sorted_column', 'name');
this.reverseSort = getLocalStorageItem('reverse_sort', '0');
this.initColumns();
this.loadColumnsOrder();
this.updateHeader();
},
initColumns : function () {
this.newColumn('state_icon', 'width: 16px', '');
this.newColumn('name', 'min-width: 200px; cursor: pointer', 'QBT_TR(Name)QBT_TR');
this.newColumn('priority', 'width: 90px; cursor: pointer', '#');
this.newColumn('size', 'width: 100px; cursor: pointer', 'QBT_TR(Size)QBT_TR');
this.newColumn('progress', 'width: 80px; cursor: pointer', 'QBT_TR(Done)QBT_TR');
this.newColumn('num_seeds', 'width: 100px; cursor: pointer', 'QBT_TR(Seeds)QBT_TR');
this.newColumn('num_leechs', 'width: 100px; cursor: pointer', 'QBT_TR(Peers)QBT_TR');
this.newColumn('dlspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Down Speed)QBT_TR');
this.newColumn('upspeed', 'width: 100px; cursor: pointer', 'QBT_TR(Up Speed)QBT_TR');
this.newColumn('eta', 'width: 100px; cursor: pointer', 'QBT_TR(ETA)QBT_TR');
this.newColumn('ratio', 'width: 100px; cursor: pointer', 'QBT_TR(Ratio)QBT_TR');
this.columns['state_icon'].onclick = '';
this.columns['state_icon'].dataProperties[0] = 'state';
this.columns['num_seeds'].dataProperties.push('num_complete');
this.columns['num_leechs'].dataProperties.push('num_incomplete');
this.initColumnsFunctions();
},
newColumn : function (name, style, caption) {
var column = {};
column['name'] = name;
column['visible'] = getLocalStorageItem('column_' + name + '_visible', '1');
column['force_hide'] = false;
column['caption'] = caption;
column['style'] = style;
column['onclick'] = 'setSortedColumn(\'' + name + '\');';
column['dataProperties'] = [name];
column['getRowValue'] = function (row, pos) {
if (pos == undefined)
pos = 0;
return row['full_data'][this.dataProperties[pos]];
};
column['compareRows'] = function (row1, row2) {
if (this.getRowValue(row1) < this.getRowValue(row2))
return -1;
else if (this.getRowValue(row1) > this.getRowValue(row2))
return 1;
else return 0;
};
column['updateTd'] = function (td, row) {
td.innerHTML = this.getRowValue(row);
};
this.columns.push(column);
this.columns[name] = column;
$('torrentTableHeader').appendChild(new Element('th'));
},
loadColumnsOrder : function () {
columnsOrder = ['state_icon']; // status icon column is always the first
val = localStorage.getItem('columns_order');
if (val === null || val === undefined) return;
val.split(',').forEach(function(v) {
if ((v in this.columns) && (!columnsOrder.contains(v)))
columnsOrder.push(v);
}.bind(this));
for (i = 0; i < this.columns.length; i++)
if (!columnsOrder.contains(this.columns[i].name))
columnsOrder.push(this.columns[i].name);
for (i = 0; i < this.columns.length; i++)
this.columns[i] = this.columns[columnsOrder[i]];
},
saveColumnsOrder : function () {
val = '';
for (i = 0; i < this.columns.length; i++) {
if (i > 0)
val += ',';
val += this.columns[i].name;
}
localStorage.setItem('columns_order', val);
},
updateHeader : function () {
ths = $('torrentTableHeader').getElements('th');
for (var i = 0; i < ths.length; i++) {
th = ths[i];
th.setAttribute('onclick', this.columns[i].onclick);
th.innerHTML = this.columns[i].caption;
th.setAttribute('style', this.columns[i].style);
if ((this.columns[i].visible == '0') || this.columns[i].force_hide)
th.addClass('invisible');
else
th.removeClass('invisible');
}
},
getColumnPos : function (columnName) {
for (var i = 0; i < this.columns.length; i++)
if (this.columns[i].name == columnName)
return i;
return -1;
},
updateColumn : function (columnName) {
var pos = this.getColumnPos(columnName);
var visible = ((this.columns[pos].visible != '0') && !this.columns[pos].force_hide);
var ths = $('torrentTableHeader').getElements('th');
if (visible)
ths[pos].removeClass('invisible');
else
ths[pos].addClass('invisible');
var trs = this.table.getElements('tr');
for (var i = 0; i < trs.length; i++)
if (visible)
trs[i].getElements('td')[pos].removeClass('invisible');
else
trs[i].getElements('td')[pos].addClass('invisible');
},
setSortedColumn : function (column) {
if (column != this.table.sortedColumn) {
this.table.sortedColumn = column;
this.table.reverseSort = 'false';
} else {
if (column != this.sortedColumn) {
this.sortedColumn = column;
this.reverseSort = '0';
}
else {
// Toggle sort order
this.table.reverseSort = this.table.reverseSort == 'true' ? 'false' : 'true';
this.reverseSort = this.reverseSort == '0' ? '1' : '0';
}
localStorage.setItem('sorted_column', column);
localStorage.setItem('reverse_sort', this.table.reverseSort);
localStorage.setItem('reverse_sort', this.reverseSort);
},
getCurrentTorrentHash : function () {
@ -78,224 +199,269 @@ var dynamicTable = new Class({
}.bind(this));
},
hidePriority : function () {
if (this.priority_hidden)
return;
$('prioHeader').addClass('invisible');
var trs = this.table.getElements('tr');
trs.each(function (tr, i) {
var tds = tr.getElements('td');
tds[2].addClass('invisible');
}.bind(this));
this.priority_hidden = true;
},
showPriority : function () {
if (!this.priority_hidden)
return;
$('prioHeader').removeClass('invisible');
var trs = this.table.getElements('tr');
trs.each(function (tr, i) {
var tds = tr.getElements('td');
tds[2].removeClass('invisible');
}.bind(this));
this.priority_hidden = false;
},
insertRow : function (id, row, data, attrs, pos) {
if (this.rows.has(id)) {
return;
}
var tr = new Element('tr');
for (var a in attrs)
tr.set(a, attrs[a]);
tr.addClass("menu-target");
this.rows.set(id, tr);
for (var i = 0; i < row.length; i++) {
var td = new Element('td');
if (i == this.progressIndex) {
td.adopt(new ProgressBar(row[i].toFloat(), {
'id' : 'pb_' + id,
'width' : 80
}));
if (typeof data[i] != 'undefined')
td.set('data-raw', data[i])
} else {
if (i == 0) {
td.adopt(new Element('img', {
'src' : row[i],
'class' : 'statusIcon'
}));
} else {
if (i == 2) {
// Priority
if (this.priority_hidden)
td.addClass('invisible');
}
td.set('html', row[i]);
if (typeof data[i] != 'undefined')
td.set('data-raw', data[i])
}
}
td.injectInside(tr);
};
tr.addEvent('mouseover', function (e) {
tr.addClass('over');
}.bind(this));
tr.addEvent('mouseout', function (e) {
tr.removeClass('over');
}.bind(this));
tr.addEvent('contextmenu', function (e) {
if (!this.cur.contains(id)) {
// Remove selected style from previous ones
for (i = 0; i < this.cur.length; i++) {
if (this.rows.has(this.cur[i])) {
var temptr = this.rows.get(this.cur[i]);
temptr.removeClass('selected');
}
}
this.cur.empty();
this.cur[this.cur.length] = id;
temptr = this.rows.get(id);
temptr.addClass("selected");
}
return true;
}.bind(this));
tr.addEvent('click', function (e) {
e.stop();
if (e.control) {
// CTRL key was pressed
if (this.cur.contains(id)) {
// remove it
this.cur.erase(id);
// Remove selected style
if (this.rows.has(id)) {
temptr = this.rows.get(id);
temptr.removeClass('selected');
}
} else {
this.cur[this.cur.length] = id;
// Add selected style
if (this.rows.has(id)) {
temptr = this.rows.get(id);
temptr.addClass('selected');
}
}
} else {
if (e.shift && this.cur.length == 1) {
// Shift key was pressed
var first_id = this.cur[0];
var first_tr = this.rows.get(first_id);
var last_id = id;
var last_tr = this.rows.get(last_id);
var all_trs = this.table.getChildren('tr');
var index_first_tr = all_trs.indexOf(first_tr);
var index_last_tr = all_trs.indexOf(last_tr);
var trs_to_select = all_trs.filter(function (item, index) {
if (index_first_tr < index_last_tr)
return (index > index_first_tr) && (index <= index_last_tr);
else
return (index < index_first_tr) && (index >= index_last_tr);
});
trs_to_select.each(function (item, index) {
// Add to selection
this.cur[this.cur.length] = this.getRowId(item);
// Select it visually
item.addClass('selected');
}.bind(this));
} else {
// Simple selection
// Remove selected style from previous ones
for (i = 0; i < this.cur.length; i++) {
if (this.rows.has(this.cur[i])) {
var temptr = this.rows.get(this.cur[i]);
temptr.removeClass('selected');
}
}
this.cur.empty();
// Add selected style to new one
if (this.rows.has(id)) {
temptr = this.rows.get(id);
temptr.addClass('selected');
}
this.cur[0] = id;
updatePropertiesPanel();
}
}
return false;
}.bind(this));
// Insert
var trs = this.table.getChildren('tr');
if (pos >= trs.length) {
tr.inject(this.table);
} else {
tr.inject(trs[pos], 'before');
}
//tr.injectInside(this.table);
// Update context menu
this.context_menu.addTarget(tr);
},
selectAll : function () {
this.cur.empty();
this.rows.each(function (tr, id) {
this.cur[this.cur.length] = id;
if (!tr.hasClass('selected')) {
var trs = this.table.getElements('tr');
for (var i = 0; i < trs.length; i++) {
var tr = trs[i];
this.cur.push(tr.hash);
if (!tr.hasClass('selected'))
tr.addClass('selected');
}
}, this);
}
},
updateRow : function (id, row, data, attrs, newpos) {
if (!this.rows.has(id)) {
return false;
}
var tr = this.rows.get(id);
for (var a in attrs)
tr.set(a, attrs[a]);
var tds = tr.getElements('td');
for (var i = 0; i < row.length; i++) {
if (i == 1)
continue; // Do not refresh name
if (i == this.progressIndex) {
$('pb_' + id).setValue(row[i]);
} else {
if (i == 0) {
tds[i].getChildren('img')[0].set('src', row[i]);
} else {
tds[i].set('html', row[i]);
}
selectRow : function (hash) {
this.cur.empty();
this.cur.push(hash);
var trs = this.table.getElements('tr');
for (var i = 0; i < trs.length; i++) {
var tr = trs[i];
if (tr.hash == hash) {
if (!tr.hasClass('selected'))
tr.addClass('selected');
}
if (typeof data[i] != 'undefined')
tds[i].set('data-raw', data[i])
};
// Prevent freezing of the backlight.
tr.removeClass('over');
// Move to 'newpos'
var trs = this.table.getChildren('tr');
if (newpos >= trs.length) {
tr.inject(this.table);
} else {
tr.inject(trs[newpos], 'before');
else
if (tr.hasClass('selected'))
tr.removeClass('selected');
}
},
updateRowData : function (data) {
var hash = data['hash'];
var row;
if (!this.rows.has(hash)) {
row = {};
this.rows.set(hash, row);
row['full_data'] = {};
row['hash'] = hash;
}
else
row = this.rows.get(hash);
row['data'] = data;
for(var x in data)
row['full_data'][x] = data[x];
},
applyFilter : function (row, filterName, labelName) {
var state = row['full_data'].state;
switch(filterName) {
case 'downloading':
if ((state != 'downloading') && !~state.indexOf('DL'))
return false;
break;
case 'completed':
if ((state != 'uploading') && !~state.indexOf('UP'))
return false;
break;
case 'paused':
if (!~state.indexOf('paused'))
return false;
break;
case 'active':
if ((state != 'uploading') && (state != 'downloading'))
return false;
break;
case 'inactive':
if ((state == 'uploading') || (state == 'downloading'))
return false;
break;
}
if (labelName == null)
return true;
if (labelName != row['full_data'].label)
return false;
return true;
},
removeRow : function (id) {
if (this.cur.contains(id)) {
this.cur.erase(id);
getFilteredAndSortedRows : function () {
var filteredRows = new Array();
var rows = this.rows.getValues();
for (i = 0; i < rows.length; i++)
if (this.applyFilter(rows[i], selected_filter, selected_label)) {
filteredRows.push(rows[i]);
filteredRows[rows[i].hash] = rows[i];
}
filteredRows.sort(function (row1, row2) {
column = this.columns[this.sortedColumn];
res = column.compareRows(row1, row2);
if (this.reverseSort == '0')
return res;
else
return -res;
}.bind(this));
return filteredRows;
},
getTrByHash : function (hash) {
trs = this.table.getElements('tr');
for (var i = 0; i < trs.length; i++)
if (trs[i].hash == hash)
return trs[i];
return null;
},
updateTable : function (fullUpdate) {
if (fullUpdate == undefined)
fullUpdate = false;
var rows = this.getFilteredAndSortedRows();
for (var i = 0; i < this.cur.length; i++)
if (!(this.cur[i] in rows)) {
this.cur.splice(i, 1);
i--;
}
var trs = this.table.getElements('tr');
for (var rowPos = 0; rowPos < rows.length; rowPos++) {
var hash = rows[rowPos]['hash'];
tr_found = false;
for (j = rowPos; j < trs.length; j++)
if (trs[j]['hash'] == hash) {
trs[rowPos].removeClass('over');
tr_found = true;
if (rowPos == j)
break;
trs[j].inject(trs[rowPos], 'before');
var tmpTr = trs[j];
trs.splice(j, 1);
trs.splice(rowPos, 0, tmpTr);
break;
}
if (tr_found) // row already exists in the table
this.updateRow(trs[rowPos], fullUpdate);
else { // else create a new row in the table
var tr = new Element('tr');
tr.addClass("menu-target");
tr['hash'] = rows[rowPos]['hash'];
tr.addEvent('contextmenu', function (e) {
if (!myTable.cur.contains(this.hash))
myTable.selectRow(this.hash);
return true;
});
tr.addEvent('click', function (e) {
e.stop();
if (e.control) {
// CTRL key was pressed
if (myTable.cur.contains(this.hash)) {
// remove it
myTable.cur.erase(this.hash);
// Remove selected style
this.removeClass('selected');
}
else {
myTable.cur.push(this.hash);
// Add selected style
this.addClass('selected');
}
}
else {
if (e.shift && myTable.cur.length == 1) {
// Shift key was pressed
var first_row_hash = myTable.cur[0];
var last_row_hash = this.hash;
myTable.cur.empty();
var trs = myTable.table.getElements('tr');
var select = false;
for (var i = 0; i < trs.length; i++) {
var tr = trs[i];
if ((tr.hash == first_row_hash) || (tr.hash == last_row_hash)) {
myTable.cur.push(tr.hash);
tr.addClass('selected');
select = !select;
}
else {
if (select) {
myTable.cur.push(tr.hash);
tr.addClass('selected');
}
else
tr.removeClass('selected')
}
}
} else {
// Simple selection
myTable.selectRow(this.hash);
updatePropertiesPanel();
}
}
return false;
});
for (var j = 0 ; j < this.columns.length; j++) {
var td = new Element('td');
if ((this.columns[j].visible == '0') || this.columns[j].force_hide)
td.addClass('invisible');
td.injectInside(tr);
}
// Insert
if (rowPos >= trs.length) {
tr.inject(this.table);
trs.push(tr);
}
else {
tr.inject(trs[rowPos], 'before');
trs.splice(rowPos, 0, tr);
}
// Update context menu
this.context_menu.addTarget(tr);
this.updateRow(tr, true);
}
}
if (this.rows.has(id)) {
var tr = this.rows.get(id);
rowPos = rows.length;
while ((rowPos < trs.length) && (trs.length > 0)) {
trs[trs.length - 1].dispose();
trs.pop();
}
},
updateRow : function (tr, fullUpdate) {
var row = this.rows.get(tr.hash);
data = row[fullUpdate ? 'full_data' : 'data'];
tds = tr.getElements('td');
for(var prop in data)
for (var i = 0; i < this.columns.length; i++)
for (var j = 0; j < this.columns[i].dataProperties.length; j++)
if (this.columns[i].dataProperties[j] == prop)
this.columns[i].updateTd(tds[i], row);
if (this.cur.contains(tr.hash)) {
if (!tr.hasClass('selected'))
tr.addClass('selected');
}
else {
if (tr.hasClass('selected'))
tr.removeClass('selected');
}
},
removeRow : function (hash) {
this.cur.erase(hash);
var tr = this.getTrByHash(hash);
if (tr != null) {
tr.dispose();
this.altRow();
this.rows.erase(id);
this.rows.erase(hash);
return true;
}
return false;
@ -305,12 +471,152 @@ var dynamicTable = new Class({
return this.cur;
},
getRowId : function (tr) {
return this.rows.keyOf(tr);
},
getRowIds : function () {
return this.rows.getKeys();
},
initColumnsFunctions : function () {
// state_icon
this.columns['state_icon'].updateTd = function (td, row) {
var state = this.getRowValue(row);
if (state == "pausedUP" || state == "pausedDL")
state = "paused";
else if (state == "queuedUP" || state == "queuedDL")
state = "queued";
else if (state == "checkingUP" || state == "checkingDL")
state = "checking";
var img_path = 'images/skin/' + state + '.png';
if (td.getChildren('img').length) {
var img = td.getChildren('img')[0];
if (img.src.indexOf(img_path) < 0)
img.set('src', img_path);
}
else
td.adopt(new Element('img', {
'src' : img_path,
'class' : 'statusIcon'
}));
};
// name
this.columns['name'].updateTd = function (td, row) {
td.set('html', escapeHtml(this.getRowValue(row)));
};
// priority
this.columns['priority'].updateTd = function (td, row) {
var priority = this.getRowValue(row);
td.set('html', priority < 0 ? null : priority);
};
this.columns['priority'].compareRows = function (row1, row2) {
var row1_val = this.getRowValue(row1);
var row2_val = this.getRowValue(row2);
if (row1_val == -1)
row1_val = 1000000;
if (row2_val == -1)
row2_val = 1000000;
if (row1_val < row2_val)
return -1;
else if (row1_val > row2_val)
return 1;
else return 0;
};
// size
this.columns['size'].updateTd = function (td, row) {
var size = this.getRowValue(row);
td.set('html', friendlyUnit(size, false));
};
// progress
this.columns['progress'].updateTd = function (td, row) {
var progress = this.getRowValue(row);
var progressFormated = (progress * 100).round(1);
if (progressFormated == 100.0 && progress != 1.0)
progressFormated = 99.9;
if (td.getChildren('div').length) {
var div = td.getChildren('div')[0];
if (div.getValue() != progressFormated)
div.setValue(progressFormated);
}
else
td.adopt(new ProgressBar(progressFormated.toFloat(), {
'width' : 80
}));
};
// num_seeds
this.columns['num_seeds'].updateTd = function (td, row) {
var num_seeds = this.getRowValue(row, 0);
var num_complete = this.getRowValue(row, 1);
var html = num_seeds;
if (num_complete != -1)
html += ' (' + num_complete + ')';
td.set('html', html);
};
this.columns['num_seeds'].compareRows = function (row1, row2) {
var num_seeds1 = this.getRowValue(row1, 0);
var num_complete1 = this.getRowValue(row1, 1);
var num_seeds2 = this.getRowValue(row2, 0);
var num_complete2 = this.getRowValue(row2, 1);
if (num_complete1 < num_complete2)
return -1;
else if (num_complete1 > num_complete2)
return 1;
else if (num_seeds1 < num_seeds2)
return -1;
else if (num_seeds1 > num_seeds2)
return 1;
else return 0;
};
// num_leechs
this.columns['num_leechs'].updateTd = this.columns['num_seeds'].updateTd;
this.columns['num_leechs'].compareRows = this.columns['num_seeds'].compareRows;
// dlspeed
this.columns['dlspeed'].updateTd = function (td, row) {
var speed = this.getRowValue(row);
td.set('html', friendlyUnit(speed, true));
};
// upspeed
this.columns['upspeed'].updateTd = this.columns['dlspeed'].updateTd;
// eta
this.columns['eta'].updateTd = function (td, row) {
var eta = this.getRowValue(row);
td.set('html', friendlyDuration(eta, true));
};
// ratio
this.columns['ratio'].updateTd = function (td, row) {
var ratio = this.getRowValue(row);
var html = null;
if (ratio == -1)
html = '∞';
else
html = (Math.floor(100 * ratio) / 100).toFixed(2); //Don't round up
td.set('html', html);
};
}
});