diff --git a/src/webui/www/private/css/style.css b/src/webui/www/private/css/style.css index 588ef971e..3200778ce 100644 --- a/src/webui/www/private/css/style.css +++ b/src/webui/www/private/css/style.css @@ -456,7 +456,7 @@ td.generalLabel { vertical-align: top; } -#filesTable { +#torrentFilesTableDiv { line-height: 20px; } diff --git a/src/webui/www/private/index.html b/src/webui/www/private/index.html index e4c9c66a9..8578319f1 100644 --- a/src/webui/www/private/index.html +++ b/src/webui/www/private/index.html @@ -167,6 +167,17 @@
  • QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Remove tracker)QBT_TR[CONTEXT=TrackerListWidget]
  • QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget] QBT_TR(Copy tracker URL)QBT_TR[CONTEXT=TrackerListWidget]
  • +
    diff --git a/src/webui/www/private/properties_content.html b/src/webui/www/private/properties_content.html index badda462c..9d1ac5a32 100644 --- a/src/webui/www/private/properties_content.html +++ b/src/webui/www/private/properties_content.html @@ -134,20 +134,21 @@ diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index d9bc334d4..b2de70fb8 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -27,6 +27,7 @@ var torrentsTable = new TorrentsTable(); var torrentTrackersTable = new TorrentTrackersTable(); var torrentPeersTable = new TorrentPeersTable(); +var torrentFilesTable = new TorrentFilesTable(); var searchResultsTable = new SearchResultsTable(); var searchPluginsTable = new SearchPluginsTable(); diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index ddc1c2832..a74e7b308 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -452,6 +452,10 @@ var DynamicTable = new Class({ } }, + getSortedColunn: function() { + return localStorage.getItem('sorted_column_' + this.dynamicTableDivId); + }, + setSortedColumn: function(column) { if (column != this.sortedColumn) { var oldColumn = this.sortedColumn; @@ -1579,4 +1583,78 @@ var TorrentTrackersTable = new Class({ }, }); +var TorrentFilesTable = new Class({ + Extends: DynamicTable, + + initColumns: function() { + this.newColumn('checked', '', '', 50, true); + this.newColumn('name', '', 'QBT_TR(Name)QBT_TR[CONTEXT=TrackerListWidget]', 300, true); + this.newColumn('size', '', 'QBT_TR(Size)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); + this.newColumn('progress', '', 'QBT_TR(Progress)QBT_TR[CONTEXT=TrackerListWidget]', 100, true); + this.newColumn('priority', '', 'QBT_TR(Download Priority)QBT_TR[CONTEXT=TrackerListWidget]', 150, true); + this.newColumn('remaining', '', 'QBT_TR(Remaining)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); + this.newColumn('availability', '', 'QBT_TR(Availability)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); + + this.initColumnsFunctions(); + }, + + initColumnsFunctions: function() { + var displaySize = function(td, row) { + var size = this.getRowValue(row); + td.set('html', friendlyUnit(size, false)); + } + var displayPercentage = function(td, row) { + var value = this.getRowValue(row); + td.set('html', friendlyPercentage(value)); + }; + + this.columns['checked'].updateTd = function(td, row) { + var id = row.rowId; + var value = this.getRowValue(row); + + if (isDownloadCheckboxExists(id)) { + updateDownloadCheckbox(id, value); + } + else { + var treeImg = new Element('img', { + src: 'images/L.gif', + style: 'margin-bottom: -2px' + }); + td.adopt(treeImg, createDownloadCheckbox(row.rowId, value)); + } + }; + + this.columns['size'].updateTd = displaySize; + + this.columns['progress'].updateTd = function(td, row) { + var id = row.rowId; + var value = this.getRowValue(row); + + var progressBar = $('pbf_' + id); + if (progressBar === null) { + td.adopt(new ProgressBar(value.toFloat(), { + 'id': 'pbf_' + id, + 'width': 80 + })); + } + else { + progressBar.setValue(value.toFloat()); + } + }; + + this.columns['priority'].updateTd = function(td, row) { + var id = row.rowId; + var value = this.getRowValue(row); + + if (isPriorityComboExists(id)) + updatePriorityCombo(id, value); + else + td.adopt(createPriorityCombo(id, value)); + }; + + this.columns['remaining'].updateTd = displaySize; + this.columns['availability'].updateTd = displayPercentage; + } +}); + /*************************************************************/ diff --git a/src/webui/www/private/scripts/prop-files.js b/src/webui/www/private/scripts/prop-files.js index 089741bd0..82bed24d0 100644 --- a/src/webui/www/private/scripts/prop-files.js +++ b/src/webui/www/private/scripts/prop-files.js @@ -3,68 +3,6 @@ var is_seed = true; var current_hash = ""; -var setCBState = function(state) { - $("tristate_cb").state = state; - if (state === "partial") { - $("tristate_cb").indeterminate = true; - } - else if (state === "checked") { - $("tristate_cb").indeterminate = false; - $("tristate_cb").checked = true; - } - else if (state === "unchecked") { - $("tristate_cb").indeterminate = false; - $("tristate_cb").checked = false; - } -}; - -var switchCBState = function() { - // Uncheck - if (($("tristate_cb").state === "partial") || ($("tristate_cb").state === "checked")) { - $("tristate_cb").state = "unchecked"; - $("tristate_cb").checked = false; - // Uncheck all checkboxes - var indexes = []; - $$('input.DownloadedCB').each(function(item, index) { - item.erase("checked"); - indexes.push(index); - }); - setFilePriority(indexes, 0); - } - else if ($("tristate_cb").state === "unchecked") { - // Check - $("tristate_cb").state = "checked"; - $("tristate_cb").checked = true; - // Check all checkboxes - var indexes = []; - $$('input.DownloadedCB').each(function(item, index) { - item.set("checked", "checked"); - indexes.push(index); - }); - setFilePriority(indexes, FilePriority.Normal); - } -}; - -var allCBChecked = function() { - var CBs = $$('input.DownloadedCB'); - for (var i = 0; i < CBs.length; i += 1) { - var item = CBs[i]; - if (!$defined(item.get('checked')) || !item.get('checked')) - return false; - } - return true; -}; - -var allCBUnchecked = function() { - var CBs = $$('input.DownloadedCB'); - for (var i = 0; i < CBs.length; i += 1) { - var item = CBs[i]; - if ($defined(item.get('checked')) && item.get('checked')) - return false; - } - return true; -}; - var FilePriority = { "Ignored": 0, "Normal": 1, @@ -86,194 +24,191 @@ var normalizePriority = function(priority) { } }; -var setFilePriority = function(id, priority) { - if (current_hash === "") return; - var ids = Array.isArray(id) ? id : [id]; +var fileCheckboxChanged = function(e) { + var checkbox = e.target; + var priority = checkbox.checked ? FilePriority.Normal : FilePriority.Ignored; + var id = checkbox.get('data-id'); - clearTimeout(loadTorrentFilesDataTimer); - new Request({ - url: 'api/v2/torrents/filePrio', - method: 'post', - data: { - 'hash': current_hash, - 'id': ids.join('|'), - 'priority': priority - }, - onComplete: function() { - loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000); - } - }).send(); - // Display or add combobox - if (priority > 0) { - ids.forEach(function(_id) { - if ($('comboPrio' + _id).hasClass("invisible")) { - $('comboPrio' + _id).set("value", priority); - $('comboPrio' + _id).removeClass("invisible"); - } - }); - } - else { - ids.forEach(function(_id) { - if (!$('comboPrio' + _id).hasClass("invisible")) - $('comboPrio' + _id).addClass("invisible"); - }); - } + setFilePriority(id, priority); + setGlobalCheckboxState(); + return true; }; -var createDownloadedCB = function(id, downloaded) { - var CB = new Element('input'); - CB.set('type', 'checkbox'); - if (downloaded) - CB.set('checked', 'checked'); - CB.set('id', 'cbPrio' + id); - CB.set('class', 'DownloadedCB'); - CB.addEvent('change', function(e) { - var checked = 0; - if ($defined($('cbPrio' + id).get('checked')) && $('cbPrio' + id).get('checked')) - checked = 1; - setFilePriority(id, checked); - if (allCBChecked()) { - setCBState("checked"); - } - else if (allCBUnchecked()) { - setCBState("unchecked"); - } - else { - setCBState("partial"); - } - }); - return CB; +var fileComboboxChanged = function(e) { + var combobox = e.target; + var newPriority = combobox.value; + var id = combobox.get('data-id'); + + setFilePriority(id, newPriority); +}; + +var isDownloadCheckboxExists = function(id) { + return ($('cbPrio' + id) !== null); +}; + +var createDownloadCheckbox = function(id, download) { + var checkbox = new Element('input'); + checkbox.set('type', 'checkbox'); + if (download) + checkbox.set('checked', 'checked'); + checkbox.set('id', 'cbPrio' + id); + checkbox.set('data-id', id); + checkbox.set('class', 'DownloadedCB'); + checkbox.addEvent('change', fileCheckboxChanged); + return checkbox; +}; + +var updateDownloadCheckbox = function(id, download) { + var checkbox = $('cbPrio' + id); + checkbox.checked = download; }; -var createPriorityCombo = function(id, selected_prio) { +var isPriorityComboExists = function(id) { + return ($('comboPrio' + id) !== null); +}; + +var createPriorityOptionElement = function(priority, selected, html) { + var elem = new Element('option'); + elem.set('value', priority.toString()); + elem.set('html', html); + if (selected) + elem.setAttribute('selected', ''); + return elem; +}; + +var createPriorityCombo = function(id, selectedPriority) { var select = new Element('select'); select.set('id', 'comboPrio' + id); - select.addEvent('change', function(e) { - var new_prio = $('comboPrio' + id).get('value'); - setFilePriority(id, new_prio); - }); + select.set('data-id', id); + select.set('disabled', is_seed); + select.addClass('combo_priority'); + select.addEvent('change', fileComboboxChanged); + + createPriorityOptionElement(FilePriority.Ignored, (FilePriority.Ignored === selectedPriority), 'QBT_TR(Do not download)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select); + createPriorityOptionElement(FilePriority.Normal, (FilePriority.Normal === selectedPriority), 'QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select); + createPriorityOptionElement(FilePriority.High, (FilePriority.High === selectedPriority), 'QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select); + createPriorityOptionElement(FilePriority.Maximum, (FilePriority.Maximum === selectedPriority), 'QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]').injectInside(select); + + return select; +}; - function createOptionElement(priority, html) { - var elem = new Element("option"); - elem.set('value', priority.toString()); - elem.set('html', html); - if (priority == selected_prio) - elem.setAttribute('selected', ''); - return elem; +var updatePriorityCombo = function(id, selectedPriority) { + var combobox = $('comboPrio' + id); + + if (parseInt(combobox.value) !== selectedPriority) + selectComboboxPriority(combobox, selectedPriority); + + if (combobox.disabled !== is_seed) + combobox.disabled = is_seed; +}; + +var selectComboboxPriority = function(combobox, priority) { + var options = combobox.options; + for (var i = 0; i < options.length; ++i) { + var option = options[i]; + if (parseInt(option.value) === priority) + option.setAttribute('selected', ''); + else + option.removeAttribute('selected'); } - createOptionElement(FilePriority.Normal, "QBT_TR(Normal)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select); - createOptionElement(FilePriority.High, "QBT_TR(High)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select); - createOptionElement(FilePriority.Maximum, "QBT_TR(Maximum)QBT_TR[CONTEXT=PropListDelegate]").injectInside(select); + combobox.value = priority; +}; - if (is_seed || (selected_prio === FilePriority.Ignored) || (selected_prio === FilePriority.Mixed)) { - select.addClass("invisible"); +var switchCheckboxState = function() { + var rows = []; + var priority = FilePriority.Ignored; + + if ($('tristate_cb').state === "checked") { + setGlobalCheckboxUnchecked(); + // set file priority for all checked to Ignored + torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) { + if (row.full_data.checked) + rows.push(row.full_data.rowId); + }); } else { - select.removeClass("invisible"); + setGlobalCheckboxChecked(); + priority = FilePriority.Normal; + // set file priority for all unchecked to Normal + torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) { + if (!row.full_data.checked) + rows.push(row.full_data.rowId); + }); } - select.addClass("combo_priority"); - return select; + + if (rows.length > 0) + setFilePriority(rows, priority); }; -var filesDynTable = new Class({ +var setGlobalCheckboxState = function() { + if (isAllCheckboxesChecked()) + setGlobalCheckboxChecked(); + else if (isAllCheckboxesUnchecked()) + setGlobalCheckboxUnchecked(); + else + setGlobalCheckboxPartial(); +}; - initialize: function() {}, +var setGlobalCheckboxChecked = function() { + $('tristate_cb').state = "checked"; + $('tristate_cb').indeterminate = false; + $('tristate_cb').checked = true; +}; - setup: function(table) { - this.table = $(table); - this.rows = new Hash(); - }, +var setGlobalCheckboxUnchecked = function() { + $('tristate_cb').state = "unchecked"; + $('tristate_cb').indeterminate = false; + $('tristate_cb').checked = false; +}; - removeRow: function(id) { - if (this.rows.has(id)) { - var tr = this.rows.get(id); - tr.dispose(); - this.rows.erase(id); - return true; - } - return false; - }, +var setGlobalCheckboxPartial = function() { + $('tristate_cb').state = "partial"; + $('tristate_cb').indeterminate = true; +}; - removeAllRows: function() { - this.rows.each(function(tr, id) { - this.removeRow(id); - }.bind(this)); - }, +var isAllCheckboxesChecked = function() { + var checkboxes = $$('input.DownloadedCB'); + for (var i = 0; i < checkboxes.length; ++i) { + if (!checkboxes[i].checked) + return false; + } + return true; +}; - updateRow: function(tr, row, id) { - var tds = tr.getElements('td'); - for (var i = 0; i < row.length; ++i) { - switch (i) { - case 0: // checkbox - if (row[i] > 0) - tds[i].getChildren('input')[0].set('checked', 'checked'); - else - tds[i].getChildren('input')[0].removeProperty('checked'); - break; - case 3: // progress bar - $('pbf_' + id).setValue(row[i].toFloat()); - break; - case 4: // download priority - var priority = normalizePriority(row[i]); - if (!is_seed && (priority > 0)) { - tds[i].getChildren('select').set('value', priority); - if ($('comboPrio' + id).hasClass("invisible")) - $('comboPrio' + id).removeClass("invisible"); - } - else { - if (!$('comboPrio' + id).hasClass("invisible")) - $('comboPrio' + id).addClass("invisible"); - } - break; - default: - tds[i].set('html', row[i]); - } - } - return true; - }, +var isAllCheckboxesUnchecked = function() { + var checkboxes = $$('input.DownloadedCB'); + for (var i = 0; i < checkboxes.length; ++i) { + if (checkboxes[i].checked) + return false; + } + return true; +}; - insertRow: function(id, row) { - if (this.rows.has(id)) { - var tableRow = this.rows.get(id); - this.updateRow(tableRow, row, id); - return; - } - //this.removeRow(id); - var tr = new Element('tr'); - this.rows.set(id, tr); - for (var i = 0; i < row.length; ++i) { - var td = new Element('td'); - switch (i) { - case 0: - var tree_img = new Element('img', { - src: 'images/L.gif', - style: 'margin-bottom: -2px' - }); - td.adopt(tree_img, createDownloadedCB(id, row[i])); - break; - case 1: - td.set('html', row[i]); - td.set('title', row[i]); - break; - case 3: - td.adopt(new ProgressBar(row[i].toFloat(), { - 'id': 'pbf_' + id, - 'width': 80 - })); - break; - case 4: - td.adopt(createPriorityCombo(id, normalizePriority(row[i]))); - break; - default: - td.set('html', row[i]); - break; - } - td.injectInside(tr); +var setFilePriority = function(id, priority) { + if (current_hash === "") return; + var ids = Array.isArray(id) ? id : [id]; + + clearTimeout(loadTorrentFilesDataTimer); + new Request({ + url: 'api/v2/torrents/filePrio', + method: 'post', + data: { + 'hash': current_hash, + 'id': ids.join('|'), + 'priority': priority + }, + onComplete: function() { + loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000); } - tr.injectInside(this.table); - }, -}); + }).send(); + + ids.forEach(function(_id) { + var combobox = $('comboPrio' + _id); + if (combobox !== null) + selectComboboxPriority(combobox, priority); + }); +}; var loadTorrentFilesDataTimer; var loadTorrentFilesData = function() { @@ -284,13 +219,13 @@ var loadTorrentFilesData = function() { } var new_hash = torrentsTable.getCurrentTorrentHash(); if (new_hash === "") { - fTable.removeAllRows(); + torrentFilesTable.clear(); clearTimeout(loadTorrentFilesDataTimer); loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); return; } if (new_hash != current_hash) { - fTable.removeAllRows(); + torrentFilesTable.clear(); current_hash = new_hash; } var url = new URI('api/v2/torrents/files?hash=' + current_hash); @@ -298,51 +233,48 @@ var loadTorrentFilesData = function() { url: url, noCache: true, method: 'get', - onFailure: function() { - $('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]'); + onComplete: function() { clearTimeout(loadTorrentFilesDataTimer); - loadTorrentFilesDataTimer = loadTorrentFilesData.delay(10000); + loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); }, onSuccess: function(files) { - $('error_div').set('html', ''); - if (files) { - // Update Trackers data - var i = 0; - files.each(function(file) { - if (i === 0) { - is_seed = file.is_seed; - } - var row = []; - row.length = 4; - row[0] = file.priority; - row[1] = escapeHtml(file.name); - row[2] = friendlyUnit(file.size, false); - row[3] = (file.progress * 100).round(1); - if (row[3] == 100.0 && file.progress < 1.0) - row[3] = 99.9; - row[4] = file.priority; - row[5] = friendlyUnit(file.size * (1.0 - file.progress)); - row[6] = friendlyPercentage(file.availability); - - fTable.insertRow(i, row); - ++i; - }.bind(this)); - // Set global CB state - if (allCBChecked()) { - setCBState("checked"); - } - else if (allCBUnchecked()) { - setCBState("unchecked"); - } - else { - setCBState("partial"); - } - } - else { - fTable.removeAllRows(); + var selectedFiles = torrentFilesTable.selectedRowsIds(); + + if (!files) { + torrentFilesTable.clear(); + return; } - clearTimeout(loadTorrentFilesDataTimer); - loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); + + var i = 0; + files.each(function(file) { + if (i === 0) + is_seed = file.is_seed; + + var row = { + rowId: i, + checked: (file.priority !== FilePriority.Ignored), + name: escapeHtml(file.name), + size: file.size, + progress: (file.progress * 100).round(1), + priority: normalizePriority(file.priority), + remaining: (file.size * (1.0 - file.progress)), + availability: file.availability + }; + + if ((row.progress === 100) && (file.progress < 1)) + row.progress = 99.9; + + ++i; + torrentFilesTable.updateRowData(row); + }.bind(this)); + + torrentFilesTable.updateTable(false); + torrentFilesTable.altRow(); + + if (selectedFiles.length > 0) + torrentFilesTable.reselectRows(selectedFiles); + + setGlobalCheckboxState(); } }).send(); }; @@ -352,5 +284,62 @@ var updateTorrentFilesData = function() { loadTorrentFilesData(); }; -var fTable = new filesDynTable(); -fTable.setup($('filesTable')); +var torrentFilesContextMenu = new ContextMenu({ + targets: '#torrentFilesTableDiv tr', + menu: 'torrentFilesMenu', + actions: { + FilePrioIgnore: function(element, ref) { + var selectedRows = torrentFilesTable.selectedRowsIds(); + if (selectedRows.length === 0) return; + + setFilePriority(selectedRows, FilePriority.Ignored); + }, + FilePrioNormal: function(element, ref) { + var selectedRows = torrentFilesTable.selectedRowsIds(); + if (selectedRows.length === 0) return; + + setFilePriority(selectedRows, FilePriority.Normal); + }, + FilePrioHigh: function(element, ref) { + var selectedRows = torrentFilesTable.selectedRowsIds(); + if (selectedRows.length === 0) return; + + setFilePriority(selectedRows, FilePriority.High); + }, + FilePrioMaximum: function(element, ref) { + var selectedRows = torrentFilesTable.selectedRowsIds(); + if (selectedRows.length === 0) return; + + setFilePriority(selectedRows, FilePriority.Maximum); + } + }, + offsets: { + x: -15, + y: 2 + }, + onShow: function() { + var selectedRows = torrentFilesTable.selectedRowsIds(); + + if (is_seed) + this.hideItem('FilePrio'); + else + this.showItem('FilePrio'); + } +}); + +torrentFilesTable.setup('torrentFilesTableDiv', 'torrentFilesTableFixedHeaderDiv', torrentFilesContextMenu); +// inject checkbox into table header +var tableHeaders = $$('#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th'); +if (tableHeaders.length > 0) { + var checkbox = new Element('input'); + checkbox.set('type', 'checkbox'); + checkbox.set('id', 'tristate_cb'); + checkbox.addEvent('click', switchCheckboxState); + + var checkboxTH = tableHeaders[0]; + checkbox.injectInside(checkboxTH); +} + +// default sort by name column +if (torrentFilesTable.getSortedColunn() === null) + torrentFilesTable.setSortedColumn('name');