From 04f1c0c2383c94a4e8feda4dd541defa3e706eaf Mon Sep 17 00:00:00 2001 From: buinsky Date: Wed, 24 Dec 2014 04:39:18 +0300 Subject: [PATCH] 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 --- src/webui/btjson.cpp | 9 +- src/webui/www/public/scripts/client.js | 131 ++-- src/webui/www/public/scripts/contextmenu.js | 10 +- src/webui/www/public/scripts/dynamicTable.js | 718 +++++++++++++------ src/webui/www/public/scripts/misc.js | 9 +- src/webui/www/public/transferlist.html | 27 +- 6 files changed, 578 insertions(+), 326 deletions(-) diff --git a/src/webui/btjson.cpp b/src/webui/btjson.cpp index 76eeb6ed4..5059594aa 100644 --- a/src/webui/btjson.cpp +++ b/src/webui/btjson.cpp @@ -218,12 +218,9 @@ static QVariantMap toMap(const QTorrentHandle& h) ret[KEY_TORRENT_RATIO] = (ratio > QBtSession::MAX_RATIO) ? -1 : ratio; ret[KEY_TORRENT_STATE] = h.torrentState().toString(); ret[KEY_TORRENT_ETA] = h.eta(); - if (h.has_metadata()) { - if (status.sequential_download) - ret[KEY_TORRENT_SEQUENTIAL_DOWNLOAD] = true; - if (h.first_last_piece_first()) - ret[KEY_TORRENT_FIRST_LAST_PIECE_PRIO] = true; - } + ret[KEY_TORRENT_SEQUENTIAL_DOWNLOAD] = status.sequential_download; + if (h.has_metadata()) + ret[KEY_TORRENT_FIRST_LAST_PIECE_PRIO] = h.first_last_piece_first(); return ret; } diff --git a/src/webui/www/public/scripts/client.js b/src/webui/www/public/scripts/client.js index 91c0a74e7..94e132fe0 100644 --- a/src/webui/www/public/scripts/client.js +++ b/src/webui/www/public/scripts/client.js @@ -29,22 +29,26 @@ var updateTransferInfo = function(){}; var updateTransferList = function(){}; var alternativeSpeedLimits = false; -var stateToImg = function (state) { - if (state == "pausedUP" || state == "pausedDL") { - state = "paused"; - } else { - if (state == "queuedUP" || state == "queuedDL") { - state = "queued"; - } else { - if (state == "checkingUP" || state == "checkingDL") { - state = "checking"; - } - } +selected_filter = getLocalStorageItem('selected_filter', 'all'); +selected_label = null; + +var loadSelectedLabel = function () { + if (getLocalStorageItem('any_label', '1') == '0') + selected_label = getLocalStorageItem('selected_label', ''); + else + selected_label = null; +} +loadSelectedLabel(); + +var saveSelectedLabel = function () { + if (selected_label == null) + localStorage.setItem('any_label', '1'); + else { + localStorage.setItem('any_label', '0'); + localStorage.setItem('selected_label', selected_label); } - return 'images/skin/' + state + '.png'; -}; +} -filter = getLocalStorageItem('selected_filter', 'all'); window.addEvent('load', function () { @@ -96,7 +100,7 @@ window.addEvent('load', function () { $("active_filter").removeClass("selectedFilter"); $("inactive_filter").removeClass("selectedFilter"); $(f + "_filter").addClass("selectedFilter"); - filter = f; + selected_filter = f; localStorage.setItem('selected_filter', f); // Reload torrents if (typeof myTable.table != 'undefined') @@ -116,7 +120,7 @@ window.addEvent('load', function () { loadMethod : 'xhr', contentURL : 'filters.html', onContentLoaded : function () { - setFilter(filter); + setFilter(selected_filter); }, column : 'filtersColumn', height : 300 @@ -129,11 +133,7 @@ window.addEvent('load', function () { var loadTorrentsInfoTimer; var loadTorrentsInfo = function () { - var queueing_enabled = false; var url = new URI('json/torrents'); - url.setData('filter', filter); - url.setData('sort', myTable.table.sortedColumn); - url.setData('reverse', myTable.table.reverseSort); var request = new Request.JSON({ url : url, noCache : true, @@ -143,84 +143,36 @@ window.addEvent('load', function () { clearTimeout(loadTorrentsInfoTimer); loadTorrentsInfoTimer = loadTorrentsInfo.delay(2000); }, - onSuccess : function (events) { + onSuccess : function (response) { $('error_div').set('html', ''); - if (events) { - // Add new torrents or update them - torrent_hashes = myTable.getRowIds(); - events_hashes = new Array(); - pos = 0; - events.each(function (event) { - events_hashes[events_hashes.length] = event.hash; - var row = new Array(); - var data = new Array(); - row.length = 10; - row[0] = stateToImg(event.state); - row[1] = event.name; - row[2] = event.priority > -1 ? event.priority : null; - data[2] = event.priority; - row[3] = friendlyUnit(event.size, false); - data[3] = event.size; - row[4] = (event.progress * 100).round(1); - if (row[4] == 100.0 && event.progress != 1.0) - row[4] = 99.9; - data[4] = event.progress; - row[5] = event.num_seeds; - if (event.num_complete != -1) - row[5] += " (" + event.num_complete + ")"; - data[5] = event.num_seeds; - row[6] = event.num_leechs; - if (event.num_incomplete != -1) - row[6] += " (" + event.num_incomplete + ")"; - data[6] = event.num_leechs; - row[7] = friendlyUnit(event.dlspeed, true); - data[7] = event.dlspeed; - row[8] = friendlyUnit(event.upspeed, true); - data[8] = event.upspeed; - row[9] = friendlyDuration(event.eta); - data[9] = event.eta; - if (event.ratio == -1) - row[10] = "∞"; - else - row[10] = (Math.floor(100 * event.ratio) / 100).toFixed(2); //Don't round up - data[10] = event.ratio; - if (row[2] != null) + if (response) { + var queueing_enabled = false; + var torrents_hashes = new Array(); + for (var i = 0; i < response.length; i++) { + torrents_hashes.push(response[i].hash); + if (response[i].priority > -1) queueing_enabled = true; + myTable.updateRowData(response[i]) + } + + var keys = myTable.rows.getKeys(); + for (var i = 0; i < keys.length; i++) { + if (!torrents_hashes.contains(keys[i])) + myTable.rows.erase(keys[i]); + } - attrs = {}; - attrs['downloaded'] = (event.progress == 1.0); - attrs['state'] = event.state; - attrs['seq_dl'] = (event.seq_dl == true); - attrs['f_l_piece_prio'] = (event.f_l_piece_prio == true); - - if (!torrent_hashes.contains(event.hash)) { - // New unfinished torrent - torrent_hashes[torrent_hashes.length] = event.hash; - //alert("Inserting row"); - myTable.insertRow(event.hash, row, data, attrs, pos); - } else { - // Update torrent data - myTable.updateRow(event.hash, row, data, attrs, pos); - } - - pos++; - }); - // Remove deleted torrents - torrent_hashes.each(function (hash) { - if (!events_hashes.contains(hash)) { - myTable.removeRow(hash); - } - }); + myTable.columns['priority'].force_hide = !queueing_enabled; + myTable.updateColumn('priority'); if (queueing_enabled) { $('queueingButtons').removeClass('invisible'); $('queueingMenuItems').removeClass('invisible'); - myTable.showPriority(); - } else { + } + else { $('queueingButtons').addClass('invisible'); $('queueingMenuItems').addClass('invisible'); - myTable.hidePriority(); } + myTable.updateTable(true); myTable.altRow(); } clearTimeout(loadTorrentsInfoTimer); @@ -230,8 +182,9 @@ window.addEvent('load', function () { }; updateTransferList = function() { + myTable.updateTable(); clearTimeout(loadTorrentsInfoTimer); - loadTorrentsInfo(); + loadTorrentsInfoTimer = loadTorrentsInfo.delay(30); } var loadTransferInfoTimer; diff --git a/src/webui/www/public/scripts/contextmenu.js b/src/webui/www/public/scripts/contextmenu.js index 81d5fdbfc..2aaff877d 100644 --- a/src/webui/www/public/scripts/contextmenu.js +++ b/src/webui/www/public/scripts/contextmenu.js @@ -139,22 +139,22 @@ var ContextMenu = new Class({ var h = myTable.selectedIds(); h.each(function(item, index){ - tr = myTable.rows.get(item); + var data = myTable.rows.get(item).full_data; - if (tr.getAttribute('seq_dl') != 'true') + if (data['seq_dl'] != true) all_are_seq_dl = false; else there_are_seq_dl = true; - if (tr.getAttribute('f_l_piece_prio') != 'true') + if (data['f_l_piece_prio'] != true) all_are_f_l_piece_prio = false; else there_are_f_l_piece_prio = true; - if (tr.getAttribute('downloaded') != 'true') + if (data['progress'] != 1.0) // not downloaded all_are_downloaded = false; - state = tr.getAttribute('state'); + state = data['state']; if ((state != 'pausedUP') && (state != 'pausedDL')) all_are_paused = false; else diff --git a/src/webui/www/public/scripts/dynamicTable.js b/src/webui/www/public/scripts/dynamicTable.js index f7e3accac..9667ac371 100644 --- a/src/webui/www/public/scripts/dynamicTable.js +++ b/src/webui/www/public/scripts/dynamicTable.js @@ -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'); + selectAll : function () { + this.cur.empty(); + 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; + for (var i = 0; i < trs.length; i++) { + var tr = trs[i]; + this.cur.push(tr.hash); + if (!tr.hasClass('selected')) + tr.addClass('selected'); + } }, - showPriority : function () { - if (!this.priority_hidden) - return; - $('prioHeader').removeClass('invisible'); + selectRow : function (hash) { + this.cur.empty(); + this.cur.push(hash); 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; + for (var i = 0; i < trs.length; i++) { + var tr = trs[i]; + if (tr.hash == hash) { + if (!tr.hasClass('selected')) + tr.addClass('selected'); + } + else + if (tr.hasClass('selected')) + tr.removeClass('selected'); + } }, - insertRow : function (id, row, data, attrs, pos) { - if (this.rows.has(id)) { - return; + 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; } - 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]) - } + 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; + }, + + 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]; } - td.injectInside(tr); - }; - tr.addEvent('mouseover', function (e) { - tr.addClass('over'); - }.bind(this)); - tr.addEvent('mouseout', function (e) { - tr.removeClass('over'); + 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)); - 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 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--; } - 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'); - } + + 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; } - } 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'); + 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'); } } - this.cur.empty(); - // Add selected style to new one - if (this.rows.has(id)) { - temptr = this.rows.get(id); - temptr.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(); + } } - this.cur[0] = id; - 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); } - return false; - }.bind(this)); + } + + rowPos = rows.length; - // Insert - var trs = this.table.getChildren('tr'); - if (pos >= trs.length) { - tr.inject(this.table); - } else { - tr.inject(trs[pos], 'before'); + while ((rowPos < trs.length) && (trs.length > 0)) { + trs[trs.length - 1].dispose(); + trs.pop(); } - //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')) { - tr.addClass('selected'); - } - }, this); - }, + updateRow : function (tr, fullUpdate) { + var row = this.rows.get(tr.hash); + data = row[fullUpdate ? 'full_data' : 'data']; - updateRow : function (id, row, data, attrs, newpos) { - if (!this.rows.has(id)) { - return false; + 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'); } - - 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]); - } - } - 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'); } - - return true; }, - removeRow : function (id) { - if (this.cur.contains(id)) { - this.cur.erase(id); - } - if (this.rows.has(id)) { - var tr = this.rows.get(id); + 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); + }; } }); diff --git a/src/webui/www/public/scripts/misc.js b/src/webui/www/public/scripts/misc.js index 31dad6c89..7087f8281 100644 --- a/src/webui/www/public/scripts/misc.js +++ b/src/webui/www/public/scripts/misc.js @@ -16,7 +16,8 @@ function friendlyUnit(value, isSpeed) { while (value >= 1024. && i++ < 6) value /= 1024.; var ret; - ret = value.toFixed(1) + " " + units[i]; + ret = (Math.floor(10 * value) / 10).toFixed(1) //Don't round up + + " " + units[i]; if (isSpeed) ret += "QBT_TR(/s)QBT_TR"; return ret; @@ -73,3 +74,9 @@ if (!Date.prototype.toISOString) { }()); } + +function escapeHtml(str) { + var div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; +}; \ No newline at end of file diff --git a/src/webui/www/public/transferlist.html b/src/webui/www/public/transferlist.html index ac4a6c81d..d12e2027a 100644 --- a/src/webui/www/public/transferlist.html +++ b/src/webui/www/public/transferlist.html @@ -1,21 +1,10 @@ - - - - - - - - - - - - - - - - - -
QBT_TR(Name)QBT_TR#QBT_TR(Size)QBT_TRQBT_TR(Done)QBT_TRQBT_TR(Seeds)QBT_TRQBT_TR(Peers)QBT_TRQBT_TR(Down Speed)QBT_TRQBT_TR(Up Speed)QBT_TRQBT_TR(ETA)QBT_TRQBT_TR(Ratio)QBT_TR
+ + + + + + +