From 1439bcc8644ebc5e32f8aff282d576d7ce8e40d6 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Sun, 11 Aug 2019 23:53:20 -0700 Subject: [PATCH] Move JavaScript code into explicit namespaces This cleans up the global namespace by explicitly exporting shared values. All html and JavaScript files have been converted to use explicit exports except for client.js and mocha-init.js --- src/webui/www/private/addpeers.html | 2 +- src/webui/www/private/download.html | 4 +- src/webui/www/private/newcategory.html | 14 +- src/webui/www/private/newtag.html | 4 +- src/webui/www/private/scripts/client.js | 83 +- src/webui/www/private/scripts/contextmenu.js | 942 ++--- src/webui/www/private/scripts/download.js | 175 +- src/webui/www/private/scripts/dynamicTable.js | 3519 +++++++++-------- src/webui/www/private/scripts/file-tree.js | 306 +- src/webui/www/private/scripts/filesystem.js | 69 +- src/webui/www/private/scripts/misc.js | 312 +- src/webui/www/private/scripts/mocha-init.js | 2 +- src/webui/www/private/scripts/preferences.js | 44 +- src/webui/www/private/scripts/progressbar.js | 219 +- src/webui/www/private/scripts/prop-files.js | 1171 +++--- src/webui/www/private/scripts/prop-general.js | 344 +- src/webui/www/private/scripts/prop-peers.js | 266 +- .../www/private/scripts/prop-trackers.js | 372 +- .../www/private/scripts/prop-webseeds.js | 202 +- src/webui/www/private/setlocation.html | 2 +- src/webui/www/private/shareratio.html | 4 +- src/webui/www/private/upload.html | 4 +- src/webui/www/private/views/about.html | 34 +- src/webui/www/private/views/aboutToolbar.html | 64 +- src/webui/www/private/views/filters.html | 145 +- .../private/views/installsearchplugin.html | 82 +- src/webui/www/private/views/preferences.html | 2027 +++++----- .../www/private/views/preferencesToolbar.html | 54 +- src/webui/www/private/views/properties.html | 8 +- src/webui/www/private/views/search.html | 1007 ++--- .../www/private/views/searchplugins.html | 283 +- src/webui/www/private/views/transferlist.html | 150 +- 32 files changed, 6140 insertions(+), 5774 deletions(-) diff --git a/src/webui/www/private/addpeers.html b/src/webui/www/private/addpeers.html index e90ac099c..93b774e5f 100644 --- a/src/webui/www/private/addpeers.html +++ b/src/webui/www/private/addpeers.html @@ -61,7 +61,7 @@

QBT_TR(List of peers to add (one IP per line):)QBT_TR[CONTEXT=PeersAdditionDialog]

- +
diff --git a/src/webui/www/private/download.html b/src/webui/www/private/download.html index 918a264db..5dfe84cc8 100644 --- a/src/webui/www/private/download.html +++ b/src/webui/www/private/download.html @@ -27,7 +27,7 @@ - @@ -63,7 +63,7 @@
- diff --git a/src/webui/www/private/newcategory.html b/src/webui/www/private/newcategory.html index b2cf42d88..8e2135ab2 100644 --- a/src/webui/www/private/newcategory.html +++ b/src/webui/www/private/newcategory.html @@ -30,18 +30,18 @@ }).activate(); window.addEvent('domready', function() { - const uriAction = safeTrim(new URI().getData('action')); - const uriHashes = safeTrim(new URI().getData('hashes')); - const uriCategoryName = safeTrim(new URI().getData('categoryName')); - const uriSavePath = safeTrim(new URI().getData('savePath')); + const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action')); + const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes')); + const uriCategoryName = window.qBittorrent.Misc.safeTrim(new URI().getData('categoryName')); + const uriSavePath = window.qBittorrent.Misc.safeTrim(new URI().getData('savePath')); if (uriAction === "edit") { if (!uriCategoryName) return false; $('categoryName').set('disabled', true); - $('categoryName').set('value', escapeHtml(uriCategoryName)); - $('savePath').set('value', escapeHtml(uriSavePath)); + $('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName)); + $('savePath').set('value', window.qBittorrent.Misc.escapeHtml(uriSavePath)); $('savePath').focus(); } else { @@ -90,7 +90,7 @@ }).send(); }, onError: function() { - alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + escapeHtml(categoryName)); + alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + window.qBittorrent.Misc.escapeHtml(categoryName)); } }).send(); break; diff --git a/src/webui/www/private/newtag.html b/src/webui/www/private/newtag.html index d1b7d4a3a..30ab09529 100644 --- a/src/webui/www/private/newtag.html +++ b/src/webui/www/private/newtag.html @@ -30,8 +30,8 @@ }).activate(); window.addEvent('domready', function() { - const uriAction = safeTrim(new URI().getData('action')); - const uriHashes = safeTrim(new URI().getData('hashes')); + const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action')); + const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes')); if (uriAction === 'create') $('legendText').innerText = 'QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]'; diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 87fca4150..826228738 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -24,21 +24,10 @@ 'use strict'; -this.torrentsTable = new TorrentsTable(); -const torrentTrackersTable = new TorrentTrackersTable(); -const torrentPeersTable = new TorrentPeersTable(); -const torrentFilesTable = new TorrentFilesTable(); -const searchResultsTable = new SearchResultsTable(); -const searchPluginsTable = new SearchPluginsTable(); +this.torrentsTable = new window.qBittorrent.DynamicTable.TorrentsTable(); let updatePropertiesPanel = function() {}; -let updateTorrentData = function() {}; -let updateTrackersData = function() {}; -let updateTorrentPeersData = function() {}; -let updateWebSeedsData = function() {}; -let updateTorrentFilesData = function() {}; - this.updateMainData = function() {}; let alternativeSpeedLimits = false; let queueing_enabled = true; @@ -365,12 +354,12 @@ window.addEvent('load', function() { const create_link = function(hash, text, count) { const html = '' + '' - + escapeHtml(text) + ' (' + count + ')' + ''; + + window.qBittorrent.Misc.escapeHtml(text) + ' (' + count + ')' + ''; const el = new Element('li', { id: hash, html: html }); - categoriesFilterContextMenu.addTarget(el); + window.qBittorrent.Filters.categoriesFilterContextMenu.addTarget(el); return el; }; @@ -422,12 +411,12 @@ window.addEvent('load', function() { const createLink = function(hash, text, count) { const html = '' + '' - + escapeHtml(text) + ' (' + count + ')' + ''; + + window.qBittorrent.Misc.escapeHtml(text) + ' (' + count + ')' + ''; const el = new Element('li', { id: hash, html: html }); - tagsFilterContextMenu.addTarget(el); + window.qBittorrent.Filters.tagsFilterContextMenu.addTarget(el); return el; }; @@ -579,11 +568,11 @@ window.addEvent('load', function() { updateFiltersList(); if (update_categories) { updateCategoryList(); - torrentsTableContextMenu.updateCategoriesSubMenu(category_list); + window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(category_list); } if (updateTags) { updateTagList(); - torrentsTableContextMenu.updateTagsSubMenu(tagList); + window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagList); } if (full_update) @@ -603,39 +592,39 @@ window.addEvent('load', function() { }; const processServerState = function() { - let transfer_info = friendlyUnit(serverState.dl_info_speed, true); + let transfer_info = window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_speed, true); if (serverState.dl_rate_limit > 0) - transfer_info += " [" + friendlyUnit(serverState.dl_rate_limit, true) + "]"; - transfer_info += " (" + friendlyUnit(serverState.dl_info_data, false) + ")"; + transfer_info += " [" + window.qBittorrent.Misc.friendlyUnit(serverState.dl_rate_limit, true) + "]"; + transfer_info += " (" + window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_data, false) + ")"; $("DlInfos").set('html', transfer_info); - transfer_info = friendlyUnit(serverState.up_info_speed, true); + transfer_info = window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true); if (serverState.up_rate_limit > 0) - transfer_info += " [" + friendlyUnit(serverState.up_rate_limit, true) + "]"; - transfer_info += " (" + friendlyUnit(serverState.up_info_data, false) + ")"; + transfer_info += " [" + window.qBittorrent.Misc.friendlyUnit(serverState.up_rate_limit, true) + "]"; + transfer_info += " (" + window.qBittorrent.Misc.friendlyUnit(serverState.up_info_data, false) + ")"; $("UpInfos").set('html', transfer_info); if (speedInTitle) { - document.title = "QBT_TR([D: %1, U: %2] qBittorrent %3)QBT_TR[CONTEXT=MainWindow]".replace("%1", friendlyUnit(serverState.dl_info_speed, true)).replace("%2", friendlyUnit(serverState.up_info_speed, true)).replace("%3", qbtVersion()); + document.title = "QBT_TR([D: %1, U: %2] qBittorrent %3)QBT_TR[CONTEXT=MainWindow]".replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_speed, true)).replace("%2", window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true)).replace("%3", qbtVersion()); document.title += " QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]"; } else document.title = ("qBittorrent " + qbtVersion() + " QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]"); - $('freeSpaceOnDisk').set('html', 'QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]'.replace("%1", friendlyUnit(serverState.free_space_on_disk))); + $('freeSpaceOnDisk').set('html', 'QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]'.replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.free_space_on_disk))); $('DHTNodes').set('html', 'QBT_TR(DHT: %1 nodes)QBT_TR[CONTEXT=StatusBar]'.replace("%1", serverState.dht_nodes)); // Statistics dialog if (document.getElementById("statisticspage")) { - $('AlltimeDL').set('html', friendlyUnit(serverState.alltime_dl, false)); - $('AlltimeUL').set('html', friendlyUnit(serverState.alltime_ul, false)); - $('TotalWastedSession').set('html', friendlyUnit(serverState.total_wasted_session, false)); + $('AlltimeDL').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.alltime_dl, false)); + $('AlltimeUL').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.alltime_ul, false)); + $('TotalWastedSession').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.total_wasted_session, false)); $('GlobalRatio').set('html', serverState.global_ratio); $('TotalPeerConnections').set('html', serverState.total_peer_connections); $('ReadCacheHits').set('html', serverState.read_cache_hits + "%"); - $('TotalBuffersSize').set('html', friendlyUnit(serverState.total_buffers_size, false)); + $('TotalBuffersSize').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.total_buffers_size, false)); $('WriteCacheOverload').set('html', serverState.write_cache_overload + "%"); $('ReadCacheOverload').set('html', serverState.read_cache_overload + "%"); $('QueuedIOJobs').set('html', serverState.queued_io_jobs); $('AverageTimeInQueue').set('html', serverState.average_time_queue + " ms"); - $('TotalQueuedSize').set('html', friendlyUnit(serverState.total_queued_size, false)); + $('TotalQueuedSize').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.total_queued_size, false)); } if (serverState.connection_status == "connected") @@ -790,7 +779,7 @@ window.addEvent('load', function() { const showSearchTab = function() { if (!searchTabInitialized) { - initSearchTab(); + window.qBittorrent.Search.init(); searchTabInitialized = true; } @@ -878,16 +867,26 @@ window.addEvent('load', function() { MochaUI.initializeTabs('propertiesTabs'); updatePropertiesPanel = function() { - if (!$('prop_general').hasClass('invisible')) - updateTorrentData(); - else if (!$('prop_trackers').hasClass('invisible')) - updateTrackersData(); - else if (!$('prop_peers').hasClass('invisible')) - updateTorrentPeersData(); - else if (!$('prop_webseeds').hasClass('invisible')) - updateWebSeedsData(); - else if (!$('prop_files').hasClass('invisible')) - updateTorrentFilesData(); + if (!$('prop_general').hasClass('invisible')) { + if (window.qBittorrent.PropGeneral !== undefined) + window.qBittorrent.PropGeneral.updateData(); + } + else if (!$('prop_trackers').hasClass('invisible')) { + if (window.qBittorrent.PropTrackers !== undefined) + window.qBittorrent.PropTrackers.updateData(); + } + else if (!$('prop_peers').hasClass('invisible')) { + if (window.qBittorrent.PropPeers !== undefined) + window.qBittorrent.PropPeers.updateData(); + } + else if (!$('prop_webseeds').hasClass('invisible')) { + if (window.qBittorrent.PropWebseeds !== undefined) + window.qBittorrent.PropWebseeds.updateData(); + } + else if (!$('prop_files').hasClass('invisible')) { + if (window.qBittorrent.PropFiles !== undefined) + window.qBittorrent.PropFiles.updateData(); + } }; $('PropGeneralLink').addEvent('click', function(e) { diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 310f520f8..0d16525f9 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -28,497 +28,515 @@ 'use strict'; -let lastShownContextMenu = null; -const ContextMenu = new Class({ - //implements - Implements: [Options, Events], - - //options - options: { - actions: {}, - menu: 'menu_id', - stopEvent: true, - targets: 'body', - offsets: { - x: 0, - y: 0 +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.ContextMenu = (function() { + const exports = function() { + return { + ContextMenu: ContextMenu, + TorrentsTableContextMenu: TorrentsTableContextMenu, + CategoriesFilterContextMenu: CategoriesFilterContextMenu, + TagsFilterContextMenu: TagsFilterContextMenu, + SearchPluginsTableContextMenu: SearchPluginsTableContextMenu + }; + }; + + let lastShownContextMenu = null; + const ContextMenu = new Class({ + //implements + Implements: [Options, Events], + + //options + options: { + actions: {}, + menu: 'menu_id', + stopEvent: true, + targets: 'body', + offsets: { + x: 0, + y: 0 + }, + onShow: $empty, + onHide: $empty, + onClick: $empty, + fadeSpeed: 200, + touchTimer: 600 }, - onShow: $empty, - onHide: $empty, - onClick: $empty, - fadeSpeed: 200, - touchTimer: 600 - }, - - //initialization - initialize: function(options) { - //set options - this.setOptions(options); - - //option diffs menu - this.menu = $(this.options.menu); - this.targets = $$(this.options.targets); - - //fx - this.fx = new Fx.Tween(this.menu, { - property: 'opacity', - duration: this.options.fadeSpeed, - onComplete: function() { - if (this.getStyle('opacity')) { - this.setStyle('visibility', 'visible'); - } - else { - this.setStyle('visibility', 'hidden'); - } - }.bind(this.menu) - }); - //hide and begin the listener - this.hide().startListener(); + //initialization + initialize: function(options) { + //set options + this.setOptions(options); + + //option diffs menu + this.menu = $(this.options.menu); + this.targets = $$(this.options.targets); + + //fx + this.fx = new Fx.Tween(this.menu, { + property: 'opacity', + duration: this.options.fadeSpeed, + onComplete: function() { + if (this.getStyle('opacity')) { + this.setStyle('visibility', 'visible'); + } + else { + this.setStyle('visibility', 'hidden'); + } + }.bind(this.menu) + }); - //hide the menu - this.menu.setStyles({ - 'position': 'absolute', - 'top': '-900000px', - 'display': 'block' - }); - }, - - adjustMenuPosition: function(e) { - this.updateMenuItems(); - - const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75; - - if (this.menu.hasClass('scrollableMenu')) - this.menu.setStyle('max-height', scrollableMenuMaxHeight); - - // draw the menu off-screen to know the menu dimensions - this.menu.setStyles({ - left: '-999em', - top: '-999em' - }); - - // position the menu - let xPosMenu = e.page.x + this.options.offsets.x; - let yPosMenu = e.page.y + this.options.offsets.y; - if (xPosMenu + this.menu.offsetWidth > document.documentElement.clientWidth) - xPosMenu -= this.menu.offsetWidth; - if (yPosMenu + this.menu.offsetHeight > document.documentElement.clientHeight) - yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight; - if (xPosMenu < 0) - xPosMenu = 0; - if (yPosMenu < 0) - yPosMenu = 0; - this.menu.setStyles({ - left: xPosMenu, - top: yPosMenu, - position: 'absolute', - 'z-index': '2000' - }); - - // position the sub-menu - const uls = this.menu.getElementsByTagName('ul'); - for (let i = 0; i < uls.length; ++i) { - const ul = uls[i]; - if (ul.hasClass('scrollableMenu')) - ul.setStyle('max-height', scrollableMenuMaxHeight); - const rectParent = ul.parentNode.getBoundingClientRect(); - const xPosOrigin = rectParent.left; - const yPosOrigin = rectParent.bottom; - let xPos = xPosOrigin + rectParent.width - 1; - let yPos = yPosOrigin - rectParent.height - 1; - if (xPos + ul.offsetWidth > document.documentElement.clientWidth) - xPos -= (ul.offsetWidth + rectParent.width - 2); - if (yPos + ul.offsetHeight > document.documentElement.clientHeight) - yPos = document.documentElement.clientHeight - ul.offsetHeight; - if (xPos < 0) - xPos = 0; - if (yPos < 0) - yPos = 0; - ul.setStyles({ - 'margin-left': xPos - xPosOrigin, - 'margin-top': yPos - yPosOrigin + //hide and begin the listener + this.hide().startListener(); + + //hide the menu + this.menu.setStyles({ + 'position': 'absolute', + 'top': '-900000px', + 'display': 'block' }); - } - }, - - setupEventListeners: function(elem) { - elem.addEvent('contextmenu', function(e) { - this.triggerMenu(e, elem); - }.bind(this)); - elem.addEvent('click', function(e) { - this.hide(); - }.bind(this)); - - elem.addEvent('touchstart', function(e) { - e.preventDefault(); - clearTimeout(this.touchstartTimer); - this.hide(); - - const touchstartEvent = e; - this.touchstartTimer = setTimeout(function() { - this.triggerMenu(touchstartEvent, elem); - }.bind(this), this.options.touchTimer); - }.bind(this)); - elem.addEvent('touchend', function(e) { - e.preventDefault(); - clearTimeout(this.touchstartTimer); - }.bind(this)); - }, - - addTarget: function(t) { - this.targets[this.targets.length] = t; - this.setupEventListeners(t); - }, - - triggerMenu: function(e, el) { - if (this.options.disabled) - return; - - //prevent default, if told to - if (this.options.stopEvent) { - e.stop(); - } - //record this as the trigger - this.options.element = $(el); - this.adjustMenuPosition(e); - //show the menu - this.show(); - }, - - //get things started - startListener: function() { - /* all elements */ - this.targets.each(function(el) { - this.setupEventListeners(el); - }.bind(this), this); - - /* menu items */ - this.menu.getElements('a').each(function(item) { - item.addEvent('click', function(e) { + }, + + adjustMenuPosition: function(e) { + this.updateMenuItems(); + + const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75; + + if (this.menu.hasClass('scrollableMenu')) + this.menu.setStyle('max-height', scrollableMenuMaxHeight); + + // draw the menu off-screen to know the menu dimensions + this.menu.setStyles({ + left: '-999em', + top: '-999em' + }); + + // position the menu + let xPosMenu = e.page.x + this.options.offsets.x; + let yPosMenu = e.page.y + this.options.offsets.y; + if (xPosMenu + this.menu.offsetWidth > document.documentElement.clientWidth) + xPosMenu -= this.menu.offsetWidth; + if (yPosMenu + this.menu.offsetHeight > document.documentElement.clientHeight) + yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight; + if (xPosMenu < 0) + xPosMenu = 0; + if (yPosMenu < 0) + yPosMenu = 0; + this.menu.setStyles({ + left: xPosMenu, + top: yPosMenu, + position: 'absolute', + 'z-index': '2000' + }); + + // position the sub-menu + const uls = this.menu.getElementsByTagName('ul'); + for (let i = 0; i < uls.length; ++i) { + const ul = uls[i]; + if (ul.hasClass('scrollableMenu')) + ul.setStyle('max-height', scrollableMenuMaxHeight); + const rectParent = ul.parentNode.getBoundingClientRect(); + const xPosOrigin = rectParent.left; + const yPosOrigin = rectParent.bottom; + let xPos = xPosOrigin + rectParent.width - 1; + let yPos = yPosOrigin - rectParent.height - 1; + if (xPos + ul.offsetWidth > document.documentElement.clientWidth) + xPos -= (ul.offsetWidth + rectParent.width - 2); + if (yPos + ul.offsetHeight > document.documentElement.clientHeight) + yPos = document.documentElement.clientHeight - ul.offsetHeight; + if (xPos < 0) + xPos = 0; + if (yPos < 0) + yPos = 0; + ul.setStyles({ + 'margin-left': xPos - xPosOrigin, + 'margin-top': yPos - yPosOrigin + }); + } + }, + + setupEventListeners: function(elem) { + elem.addEvent('contextmenu', function(e) { + this.triggerMenu(e, elem); + }.bind(this)); + elem.addEvent('click', function(e) { + this.hide(); + }.bind(this)); + + elem.addEvent('touchstart', function(e) { e.preventDefault(); - if (!item.hasClass('disabled')) { - this.execute(item.get('href').split('#')[1], $(this.options.element)); - this.fireEvent('click', [item, e]); - } + clearTimeout(this.touchstartTimer); + this.hide(); + + const touchstartEvent = e; + this.touchstartTimer = setTimeout(function() { + this.triggerMenu(touchstartEvent, elem); + }.bind(this), this.options.touchTimer); }.bind(this)); - }, this); - - //hide on body click - $(document.body).addEvent('click', function() { - this.hide(); - }.bind(this)); - }, - - updateMenuItems: function() {}, - - //show menu - show: function(trigger) { - if (lastShownContextMenu && lastShownContextMenu != this) - lastShownContextMenu.hide(); - this.fx.start(1); - this.fireEvent('show'); - this.shown = true; - lastShownContextMenu = this; - return this; - }, - - //hide the menu - hide: function(trigger) { - if (this.shown) { - this.fx.start(0); - //this.menu.fade('out'); - this.fireEvent('hide'); - this.shown = false; - } - return this; - }, - - setItemChecked: function(item, checked) { - this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity = - checked ? '1' : '0'; - return this; - }, - - getItemChecked: function(item) { - return '0' != this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity; - }, - - //hide an item - hideItem: function(item) { - this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible'); - return this; - }, - - //show an item - showItem: function(item) { - this.menu.getElement('a[href$=' + item + ']').parentNode.removeClass('invisible'); - return this; - }, - - //disable the entire menu - disable: function() { - this.options.disabled = true; - return this; - }, - - //enable the entire menu - enable: function() { - this.options.disabled = false; - return this; - }, - - //execute an action - execute: function(action, element) { - if (this.options.actions[action]) { - this.options.actions[action](element, this, action); - } - return this; - } -}); - -const TorrentsTableContextMenu = new Class({ - Extends: ContextMenu, - - updateMenuItems: function() { - let all_are_seq_dl = true; - let there_are_seq_dl = false; - let all_are_f_l_piece_prio = true; - let there_are_f_l_piece_prio = false; - let all_are_downloaded = true; - let all_are_paused = true; - let there_are_paused = false; - let all_are_force_start = true; - let there_are_force_start = false; - let all_are_super_seeding = true; - let all_are_auto_tmm = true; - let there_are_auto_tmm = false; - const tagsSelectionState = Object.clone(tagList); - - const h = torrentsTable.selectedRowsIds(); - h.each(function(item, index) { - const data = torrentsTable.rows.get(item).full_data; - - if (data['seq_dl'] !== true) - all_are_seq_dl = false; - else - there_are_seq_dl = true; + elem.addEvent('touchend', function(e) { + e.preventDefault(); + clearTimeout(this.touchstartTimer); + }.bind(this)); + }, - if (data['f_l_piece_prio'] !== true) - all_are_f_l_piece_prio = false; - else - there_are_f_l_piece_prio = true; + addTarget: function(t) { + this.targets[this.targets.length] = t; + this.setupEventListeners(t); + }, - if (data['progress'] != 1.0) // not downloaded - all_are_downloaded = false; - else if (data['super_seeding'] !== true) - all_are_super_seeding = false; + triggerMenu: function(e, el) { + if (this.options.disabled) + return; - if (data['state'] != 'pausedUP' && data['state'] != 'pausedDL') - all_are_paused = false; - else - there_are_paused = true; + //prevent default, if told to + if (this.options.stopEvent) { + e.stop(); + } + //record this as the trigger + this.options.element = $(el); + this.adjustMenuPosition(e); + //show the menu + this.show(); + }, - if (data['force_start'] !== true) - all_are_force_start = false; - else - there_are_force_start = true; + //get things started + startListener: function() { + /* all elements */ + this.targets.each(function(el) { + this.setupEventListeners(el); + }.bind(this), this); + + /* menu items */ + this.menu.getElements('a').each(function(item) { + item.addEvent('click', function(e) { + e.preventDefault(); + if (!item.hasClass('disabled')) { + this.execute(item.get('href').split('#')[1], $(this.options.element)); + this.fireEvent('click', [item, e]); + } + }.bind(this)); + }, this); + + //hide on body click + $(document.body).addEvent('click', function() { + this.hide(); + }.bind(this)); + }, - if (data['auto_tmm'] === true) - there_are_auto_tmm = true; - else - all_are_auto_tmm = false; - - const torrentTags = data['tags'].split(', '); - for (const key in tagsSelectionState) { - const tag = tagsSelectionState[key]; - const tagExists = torrentTags.contains(tag.name); - if ((tag.checked !== undefined) && (tag.checked != tagExists)) - tag.indeterminate = true; - if (tag.checked === undefined) - tag.checked = tagExists; - else - tag.checked = tag.checked && tagExists; + updateMenuItems: function() {}, + + //show menu + show: function(trigger) { + if (lastShownContextMenu && lastShownContextMenu != this) + lastShownContextMenu.hide(); + this.fx.start(1); + this.fireEvent('show'); + this.shown = true; + lastShownContextMenu = this; + return this; + }, + + //hide the menu + hide: function(trigger) { + if (this.shown) { + this.fx.start(0); + //this.menu.fade('out'); + this.fireEvent('hide'); + this.shown = false; } - }); + return this; + }, - let show_seq_dl = true; + setItemChecked: function(item, checked) { + this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity = + checked ? '1' : '0'; + return this; + }, - if (!all_are_seq_dl && there_are_seq_dl) - show_seq_dl = false; + getItemChecked: function(item) { + return '0' != this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity; + }, - let show_f_l_piece_prio = true; + //hide an item + hideItem: function(item) { + this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible'); + return this; + }, - if (!all_are_f_l_piece_prio && there_are_f_l_piece_prio) - show_f_l_piece_prio = false; + //show an item + showItem: function(item) { + this.menu.getElement('a[href$=' + item + ']').parentNode.removeClass('invisible'); + return this; + }, + + //disable the entire menu + disable: function() { + this.options.disabled = true; + return this; + }, - if (all_are_downloaded) { - this.hideItem('downloadLimit'); - this.menu.getElement('a[href$=uploadLimit]').parentNode.addClass('separator'); - this.hideItem('sequentialDownload'); - this.hideItem('firstLastPiecePrio'); - this.showItem('superSeeding'); - this.setItemChecked('superSeeding', all_are_super_seeding); + //enable the entire menu + enable: function() { + this.options.disabled = false; + return this; + }, + + //execute an action + execute: function(action, element) { + if (this.options.actions[action]) { + this.options.actions[action](element, this, action); + } + return this; } - else { - if (!show_seq_dl && show_f_l_piece_prio) - this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.addClass('separator'); - else - this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.removeClass('separator'); + }); + + const TorrentsTableContextMenu = new Class({ + Extends: ContextMenu, + + updateMenuItems: function() { + let all_are_seq_dl = true; + let there_are_seq_dl = false; + let all_are_f_l_piece_prio = true; + let there_are_f_l_piece_prio = false; + let all_are_downloaded = true; + let all_are_paused = true; + let there_are_paused = false; + let all_are_force_start = true; + let there_are_force_start = false; + let all_are_super_seeding = true; + let all_are_auto_tmm = true; + let there_are_auto_tmm = false; + const tagsSelectionState = Object.clone(tagList); + + const h = torrentsTable.selectedRowsIds(); + h.each(function(item, index) { + const data = torrentsTable.rows.get(item).full_data; + + if (data['seq_dl'] !== true) + all_are_seq_dl = false; + else + there_are_seq_dl = true; - if (show_seq_dl) - this.showItem('sequentialDownload'); - else - this.hideItem('sequentialDownload'); + if (data['f_l_piece_prio'] !== true) + all_are_f_l_piece_prio = false; + else + there_are_f_l_piece_prio = true; - if (show_f_l_piece_prio) - this.showItem('firstLastPiecePrio'); - else + if (data['progress'] != 1.0) // not downloaded + all_are_downloaded = false; + else if (data['super_seeding'] !== true) + all_are_super_seeding = false; + + if (data['state'] != 'pausedUP' && data['state'] != 'pausedDL') + all_are_paused = false; + else + there_are_paused = true; + + if (data['force_start'] !== true) + all_are_force_start = false; + else + there_are_force_start = true; + + if (data['auto_tmm'] === true) + there_are_auto_tmm = true; + else + all_are_auto_tmm = false; + + const torrentTags = data['tags'].split(', '); + for (const key in tagsSelectionState) { + const tag = tagsSelectionState[key]; + const tagExists = torrentTags.contains(tag.name); + if ((tag.checked !== undefined) && (tag.checked != tagExists)) + tag.indeterminate = true; + if (tag.checked === undefined) + tag.checked = tagExists; + else + tag.checked = tag.checked && tagExists; + } + }); + + let show_seq_dl = true; + + if (!all_are_seq_dl && there_are_seq_dl) + show_seq_dl = false; + + let show_f_l_piece_prio = true; + + if (!all_are_f_l_piece_prio && there_are_f_l_piece_prio) + show_f_l_piece_prio = false; + + if (all_are_downloaded) { + this.hideItem('downloadLimit'); + this.menu.getElement('a[href$=uploadLimit]').parentNode.addClass('separator'); + this.hideItem('sequentialDownload'); this.hideItem('firstLastPiecePrio'); + this.showItem('superSeeding'); + this.setItemChecked('superSeeding', all_are_super_seeding); + } + else { + if (!show_seq_dl && show_f_l_piece_prio) + this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.addClass('separator'); + else + this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.removeClass('separator'); - this.setItemChecked('sequentialDownload', all_are_seq_dl); - this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio); + if (show_seq_dl) + this.showItem('sequentialDownload'); + else + this.hideItem('sequentialDownload'); - this.showItem('downloadLimit'); - this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator'); - this.hideItem('superSeeding'); - } + if (show_f_l_piece_prio) + this.showItem('firstLastPiecePrio'); + else + this.hideItem('firstLastPiecePrio'); - this.showItem('start'); - this.showItem('pause'); - this.showItem('forceStart'); - if (all_are_paused) - this.hideItem('pause'); - else if (all_are_force_start) - this.hideItem('forceStart'); - else if (!there_are_paused && !there_are_force_start) - this.hideItem('start'); - - if (!all_are_auto_tmm && there_are_auto_tmm) { - this.hideItem('autoTorrentManagement'); - } - else { - this.showItem('autoTorrentManagement'); - this.setItemChecked('autoTorrentManagement', all_are_auto_tmm); - } + this.setItemChecked('sequentialDownload', all_are_seq_dl); + this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio); - const contextTagList = $('contextTagList'); - for (const tagHash in tagList) { - const checkbox = contextTagList.getElement('a[href=#Tag/' + tagHash + '] input[type=checkbox]'); - const checkboxState = tagsSelectionState[tagHash]; - checkbox.indeterminate = checkboxState.indeterminate; - checkbox.checked = checkboxState.checked; - } - }, - - updateCategoriesSubMenu: function(category_list) { - const categoryList = $('contextCategoryList'); - categoryList.empty(); - categoryList.appendChild(new Element('li', { - html: 'QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]' - })); - categoryList.appendChild(new Element('li', { - html: 'QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]' - })); - - const sortedCategories = []; - Object.each(category_list, function(category) { - sortedCategories.push(category.name); - }); - sortedCategories.sort(); - - let first = true; - Object.each(sortedCategories, function(categoryName) { - const categoryHash = genHash(categoryName); - const el = new Element('li', { - html: ' ' + escapeHtml(categoryName) + '' - }); - if (first) { - el.addClass('separator'); - first = false; + this.showItem('downloadLimit'); + this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator'); + this.hideItem('superSeeding'); } - categoryList.appendChild(el); - }); - }, - - updateTagsSubMenu: function(tagList) { - const contextTagList = $('contextTagList'); - while (contextTagList.firstChild !== null) - contextTagList.removeChild(contextTagList.firstChild); - - contextTagList.appendChild(new Element('li', { - html: '' - + 'QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]' - + ' QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]' - + '' - })); - contextTagList.appendChild(new Element('li', { - html: '' - + 'QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]' - + ' QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]' - + '' - })); - - const sortedTags = []; - for (const key in tagList) - sortedTags.push(tagList[key].name); - sortedTags.sort(); - - for (let i = 0; i < sortedTags.length; ++i) { - const tagName = sortedTags[i]; - const tagHash = genHash(tagName); - const el = new Element('li', { - html: '' - + ' ' + escapeHtml(tagName) - + '' + + this.showItem('start'); + this.showItem('pause'); + this.showItem('forceStart'); + if (all_are_paused) + this.hideItem('pause'); + else if (all_are_force_start) + this.hideItem('forceStart'); + else if (!there_are_paused && !there_are_force_start) + this.hideItem('start'); + + if (!all_are_auto_tmm && there_are_auto_tmm) { + this.hideItem('autoTorrentManagement'); + } + else { + this.showItem('autoTorrentManagement'); + this.setItemChecked('autoTorrentManagement', all_are_auto_tmm); + } + + const contextTagList = $('contextTagList'); + for (const tagHash in tagList) { + const checkbox = contextTagList.getElement('a[href=#Tag/' + tagHash + '] input[type=checkbox]'); + const checkboxState = tagsSelectionState[tagHash]; + checkbox.indeterminate = checkboxState.indeterminate; + checkbox.checked = checkboxState.checked; + } + }, + + updateCategoriesSubMenu: function(category_list) { + const categoryList = $('contextCategoryList'); + categoryList.empty(); + categoryList.appendChild(new Element('li', { + html: 'QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(New...)QBT_TR[CONTEXT=TransferListWidget]' + })); + categoryList.appendChild(new Element('li', { + html: 'QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget] QBT_TR(Reset)QBT_TR[CONTEXT=TransferListWidget]' + })); + + const sortedCategories = []; + Object.each(category_list, function(category) { + sortedCategories.push(category.name); }); - if (i === 0) - el.addClass('separator'); - contextTagList.appendChild(el); + sortedCategories.sort(); + + let first = true; + Object.each(sortedCategories, function(categoryName) { + const categoryHash = genHash(categoryName); + const el = new Element('li', { + html: ' ' + window.qBittorrent.Misc.escapeHtml(categoryName) + '' + }); + if (first) { + el.addClass('separator'); + first = false; + } + categoryList.appendChild(el); + }); + }, + + updateTagsSubMenu: function(tagList) { + const contextTagList = $('contextTagList'); + while (contextTagList.firstChild !== null) + contextTagList.removeChild(contextTagList.firstChild); + + contextTagList.appendChild(new Element('li', { + html: '' + + 'QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]' + + ' QBT_TR(Add...)QBT_TR[CONTEXT=TransferListWidget]' + + '' + })); + contextTagList.appendChild(new Element('li', { + html: '' + + 'QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]' + + ' QBT_TR(Remove All)QBT_TR[CONTEXT=TransferListWidget]' + + '' + })); + + const sortedTags = []; + for (const key in tagList) + sortedTags.push(tagList[key].name); + sortedTags.sort(); + + for (let i = 0; i < sortedTags.length; ++i) { + const tagName = sortedTags[i]; + const tagHash = genHash(tagName); + const el = new Element('li', { + html: '' + + ' ' + window.qBittorrent.Misc.escapeHtml(tagName) + + '' + }); + if (i === 0) + el.addClass('separator'); + contextTagList.appendChild(el); + } } - } -}); - -const CategoriesFilterContextMenu = new Class({ - Extends: ContextMenu, - updateMenuItems: function() { - const id = this.options.element.id; - if ((id != CATEGORIES_ALL) && (id != CATEGORIES_UNCATEGORIZED)) { - this.showItem('editCategory'); - this.showItem('deleteCategory'); + }); + + const CategoriesFilterContextMenu = new Class({ + Extends: ContextMenu, + updateMenuItems: function() { + const id = this.options.element.id; + if ((id != CATEGORIES_ALL) && (id != CATEGORIES_UNCATEGORIZED)) { + this.showItem('editCategory'); + this.showItem('deleteCategory'); + } + else { + this.hideItem('editCategory'); + this.hideItem('deleteCategory'); + } } - else { - this.hideItem('editCategory'); - this.hideItem('deleteCategory'); + }); + + const TagsFilterContextMenu = new Class({ + Extends: ContextMenu, + updateMenuItems: function() { + const id = this.options.element.id; + if ((id !== TAGS_ALL.toString()) && (id !== TAGS_UNTAGGED.toString())) + this.showItem('deleteTag'); + else + this.hideItem('deleteTag'); } - } -}); - -const TagsFilterContextMenu = new Class({ - Extends: ContextMenu, - updateMenuItems: function() { - const id = this.options.element.id; - if ((id !== TAGS_ALL.toString()) && (id !== TAGS_UNTAGGED.toString())) - this.showItem('deleteTag'); - else - this.hideItem('deleteTag'); - } -}); - -const SearchPluginsTableContextMenu = new Class({ - Extends: ContextMenu, - - updateMenuItems: function() { - const enabledColumnIndex = function(text) { - const columns = $("searchPluginsTableFixedHeaderRow").getChildren("th"); - for (let i = 0; i < columns.length; ++i) - if (columns[i].get("html") === "Enabled") - return i; - }; + }); + + const SearchPluginsTableContextMenu = new Class({ + Extends: ContextMenu, - this.showItem('Enabled'); - this.setItemChecked('Enabled', this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes"); + updateMenuItems: function() { + const enabledColumnIndex = function(text) { + const columns = $("searchPluginsTableFixedHeaderRow").getChildren("th"); + for (let i = 0; i < columns.length; ++i) + if (columns[i].get("html") === "Enabled") + return i; + }; + + this.showItem('Enabled'); + this.setItemChecked('Enabled', this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes"); + + this.showItem('Uninstall'); + } + }); - this.showItem('Uninstall'); - } -}); + return exports(); +})(); diff --git a/src/webui/www/private/scripts/download.js b/src/webui/www/private/scripts/download.js index 067105c4e..1fa8a7e0d 100644 --- a/src/webui/www/private/scripts/download.js +++ b/src/webui/www/private/scripts/download.js @@ -23,98 +23,113 @@ 'use strict'; -let categories = {}; -let defaultSavePath = ""; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -const getCategories = function() { - new Request.JSON({ - url: 'api/v2/torrents/categories', - noCache: true, - method: 'get', - onSuccess: function(data) { - if (data) { - categories = data; - for (const i in data) { - const category = data[i]; - const option = new Element("option"); - option.set('value', category.name); - option.set('html', category.name); - $('categorySelect').appendChild(option); +window.qBittorrent.Download = (function() { + const exports = function() { + return { + changeCategorySelect: changeCategorySelect, + changeTMM: changeTMM + }; + }; + + let categories = {}; + let defaultSavePath = ""; + + const getCategories = function() { + new Request.JSON({ + url: 'api/v2/torrents/categories', + noCache: true, + method: 'get', + onSuccess: function(data) { + if (data) { + categories = data; + for (const i in data) { + const category = data[i]; + const option = new Element("option"); + option.set('value', category.name); + option.set('html', category.name); + $('categorySelect').appendChild(option); + } } } - } - }).send(); -}; + }).send(); + }; -const getPreferences = function() { - new Request.JSON({ - url: 'api/v2/app/preferences', - method: 'get', - noCache: true, - onFailure: function() { - alert("Could not contact qBittorrent"); - }, - onSuccess: function(pref) { - if (!pref) - return; + const getPreferences = function() { + new Request.JSON({ + url: 'api/v2/app/preferences', + method: 'get', + noCache: true, + onFailure: function() { + alert("Could not contact qBittorrent"); + }, + onSuccess: function(pref) { + if (!pref) + return; - defaultSavePath = pref.save_path; - $('savepath').setProperty('value', defaultSavePath); - $('rootFolder').checked = pref.create_subfolder_enabled; - $('startTorrent').checked = !pref.start_paused_enabled; + defaultSavePath = pref.save_path; + $('savepath').setProperty('value', defaultSavePath); + $('rootFolder').checked = pref.create_subfolder_enabled; + $('startTorrent').checked = !pref.start_paused_enabled; - if (pref.auto_tmm_enabled == 1) { - $('autoTMM').selectedIndex = 1; - $('savepath').disabled = true; - } - else { - $('autoTMM').selectedIndex = 0; + if (pref.auto_tmm_enabled == 1) { + $('autoTMM').selectedIndex = 1; + $('savepath').disabled = true; + } + else { + $('autoTMM').selectedIndex = 0; + } } + }).send(); + }; + + const changeCategorySelect = function(item) { + if (item.value == "\\other") { + item.nextElementSibling.hidden = false; + item.nextElementSibling.value = ""; + item.nextElementSibling.select(); + + if ($('autoTMM').selectedIndex == 1) + $('savepath').value = defaultSavePath; } - }).send(); -}; + else { + item.nextElementSibling.hidden = true; + const text = item.options[item.selectedIndex].innerHTML; + item.nextElementSibling.value = text; -const changeCategorySelect = function(item) { - if (item.value == "\\other") { - item.nextElementSibling.hidden = false; - item.nextElementSibling.value = ""; - item.nextElementSibling.select(); + if ($('autoTMM').selectedIndex == 1) { + const categoryName = item.value; + const category = categories[categoryName]; + let savePath = defaultSavePath; + if (category !== undefined) + savePath = (category['savePath'] !== "") ? category['savePath'] : (defaultSavePath + categoryName); + $('savepath').value = savePath; + } + } + }; - if ($('autoTMM').selectedIndex == 1) - $('savepath').value = defaultSavePath; - } - else { - item.nextElementSibling.hidden = true; - const text = item.options[item.selectedIndex].innerHTML; - item.nextElementSibling.value = text; + const changeTMM = function(item) { + if (item.selectedIndex == 1) { + $('savepath').disabled = true; - if ($('autoTMM').selectedIndex == 1) { - const categoryName = item.value; + const categorySelect = $('categorySelect'); + const categoryName = categorySelect.options[categorySelect.selectedIndex].value; const category = categories[categoryName]; - let savePath = defaultSavePath; - if (category !== undefined) - savePath = (category['savePath'] !== "") ? category['savePath'] : (defaultSavePath + categoryName); - $('savepath').value = savePath; + $('savepath').value = (category === undefined) ? "" : category['savePath']; } - } -}; - -const changeTMM = function(item) { - if (item.selectedIndex == 1) { - $('savepath').disabled = true; + else { + $('savepath').disabled = false; + $('savepath').value = defaultSavePath; + } + }; - const categorySelect = $('categorySelect'); - const categoryName = categorySelect.options[categorySelect.selectedIndex].value; - const category = categories[categoryName]; - $('savepath').value = (category === undefined) ? "" : category['savePath']; - } - else { - $('savepath').disabled = false; - $('savepath').value = defaultSavePath; - } -}; + $(window).addEventListener("load", function() { + getPreferences(); + getCategories(); + }); -$(window).addEventListener("load", function() { - getPreferences(); - getCategories(); -}); + return exports(); +})(); diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 81d308a8c..d66f77de1 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -33,1956 +33,1975 @@ 'use strict'; -let DynamicTableHeaderContextMenuClass = null; -let ProgressColumnWidth = -1; - -const DynamicTable = new Class({ - - initialize: function() {}, - - setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu) { - this.dynamicTableDivId = dynamicTableDivId; - this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId; - this.fixedTableHeader = $(dynamicTableFixedHeaderDivId).getElements('tr')[0]; - this.hiddenTableHeader = $(dynamicTableDivId).getElements('tr')[0]; - this.tableBody = $(dynamicTableDivId).getElements('tbody')[0]; - this.rows = new Hash(); - this.selectedRows = []; - this.columns = []; - this.contextMenu = contextMenu; - this.sortedColumn = LocalPreferences.get('sorted_column_' + this.dynamicTableDivId, 0); - this.reverseSort = LocalPreferences.get('reverse_sort_' + this.dynamicTableDivId, '0'); - this.initColumns(); - this.loadColumnsOrder(); - this.updateTableHeaders(); - this.setupCommonEvents(); - this.setupHeaderEvents(); - this.setupHeaderMenu(); - this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === '1')); - }, - - setupCommonEvents: function() { - const scrollFn = function() { - $(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px'; - }.bind(this); - - $(this.dynamicTableDivId).addEvent('scroll', scrollFn); - - // if the table exists within a panel - if ($(this.dynamicTableDivId).getParent('.panel')) { - const resizeFn = function() { - const panel = $(this.dynamicTableDivId).getParent('.panel'); - let h = panel.getBoundingClientRect().height - $(this.dynamicTableFixedHeaderDivId).getBoundingClientRect().height; - $(this.dynamicTableDivId).style.height = h + 'px'; - - // Workaround due to inaccurate calculation of elements heights by browser - - let n = 2; - - while (panel.clientWidth != panel.offsetWidth && n > 0) { // is panel vertical scrollbar visible ? - --n; - h -= 0.5; - $(this.dynamicTableDivId).style.height = h + 'px'; - } - - this.lastPanelHeight = panel.getBoundingClientRect().height; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.DynamicTable = (function() { + const exports = function() { + return { + TorrentsTable: TorrentsTable, + TorrentPeersTable: TorrentPeersTable, + SearchResultsTable: SearchResultsTable, + SearchPluginsTable: SearchPluginsTable, + TorrentTrackersTable: TorrentTrackersTable, + TorrentFilesTable: TorrentFilesTable + }; + }; + + let DynamicTableHeaderContextMenuClass = null; + let ProgressColumnWidth = -1; + + const DynamicTable = new Class({ + + initialize: function() {}, + + setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu) { + this.dynamicTableDivId = dynamicTableDivId; + this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId; + this.fixedTableHeader = $(dynamicTableFixedHeaderDivId).getElements('tr')[0]; + this.hiddenTableHeader = $(dynamicTableDivId).getElements('tr')[0]; + this.tableBody = $(dynamicTableDivId).getElements('tbody')[0]; + this.rows = new Hash(); + this.selectedRows = []; + this.columns = []; + this.contextMenu = contextMenu; + this.sortedColumn = LocalPreferences.get('sorted_column_' + this.dynamicTableDivId, 0); + this.reverseSort = LocalPreferences.get('reverse_sort_' + this.dynamicTableDivId, '0'); + this.initColumns(); + this.loadColumnsOrder(); + this.updateTableHeaders(); + this.setupCommonEvents(); + this.setupHeaderEvents(); + this.setupHeaderMenu(); + this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === '1')); + }, + + setupCommonEvents: function() { + const scrollFn = function() { + $(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px'; }.bind(this); - $(this.dynamicTableDivId).getParent('.panel').addEvent('resize', resizeFn); + $(this.dynamicTableDivId).addEvent('scroll', scrollFn); + + // if the table exists within a panel + if ($(this.dynamicTableDivId).getParent('.panel')) { + const resizeFn = function() { + const panel = $(this.dynamicTableDivId).getParent('.panel'); + let h = panel.getBoundingClientRect().height - $(this.dynamicTableFixedHeaderDivId).getBoundingClientRect().height; + $(this.dynamicTableDivId).style.height = h + 'px'; + + // Workaround due to inaccurate calculation of elements heights by browser - this.lastPanelHeight = 0; + let n = 2; - // Workaround. Resize event is called not always (for example it isn't called when browser window changes it's size) + while (panel.clientWidth != panel.offsetWidth && n > 0) { // is panel vertical scrollbar visible ? + --n; + h -= 0.5; + $(this.dynamicTableDivId).style.height = h + 'px'; + } - const checkResizeFn = function() { - const panel = $(this.dynamicTableDivId).getParent('.panel'); - if (this.lastPanelHeight != panel.getBoundingClientRect().height) { this.lastPanelHeight = panel.getBoundingClientRect().height; - panel.fireEvent('resize'); - } - }.bind(this); + }.bind(this); - setInterval(checkResizeFn, 500); - } - }, + $(this.dynamicTableDivId).getParent('.panel').addEvent('resize', resizeFn); - setupHeaderEvents: function() { - this.currentHeaderAction = ''; - this.canResize = false; + this.lastPanelHeight = 0; - const resetElementBorderStyle = function(el, side) { - if (side === 'left' || side !== 'right') { - el.setStyle('border-left-style', ''); - el.setStyle('border-left-color', ''); - el.setStyle('border-left-width', ''); - } - if (side === 'right' || side !== 'left') { - el.setStyle('border-right-style', ''); - el.setStyle('border-right-color', ''); - el.setStyle('border-right-width', ''); + // Workaround. Resize event is called not always (for example it isn't called when browser window changes it's size) + + const checkResizeFn = function() { + const panel = $(this.dynamicTableDivId).getParent('.panel'); + if (this.lastPanelHeight != panel.getBoundingClientRect().height) { + this.lastPanelHeight = panel.getBoundingClientRect().height; + panel.fireEvent('resize'); + } + }.bind(this); + + setInterval(checkResizeFn, 500); } - }; + }, - const mouseMoveFn = function(e) { - const brect = e.target.getBoundingClientRect(); - const mouseXRelative = e.event.clientX - brect.left; - if (this.currentHeaderAction === '') { - if (brect.width - mouseXRelative < 5) { - this.resizeTh = e.target; - this.canResize = true; - e.target.getParent("tr").style.cursor = 'col-resize'; - } - else if ((mouseXRelative < 5) && e.target.getPrevious('[class=""]')) { - this.resizeTh = e.target.getPrevious('[class=""]'); - this.canResize = true; - e.target.getParent("tr").style.cursor = 'col-resize'; - } - else { - this.canResize = false; - e.target.getParent("tr").style.cursor = ''; + setupHeaderEvents: function() { + this.currentHeaderAction = ''; + this.canResize = false; + + const resetElementBorderStyle = function(el, side) { + if (side === 'left' || side !== 'right') { + el.setStyle('border-left-style', ''); + el.setStyle('border-left-color', ''); + el.setStyle('border-left-width', ''); } - } - if (this.currentHeaderAction === 'drag') { - const previousVisibleSibling = e.target.getPrevious('[class=""]'); - let borderChangeElement = previousVisibleSibling; - let changeBorderSide = 'right'; - - if (mouseXRelative > brect.width / 2) { - borderChangeElement = e.target; - this.dropSide = 'right'; + if (side === 'right' || side !== 'left') { + el.setStyle('border-right-style', ''); + el.setStyle('border-right-color', ''); + el.setStyle('border-right-width', ''); } - else { - this.dropSide = 'left'; + }; + + const mouseMoveFn = function(e) { + const brect = e.target.getBoundingClientRect(); + const mouseXRelative = e.event.clientX - brect.left; + if (this.currentHeaderAction === '') { + if (brect.width - mouseXRelative < 5) { + this.resizeTh = e.target; + this.canResize = true; + e.target.getParent("tr").style.cursor = 'col-resize'; + } + else if ((mouseXRelative < 5) && e.target.getPrevious('[class=""]')) { + this.resizeTh = e.target.getPrevious('[class=""]'); + this.canResize = true; + e.target.getParent("tr").style.cursor = 'col-resize'; + } + else { + this.canResize = false; + e.target.getParent("tr").style.cursor = ''; + } } + if (this.currentHeaderAction === 'drag') { + const previousVisibleSibling = e.target.getPrevious('[class=""]'); + let borderChangeElement = previousVisibleSibling; + let changeBorderSide = 'right'; + + if (mouseXRelative > brect.width / 2) { + borderChangeElement = e.target; + this.dropSide = 'right'; + } + else { + this.dropSide = 'left'; + } - e.target.getParent("tr").style.cursor = 'move'; + e.target.getParent("tr").style.cursor = 'move'; - if (!previousVisibleSibling) { // right most column - borderChangeElement = e.target; + if (!previousVisibleSibling) { // right most column + borderChangeElement = e.target; + + if (mouseXRelative <= brect.width / 2) + changeBorderSide = 'left'; + } - if (mouseXRelative <= brect.width / 2) - changeBorderSide = 'left'; + borderChangeElement.setStyle('border-' + changeBorderSide + '-style', 'solid'); + borderChangeElement.setStyle('border-' + changeBorderSide + '-color', '#e60'); + borderChangeElement.setStyle('border-' + changeBorderSide + '-width', 'initial'); + + resetElementBorderStyle(borderChangeElement, changeBorderSide === 'right' ? 'left' : 'right'); + + borderChangeElement.getSiblings('[class=""]').each(function(el) { + resetElementBorderStyle(el); + }); } + this.lastHoverTh = e.target; + this.lastClientX = e.event.clientX; + }.bind(this); - borderChangeElement.setStyle('border-' + changeBorderSide + '-style', 'solid'); - borderChangeElement.setStyle('border-' + changeBorderSide + '-color', '#e60'); - borderChangeElement.setStyle('border-' + changeBorderSide + '-width', 'initial'); + const mouseOutFn = function(e) { + resetElementBorderStyle(e.target); + }.bind(this); + + const onBeforeStart = function(el) { + this.clickedTh = el; + this.currentHeaderAction = 'start'; + this.dragMovement = false; + this.dragStartX = this.lastClientX; + }.bind(this); + + const onStart = function(el, event) { + if (this.canResize) { + this.currentHeaderAction = 'resize'; + this.startWidth = this.resizeTh.getStyle('width').toFloat(); + } + else { + this.currentHeaderAction = 'drag'; + el.setStyle('background-color', '#C1D5E7'); + } + }.bind(this); - resetElementBorderStyle(borderChangeElement, changeBorderSide === 'right' ? 'left' : 'right'); + const onDrag = function(el, event) { + if (this.currentHeaderAction === 'resize') { + let width = this.startWidth + (event.page.x - this.dragStartX); + if (width < 16) + width = 16; + this.columns[this.resizeTh.columnName].width = width; + this.updateColumn(this.resizeTh.columnName); + } + }.bind(this); - borderChangeElement.getSiblings('[class=""]').each(function(el) { + const onComplete = function(el, event) { + resetElementBorderStyle(this.lastHoverTh); + el.setStyle('background-color', ''); + if (this.currentHeaderAction === 'resize') + LocalPreferences.set('column_' + this.resizeTh.columnName + '_width_' + this.dynamicTableDivId, this.columns[this.resizeTh.columnName].width); + if ((this.currentHeaderAction === 'drag') && (el !== this.lastHoverTh)) { + this.saveColumnsOrder(); + const val = LocalPreferences.get('columns_order_' + this.dynamicTableDivId).split(','); + val.erase(el.columnName); + let pos = val.indexOf(this.lastHoverTh.columnName); + if (this.dropSide === 'right') ++pos; + val.splice(pos, 0, el.columnName); + LocalPreferences.set('columns_order_' + this.dynamicTableDivId, val.join(',')); + this.loadColumnsOrder(); + this.updateTableHeaders(); + while (this.tableBody.firstChild) + this.tableBody.removeChild(this.tableBody.firstChild); + this.updateTable(true); + } + if (this.currentHeaderAction === 'drag') { resetElementBorderStyle(el); + el.getSiblings('[class=""]').each(function(el) { + resetElementBorderStyle(el); + }); + } + this.currentHeaderAction = ''; + }.bind(this); + + const onCancel = function(el) { + this.currentHeaderAction = ''; + this.setSortedColumn(el.columnName); + }.bind(this); + + const ths = this.fixedTableHeader.getElements('th'); + + for (let i = 0; i < ths.length; ++i) { + const th = ths[i]; + th.addEvent('mousemove', mouseMoveFn); + th.addEvent('mouseout', mouseOutFn); + th.makeResizable({ + modifiers: { + x: '', + y: '' + }, + onBeforeStart: onBeforeStart, + onStart: onStart, + onDrag: onDrag, + onComplete: onComplete, + onCancel: onCancel }); } - this.lastHoverTh = e.target; - this.lastClientX = e.event.clientX; - }.bind(this); - - const mouseOutFn = function(e) { - resetElementBorderStyle(e.target); - }.bind(this); - - const onBeforeStart = function(el) { - this.clickedTh = el; - this.currentHeaderAction = 'start'; - this.dragMovement = false; - this.dragStartX = this.lastClientX; - }.bind(this); - - const onStart = function(el, event) { - if (this.canResize) { - this.currentHeaderAction = 'resize'; - this.startWidth = this.resizeTh.getStyle('width').toFloat(); - } - else { - this.currentHeaderAction = 'drag'; - el.setStyle('background-color', '#C1D5E7'); - } - }.bind(this); - - const onDrag = function(el, event) { - if (this.currentHeaderAction === 'resize') { - let width = this.startWidth + (event.page.x - this.dragStartX); - if (width < 16) - width = 16; - this.columns[this.resizeTh.columnName].width = width; - this.updateColumn(this.resizeTh.columnName); - } - }.bind(this); - - const onComplete = function(el, event) { - resetElementBorderStyle(this.lastHoverTh); - el.setStyle('background-color', ''); - if (this.currentHeaderAction === 'resize') - LocalPreferences.set('column_' + this.resizeTh.columnName + '_width_' + this.dynamicTableDivId, this.columns[this.resizeTh.columnName].width); - if ((this.currentHeaderAction === 'drag') && (el !== this.lastHoverTh)) { - this.saveColumnsOrder(); - const val = LocalPreferences.get('columns_order_' + this.dynamicTableDivId).split(','); - val.erase(el.columnName); - let pos = val.indexOf(this.lastHoverTh.columnName); - if (this.dropSide === 'right') ++pos; - val.splice(pos, 0, el.columnName); - LocalPreferences.set('columns_order_' + this.dynamicTableDivId, val.join(',')); - this.loadColumnsOrder(); - this.updateTableHeaders(); - while (this.tableBody.firstChild) - this.tableBody.removeChild(this.tableBody.firstChild); - this.updateTable(true); - } - if (this.currentHeaderAction === 'drag') { - resetElementBorderStyle(el); - el.getSiblings('[class=""]').each(function(el) { - resetElementBorderStyle(el); + }, + + setupDynamicTableHeaderContextMenuClass: function() { + if (!DynamicTableHeaderContextMenuClass) { + DynamicTableHeaderContextMenuClass = new Class({ + Extends: window.qBittorrent.ContextMenu.ContextMenu, + updateMenuItems: function() { + for (let i = 0; i < this.dynamicTable.columns.length; ++i) { + if (this.dynamicTable.columns[i].caption === '') + continue; + if (this.dynamicTable.columns[i].visible !== '0') + this.setItemChecked(this.dynamicTable.columns[i].name, true); + else + this.setItemChecked(this.dynamicTable.columns[i].name, false); + } + } }); } - this.currentHeaderAction = ''; - }.bind(this); + }, - const onCancel = function(el) { - this.currentHeaderAction = ''; - this.setSortedColumn(el.columnName); - }.bind(this); - - const ths = this.fixedTableHeader.getElements('th'); - - for (let i = 0; i < ths.length; ++i) { - const th = ths[i]; - th.addEvent('mousemove', mouseMoveFn); - th.addEvent('mouseout', mouseOutFn); - th.makeResizable({ - modifiers: { - x: '', - y: '' - }, - onBeforeStart: onBeforeStart, - onStart: onStart, - onDrag: onDrag, - onComplete: onComplete, - onCancel: onCancel - }); - } - }, - - setupDynamicTableHeaderContextMenuClass: function() { - if (!DynamicTableHeaderContextMenuClass) { - DynamicTableHeaderContextMenuClass = new Class({ - Extends: ContextMenu, - updateMenuItems: function() { - for (let i = 0; i < this.dynamicTable.columns.length; ++i) { - if (this.dynamicTable.columns[i].caption === '') - continue; - if (this.dynamicTable.columns[i].visible !== '0') - this.setItemChecked(this.dynamicTable.columns[i].name, true); - else - this.setItemChecked(this.dynamicTable.columns[i].name, false); - } - } + showColumn: function(columnName, show) { + this.columns[columnName].visible = show ? '1' : '0'; + LocalPreferences.set('column_' + columnName + '_visible_' + this.dynamicTableDivId, show ? '1' : '0'); + this.updateColumn(columnName); + }, + + setupHeaderMenu: function() { + this.setupDynamicTableHeaderContextMenuClass(); + + const menuId = this.dynamicTableDivId + '_headerMenu'; + + const ul = new Element('ul', { + id: menuId, + class: 'contextMenu scrollableMenu' }); - } - }, - showColumn: function(columnName, show) { - this.columns[columnName].visible = show ? '1' : '0'; - LocalPreferences.set('column_' + columnName + '_visible_' + this.dynamicTableDivId, show ? '1' : '0'); - this.updateColumn(columnName); - }, + const createLi = function(columnName, text) { + const html = '' + window.qBittorrent.Misc.escapeHtml(text) + ''; + return new Element('li', { + html: html + }); + }; - setupHeaderMenu: function() { - this.setupDynamicTableHeaderContextMenuClass(); + const actions = {}; - const menuId = this.dynamicTableDivId + '_headerMenu'; + const onMenuItemClicked = function(element, ref, action) { + this.showColumn(action, this.columns[action].visible === '0'); + }.bind(this); - const ul = new Element('ul', { - id: menuId, - class: 'contextMenu scrollableMenu' - }); + for (let i = 0; i < this.columns.length; ++i) { + const text = this.columns[i].caption; + if (text === '') + continue; + ul.appendChild(createLi(this.columns[i].name, text)); + actions[this.columns[i].name] = onMenuItemClicked; + } - const createLi = function(columnName, text) { - const html = '' + escapeHtml(text) + ''; - return new Element('li', { - html: html + ul.inject(document.body); + + this.headerContextMenu = new DynamicTableHeaderContextMenuClass({ + targets: '#' + this.dynamicTableFixedHeaderDivId + ' tr', + actions: actions, + menu: menuId, + offsets: { + x: -15, + y: 2 + } }); - }; - const actions = {}; + this.headerContextMenu.dynamicTable = this; + }, + + initColumns: function() {}, + + newColumn: function(name, style, caption, defaultWidth, defaultVisible) { + const column = {}; + column['name'] = name; + column['title'] = name; + column['visible'] = LocalPreferences.get('column_' + name + '_visible_' + this.dynamicTableDivId, defaultVisible ? '1' : '0'); + column['force_hide'] = false; + column['caption'] = caption; + column['style'] = style; + column['width'] = LocalPreferences.get('column_' + name + '_width_' + this.dynamicTableDivId, defaultWidth); + 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) { + const value = this.getRowValue(row) + td.innerHTML = value; + td.title = value; + }; + column['onResize'] = null; + this.columns.push(column); + this.columns[name] = column; + + this.hiddenTableHeader.appendChild(new Element('th')); + this.fixedTableHeader.appendChild(new Element('th')); + }, + + loadColumnsOrder: function() { + const columnsOrder = []; + const val = LocalPreferences.get('columns_order_' + this.dynamicTableDivId); + if (val === null || val === undefined) return; + val.split(',').forEach(function(v) { + if ((v in this.columns) && (!columnsOrder.contains(v))) + columnsOrder.push(v); + }.bind(this)); - const onMenuItemClicked = function(element, ref, action) { - this.showColumn(action, this.columns[action].visible === '0'); - }.bind(this); + for (let i = 0; i < this.columns.length; ++i) + if (!columnsOrder.contains(this.columns[i].name)) + columnsOrder.push(this.columns[i].name); - for (let i = 0; i < this.columns.length; ++i) { - const text = this.columns[i].caption; - if (text === '') - continue; - ul.appendChild(createLi(this.columns[i].name, text)); - actions[this.columns[i].name] = onMenuItemClicked; - } + for (let i = 0; i < this.columns.length; ++i) + this.columns[i] = this.columns[columnsOrder[i]]; + }, - ul.inject(document.body); - - this.headerContextMenu = new DynamicTableHeaderContextMenuClass({ - targets: '#' + this.dynamicTableFixedHeaderDivId + ' tr', - actions: actions, - menu: menuId, - offsets: { - x: -15, - y: 2 + saveColumnsOrder: function() { + let val = ''; + for (let i = 0; i < this.columns.length; ++i) { + if (i > 0) + val += ','; + val += this.columns[i].name; } - }); - - this.headerContextMenu.dynamicTable = this; - }, - - initColumns: function() {}, - - newColumn: function(name, style, caption, defaultWidth, defaultVisible) { - const column = {}; - column['name'] = name; - column['title'] = name; - column['visible'] = LocalPreferences.get('column_' + name + '_visible_' + this.dynamicTableDivId, defaultVisible ? '1' : '0'); - column['force_hide'] = false; - column['caption'] = caption; - column['style'] = style; - column['width'] = LocalPreferences.get('column_' + name + '_width_' + this.dynamicTableDivId, defaultWidth); - 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) { - const value = this.getRowValue(row) - td.innerHTML = value; - td.title = value; - }; - column['onResize'] = null; - this.columns.push(column); - this.columns[name] = column; - - this.hiddenTableHeader.appendChild(new Element('th')); - this.fixedTableHeader.appendChild(new Element('th')); - }, - - loadColumnsOrder: function() { - const columnsOrder = []; - const val = LocalPreferences.get('columns_order_' + this.dynamicTableDivId); - 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 (let i = 0; i < this.columns.length; ++i) - if (!columnsOrder.contains(this.columns[i].name)) - columnsOrder.push(this.columns[i].name); - - for (let i = 0; i < this.columns.length; ++i) - this.columns[i] = this.columns[columnsOrder[i]]; - }, - - saveColumnsOrder: function() { - let val = ''; - for (let i = 0; i < this.columns.length; ++i) { - if (i > 0) - val += ','; - val += this.columns[i].name; - } - LocalPreferences.set('columns_order_' + this.dynamicTableDivId, val); - }, - - updateTableHeaders: function() { - this.updateHeader(this.hiddenTableHeader); - this.updateHeader(this.fixedTableHeader); - }, - - updateHeader: function(header) { - const ths = header.getElements('th'); - - for (let i = 0; i < ths.length; ++i) { - const th = ths[i]; - th._this = this; - th.setAttribute('title', this.columns[i].caption); - th.innerHTML = this.columns[i].caption; - th.setAttribute('style', 'width: ' + this.columns[i].width + 'px;' + this.columns[i].style); - th.columnName = this.columns[i].name; - th.addClass('column_' + th.columnName); - if ((this.columns[i].visible == '0') || this.columns[i].force_hide) - th.addClass('invisible'); - else - th.removeClass('invisible'); - } - }, - - getColumnPos: function(columnName) { - for (let i = 0; i < this.columns.length; ++i) - if (this.columns[i].name == columnName) - return i; - return -1; - }, - - updateColumn: function(columnName) { - const pos = this.getColumnPos(columnName); - const visible = ((this.columns[pos].visible != '0') && !this.columns[pos].force_hide); - const ths = this.hiddenTableHeader.getElements('th'); - const fths = this.fixedTableHeader.getElements('th'); - const trs = this.tableBody.getElements('tr'); - const style = 'width: ' + this.columns[pos].width + 'px;' + this.columns[pos].style; - - ths[pos].setAttribute('style', style); - fths[pos].setAttribute('style', style); - - if (visible) { - ths[pos].removeClass('invisible'); - fths[pos].removeClass('invisible'); - for (let i = 0; i < trs.length; ++i) - trs[i].getElements('td')[pos].removeClass('invisible'); - } - else { - ths[pos].addClass('invisible'); - fths[pos].addClass('invisible'); - for (let j = 0; j < trs.length; ++j) - trs[j].getElements('td')[pos].addClass('invisible'); - } - if (this.columns[pos].onResize !== null) { - this.columns[pos].onResize(columnName); - } - }, - - getSortedColumn: function() { - return LocalPreferences.get('sorted_column_' + this.dynamicTableDivId); - }, - - setSortedColumn: function(column) { - if (column != this.sortedColumn) { - const oldColumn = this.sortedColumn; - this.sortedColumn = column; - this.reverseSort = '0'; - this.setSortedColumnIcon(column, oldColumn, false); - } - else { - // Toggle sort order - this.reverseSort = this.reverseSort === '0' ? '1' : '0'; - this.setSortedColumnIcon(column, null, (this.reverseSort === '1')); - } - LocalPreferences.set('sorted_column_' + this.dynamicTableDivId, column); - LocalPreferences.set('reverse_sort_' + this.dynamicTableDivId, this.reverseSort); - this.updateTable(false); - }, - - setSortedColumnIcon: function(newColumn, oldColumn, isReverse) { - const getCol = function(headerDivId, colName) { - const colElem = $$("#" + headerDivId + " .column_" + colName); - if (colElem.length == 1) - return colElem[0]; - return null; - }; + LocalPreferences.set('columns_order_' + this.dynamicTableDivId, val); + }, + + updateTableHeaders: function() { + this.updateHeader(this.hiddenTableHeader); + this.updateHeader(this.fixedTableHeader); + }, + + updateHeader: function(header) { + const ths = header.getElements('th'); + + for (let i = 0; i < ths.length; ++i) { + const th = ths[i]; + th._this = this; + th.setAttribute('title', this.columns[i].caption); + th.innerHTML = this.columns[i].caption; + th.setAttribute('style', 'width: ' + this.columns[i].width + 'px;' + this.columns[i].style); + th.columnName = this.columns[i].name; + th.addClass('column_' + th.columnName); + if ((this.columns[i].visible == '0') || this.columns[i].force_hide) + th.addClass('invisible'); + else + th.removeClass('invisible'); + } + }, - const colElem = getCol(this.dynamicTableFixedHeaderDivId, newColumn); - if (colElem !== null) { - colElem.addClass('sorted'); - if (isReverse) - colElem.addClass('reverse'); - else - colElem.removeClass('reverse'); - } - const oldColElem = getCol(this.dynamicTableFixedHeaderDivId, oldColumn); - if (oldColElem !== null) { - oldColElem.removeClass('sorted'); - oldColElem.removeClass('reverse'); - } - }, - - getSelectedRowId: function() { - if (this.selectedRows.length > 0) - return this.selectedRows[0]; - return ''; - }, - - isRowSelected: function(rowId) { - return this.selectedRows.contains(rowId); - }, - - altRow: function() { - if (!MUI.ieLegacySupport) - return; - - const trs = this.tableBody.getElements('tr'); - trs.each(function(el, i) { - if (i % 2) { - el.addClass('alt'); + getColumnPos: function(columnName) { + for (let i = 0; i < this.columns.length; ++i) + if (this.columns[i].name == columnName) + return i; + return -1; + }, + + updateColumn: function(columnName) { + const pos = this.getColumnPos(columnName); + const visible = ((this.columns[pos].visible != '0') && !this.columns[pos].force_hide); + const ths = this.hiddenTableHeader.getElements('th'); + const fths = this.fixedTableHeader.getElements('th'); + const trs = this.tableBody.getElements('tr'); + const style = 'width: ' + this.columns[pos].width + 'px;' + this.columns[pos].style; + + ths[pos].setAttribute('style', style); + fths[pos].setAttribute('style', style); + + if (visible) { + ths[pos].removeClass('invisible'); + fths[pos].removeClass('invisible'); + for (let i = 0; i < trs.length; ++i) + trs[i].getElements('td')[pos].removeClass('invisible'); } else { - el.removeClass('alt'); + ths[pos].addClass('invisible'); + fths[pos].addClass('invisible'); + for (let j = 0; j < trs.length; ++j) + trs[j].getElements('td')[pos].addClass('invisible'); } - }.bind(this)); - }, - - selectAll: function() { - this.deselectAll(); - - const trs = this.tableBody.getElements('tr'); - for (let i = 0; i < trs.length; ++i) { - const tr = trs[i]; - this.selectedRows.push(tr.rowId); - if (!tr.hasClass('selected')) - tr.addClass('selected'); - } - }, - - deselectAll: function() { - this.selectedRows.empty(); - }, - - selectRow: function(rowId) { - this.selectedRows.push(rowId); - this.setRowClass(); - this.onSelectedRowChanged(); - }, - - deselectRow: function(rowId) { - this.selectedRows.erase(rowId); - this.setRowClass(); - this.onSelectedRowChanged(); - }, - - selectRows: function(rowId1, rowId2) { - this.deselectAll(); - if (rowId1 === rowId2) { - this.selectRow(rowId1); - return; - } + if (this.columns[pos].onResize !== null) { + this.columns[pos].onResize(columnName); + } + }, + + getSortedColumn: function() { + return LocalPreferences.get('sorted_column_' + this.dynamicTableDivId); + }, + + setSortedColumn: function(column) { + if (column != this.sortedColumn) { + const oldColumn = this.sortedColumn; + this.sortedColumn = column; + this.reverseSort = '0'; + this.setSortedColumnIcon(column, oldColumn, false); + } + else { + // Toggle sort order + this.reverseSort = this.reverseSort === '0' ? '1' : '0'; + this.setSortedColumnIcon(column, null, (this.reverseSort === '1')); + } + LocalPreferences.set('sorted_column_' + this.dynamicTableDivId, column); + LocalPreferences.set('reverse_sort_' + this.dynamicTableDivId, this.reverseSort); + this.updateTable(false); + }, + + setSortedColumnIcon: function(newColumn, oldColumn, isReverse) { + const getCol = function(headerDivId, colName) { + const colElem = $$("#" + headerDivId + " .column_" + colName); + if (colElem.length == 1) + return colElem[0]; + return null; + }; - let select = false; - const that = this; - this.tableBody.getElements('tr').each(function(tr) { - if ((tr.rowId == rowId1) || (tr.rowId == rowId2)) { - select = !select; - that.selectedRows.push(tr.rowId); + const colElem = getCol(this.dynamicTableFixedHeaderDivId, newColumn); + if (colElem !== null) { + colElem.addClass('sorted'); + if (isReverse) + colElem.addClass('reverse'); + else + colElem.removeClass('reverse'); } - else if (select) { - that.selectedRows.push(tr.rowId); + const oldColElem = getCol(this.dynamicTableFixedHeaderDivId, oldColumn); + if (oldColElem !== null) { + oldColElem.removeClass('sorted'); + oldColElem.removeClass('reverse'); } - }); - this.setRowClass(); - this.onSelectedRowChanged(); - }, - - reselectRows: function(rowIds) { - this.deselectAll(); - this.selectedRows = rowIds.slice(); - this.tableBody.getElements('tr').each(function(tr) { - if (rowIds.indexOf(tr.rowId) > -1) - tr.addClass('selected'); - }); - }, - - setRowClass: function() { - const that = this; - this.tableBody.getElements('tr').each(function(tr) { - if (that.isRowSelected(tr.rowId)) - tr.addClass('selected'); - else - tr.removeClass('selected'); - }); - }, + }, - onSelectedRowChanged: function() {}, + getSelectedRowId: function() { + if (this.selectedRows.length > 0) + return this.selectedRows[0]; + return ''; + }, - updateRowData: function(data) { - const rowId = data['rowId']; - let row; + isRowSelected: function(rowId) { + return this.selectedRows.contains(rowId); + }, - if (!this.rows.has(rowId)) { - row = {}; - this.rows.set(rowId, row); - row['full_data'] = {}; - row['rowId'] = rowId; - } - else - row = this.rows.get(rowId); + altRow: function() { + if (!MUI.ieLegacySupport) + return; - row['data'] = data; + const trs = this.tableBody.getElements('tr'); + trs.each(function(el, i) { + if (i % 2) { + el.addClass('alt'); + } + else { + el.removeClass('alt'); + } + }.bind(this)); + }, - for (const x in data) - row['full_data'][x] = data[x]; - }, + selectAll: function() { + this.deselectAll(); - getFilteredAndSortedRows: function() { - const filteredRows = []; + const trs = this.tableBody.getElements('tr'); + for (let i = 0; i < trs.length; ++i) { + const tr = trs[i]; + this.selectedRows.push(tr.rowId); + if (!tr.hasClass('selected')) + tr.addClass('selected'); + } + }, + + deselectAll: function() { + this.selectedRows.empty(); + }, + + selectRow: function(rowId) { + this.selectedRows.push(rowId); + this.setRowClass(); + this.onSelectedRowChanged(); + }, + + deselectRow: function(rowId) { + this.selectedRows.erase(rowId); + this.setRowClass(); + this.onSelectedRowChanged(); + }, + + selectRows: function(rowId1, rowId2) { + this.deselectAll(); + if (rowId1 === rowId2) { + this.selectRow(rowId1); + return; + } - const rows = this.rows.getValues(); + let select = false; + const that = this; + this.tableBody.getElements('tr').each(function(tr) { + if ((tr.rowId == rowId1) || (tr.rowId == rowId2)) { + select = !select; + that.selectedRows.push(tr.rowId); + } + else if (select) { + that.selectedRows.push(tr.rowId); + } + }); + this.setRowClass(); + this.onSelectedRowChanged(); + }, + + reselectRows: function(rowIds) { + this.deselectAll(); + this.selectedRows = rowIds.slice(); + this.tableBody.getElements('tr').each(function(tr) { + if (rowIds.indexOf(tr.rowId) > -1) + tr.addClass('selected'); + }); + }, - for (let i = 0; i < rows.length; ++i) { - filteredRows.push(rows[i]); - filteredRows[rows[i].rowId] = rows[i]; - } + setRowClass: function() { + const that = this; + this.tableBody.getElements('tr').each(function(tr) { + if (that.isRowSelected(tr.rowId)) + tr.addClass('selected'); + else + tr.removeClass('selected'); + }); + }, - filteredRows.sort(function(row1, row2) { - const column = this.columns[this.sortedColumn]; - const res = column.compareRows(row1, row2); - if (this.reverseSort === '0') - return res; + onSelectedRowChanged: function() {}, + + updateRowData: function(data) { + const rowId = data['rowId']; + let row; + + if (!this.rows.has(rowId)) { + row = {}; + this.rows.set(rowId, row); + row['full_data'] = {}; + row['rowId'] = rowId; + } else - return -res; - }.bind(this)); - return filteredRows; - }, - - getTrByRowId: function(rowId) { - const trs = this.tableBody.getElements('tr'); - for (let i = 0; i < trs.length; ++i) - if (trs[i].rowId == rowId) - return trs[i]; - return null; - }, - - updateTable: function(fullUpdate) { - if (fullUpdate === undefined) - fullUpdate = false; - - const rows = this.getFilteredAndSortedRows(); - - for (let i = 0; i < this.selectedRows.length; ++i) - if (!(this.selectedRows[i] in rows)) { - this.selectedRows.splice(i, 1); - --i; + row = this.rows.get(rowId); + + row['data'] = data; + + for (const x in data) + row['full_data'][x] = data[x]; + }, + + getFilteredAndSortedRows: function() { + const filteredRows = []; + + const rows = this.rows.getValues(); + + for (let i = 0; i < rows.length; ++i) { + filteredRows.push(rows[i]); + filteredRows[rows[i].rowId] = rows[i]; } - const trs = this.tableBody.getElements('tr'); + filteredRows.sort(function(row1, row2) { + const column = this.columns[this.sortedColumn]; + const res = column.compareRows(row1, row2); + if (this.reverseSort === '0') + return res; + else + return -res; + }.bind(this)); + return filteredRows; + }, - for (let rowPos = 0; rowPos < rows.length; ++rowPos) { - const rowId = rows[rowPos]['rowId']; - let tr_found = false; - for (let j = rowPos; j < trs.length; ++j) - if (trs[j]['rowId'] == rowId) { - tr_found = true; - if (rowPos == j) - break; - trs[j].inject(trs[rowPos], 'before'); - const tmpTr = trs[j]; - trs.splice(j, 1); - trs.splice(rowPos, 0, tmpTr); - break; + getTrByRowId: function(rowId) { + const trs = this.tableBody.getElements('tr'); + for (let i = 0; i < trs.length; ++i) + if (trs[i].rowId == rowId) + return trs[i]; + return null; + }, + + updateTable: function(fullUpdate) { + if (fullUpdate === undefined) + fullUpdate = false; + + const rows = this.getFilteredAndSortedRows(); + + for (let i = 0; i < this.selectedRows.length; ++i) + if (!(this.selectedRows[i] in rows)) { + this.selectedRows.splice(i, 1); + --i; } - if (tr_found) // row already exists in the table - this.updateRow(trs[rowPos], fullUpdate); - else { // else create a new row in the table - const tr = new Element('tr'); - - tr['rowId'] = rows[rowPos]['rowId']; - - tr._this = this; - tr.addEvent('contextmenu', function(e) { - if (!this._this.isRowSelected(this.rowId)) { - this._this.deselectAll(); - this._this.selectRow(this.rowId); + + const trs = this.tableBody.getElements('tr'); + + for (let rowPos = 0; rowPos < rows.length; ++rowPos) { + const rowId = rows[rowPos]['rowId']; + let tr_found = false; + for (let j = rowPos; j < trs.length; ++j) + if (trs[j]['rowId'] == rowId) { + tr_found = true; + if (rowPos == j) + break; + trs[j].inject(trs[rowPos], 'before'); + const tmpTr = trs[j]; + trs.splice(j, 1); + trs.splice(rowPos, 0, tmpTr); + break; } - return true; - }); - tr.addEvent('click', function(e) { - e.stop(); - if (e.control || e.meta) { - // CTRL/CMD ⌘ key was pressed - if (this._this.isRowSelected(this.rowId)) - this._this.deselectRow(this.rowId); - else + if (tr_found) // row already exists in the table + this.updateRow(trs[rowPos], fullUpdate); + else { // else create a new row in the table + const tr = new Element('tr'); + + tr['rowId'] = rows[rowPos]['rowId']; + + tr._this = this; + tr.addEvent('contextmenu', function(e) { + if (!this._this.isRowSelected(this.rowId)) { + this._this.deselectAll(); + this._this.selectRow(this.rowId); + } + return true; + }); + tr.addEvent('click', function(e) { + e.stop(); + if (e.control || e.meta) { + // CTRL/CMD ⌘ key was pressed + if (this._this.isRowSelected(this.rowId)) + this._this.deselectRow(this.rowId); + else + this._this.selectRow(this.rowId); + } + else if (e.shift && (this._this.selectedRows.length == 1)) { + // Shift key was pressed + this._this.selectRows(this._this.getSelectedRowId(), this.rowId); + } + else { + // Simple selection + this._this.deselectAll(); + this._this.selectRow(this.rowId); + } + return false; + }); + tr.addEvent('touchstart', function(e) { + if (!this._this.isRowSelected(this.rowId)) { + this._this.deselectAll(); this._this.selectRow(this.rowId); + } + return false; + }); + + this.setupTr(tr); + + for (let k = 0; k < this.columns.length; ++k) { + const td = new Element('td'); + if ((this.columns[k].visible == '0') || this.columns[k].force_hide) + td.addClass('invisible'); + td.injectInside(tr); } - else if (e.shift && (this._this.selectedRows.length == 1)) { - // Shift key was pressed - this._this.selectRows(this._this.getSelectedRowId(), this.rowId); + + // Insert + if (rowPos >= trs.length) { + tr.inject(this.tableBody); + trs.push(tr); } else { - // Simple selection - this._this.deselectAll(); - this._this.selectRow(this.rowId); + tr.inject(trs[rowPos], 'before'); + trs.splice(rowPos, 0, tr); } - return false; - }); - tr.addEvent('touchstart', function(e) { - if (!this._this.isRowSelected(this.rowId)) { - this._this.deselectAll(); - this._this.selectRow(this.rowId); + + // Update context menu + if (this.contextMenu) + this.contextMenu.addTarget(tr); + + this.updateRow(tr, true); + } + } + + let rowPos = rows.length; + + while ((rowPos < trs.length) && (trs.length > 0)) { + trs[trs.length - 1].dispose(); + trs.pop(); + } + }, + + setupTr: function(tr) {}, + + updateRow: function(tr, fullUpdate) { + const row = this.rows.get(tr.rowId); + const data = row[fullUpdate ? 'full_data' : 'data']; + + const tds = tr.getElements('td'); + for (let i = 0; i < this.columns.length; ++i) { + if (data.hasOwnProperty(this.columns[i].dataProperties[0])) + this.columns[i].updateTd(tds[i], row); + } + row['data'] = {}; + }, + + removeRow: function(rowId) { + this.selectedRows.erase(rowId); + const tr = this.getTrByRowId(rowId); + if (tr !== null) { + tr.dispose(); + this.rows.erase(rowId); + return true; + } + return false; + }, + + clear: function() { + this.deselectAll(); + this.rows.empty(); + const trs = this.tableBody.getElements('tr'); + while (trs.length > 0) { + trs[trs.length - 1].dispose(); + trs.pop(); + } + }, + + selectedRowsIds: function() { + return this.selectedRows.slice(); + }, + + getRowIds: function() { + return this.rows.getKeys(); + }, + }); + + const TorrentsTable = new Class({ + Extends: DynamicTable, + + initColumns: function() { + this.newColumn('priority', '', '#', 30, true); + this.newColumn('state_icon', 'cursor: default', '', 22, true); + this.newColumn('name', '', 'QBT_TR(Name)QBT_TR[CONTEXT=TransferListModel]', 200, true); + this.newColumn('size', '', 'QBT_TR(Size)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('total_size', '', 'QBT_TR(Total Size)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('progress', '', 'QBT_TR(Done)QBT_TR[CONTEXT=TransferListModel]', 85, true); + this.newColumn('status', '', 'QBT_TR(Status)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('num_seeds', '', 'QBT_TR(Seeds)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('num_leechs', '', 'QBT_TR(Peers)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('dlspeed', '', 'QBT_TR(Down Speed)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('upspeed', '', 'QBT_TR(Up Speed)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('eta', '', 'QBT_TR(ETA)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('ratio', '', 'QBT_TR(Ratio)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('category', '', 'QBT_TR(Category)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('tags', '', 'QBT_TR(Tags)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('added_on', '', 'QBT_TR(Added On)QBT_TR[CONTEXT=TransferListModel]', 100, true); + this.newColumn('completion_on', '', 'QBT_TR(Completed On)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('tracker', '', 'QBT_TR(Tracker)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('dl_limit', '', 'QBT_TR(Down Limit)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('up_limit', '', 'QBT_TR(Up Limit)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('downloaded', '', 'QBT_TR(Downloaded)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('uploaded', '', 'QBT_TR(Uploaded)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('downloaded_session', '', 'QBT_TR(Session Download)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('uploaded_session', '', 'QBT_TR(Session Upload)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('amount_left', '', 'QBT_TR(Remaining)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('time_active', '', 'QBT_TR(Time Active)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('save_path', '', 'QBT_TR(Save path)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('completed', '', 'QBT_TR(Completed)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('max_ratio', '', 'QBT_TR(Ratio Limit)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('seen_complete', '', 'QBT_TR(Last Seen Complete)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('last_activity', '', 'QBT_TR(Last Activity)QBT_TR[CONTEXT=TransferListModel]', 100, false); + this.newColumn('availability', '', 'QBT_TR(Availability)QBT_TR[CONTEXT=TransferListModel]', 100, false); + + 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(); + }, + + initColumnsFunctions: function() { + + // state_icon + this.columns['state_icon'].updateTd = function(td, row) { + let state = this.getRowValue(row); + // normalize states + switch (state) { + case "forcedDL": + case "metaDL": + state = "downloading"; + break; + case "allocating": + state = "stalledDL"; + break; + case "forcedUP": + state = "uploading"; + break; + case "pausedDL": + state = "paused"; + break; + case "pausedUP": + state = "completed"; + break; + case "queuedDL": + case "queuedUP": + state = "queued"; + break; + case "checkingDL": + case "checkingUP": + case "queuedForChecking": + case "checkingResumeData": + case "moving": + state = "checking"; + break; + case "unknown": + case "missingFiles": + state = "error"; + break; + default: + break; // do nothing + } + + const img_path = 'images/skin/' + state + '.svg'; + + if (td.getChildren('img').length > 0) { + const img = td.getChildren('img')[0]; + if (img.src.indexOf(img_path) < 0) { + img.set('src', img_path); + img.set('title', state); } - return false; - }); + } + else { + td.adopt(new Element('img', { + 'src': img_path, + 'class': 'stateIcon', + 'title': state + })); + } + }; - this.setupTr(tr); + // status + this.columns['status'].updateTd = function(td, row) { + const state = this.getRowValue(row); + if (!state) return; - for (let k = 0; k < this.columns.length; ++k) { - const td = new Element('td'); - if ((this.columns[k].visible == '0') || this.columns[k].force_hide) - td.addClass('invisible'); - td.injectInside(tr); + let status; + switch (state) { + case "downloading": + status = "QBT_TR(Downloading)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "stalledDL": + status = "QBT_TR(Stalled)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "metaDL": + status = "QBT_TR(Downloading metadata)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "forcedDL": + status = "QBT_TR([F] Downloading)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "allocating": + status = "QBT_TR(Allocating)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "uploading": + case "stalledUP": + status = "QBT_TR(Seeding)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "forcedUP": + status = "QBT_TR([F] Seeding)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "queuedDL": + case "queuedUP": + status = "QBT_TR(Queued)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "checkingDL": + case "checkingUP": + status = "QBT_TR(Checking)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "queuedForChecking": + status = "QBT_TR(Queued for checking)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "checkingResumeData": + status = "QBT_TR(Checking resume data)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "pausedDL": + status = "QBT_TR(Paused)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "pausedUP": + status = "QBT_TR(Completed)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "moving": + status = "QBT_TR(Moving)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "missingFiles": + status = "QBT_TR(Missing Files)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + case "error": + status = "QBT_TR(Errored)QBT_TR[CONTEXT=TransferListDelegate]"; + break; + default: + status = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; } - // Insert - if (rowPos >= trs.length) { - tr.inject(this.tableBody); - trs.push(tr); + td.set('html', status); + td.set('title', status); + }; + + // priority + this.columns['priority'].updateTd = function(td, row) { + const queuePos = this.getRowValue(row); + const formattedQueuePos = (queuePos < 1) ? '*' : queuePos; + td.set('html', formattedQueuePos); + td.set('title', formattedQueuePos); + }; + + this.columns['priority'].compareRows = function(row1, row2) { + let row1_val = this.getRowValue(row1); + let 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; + }; + + // name, category, tags + this.columns['name'].updateTd = function(td, row) { + const name = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row)) + td.set('html', name); + td.set('title', name); + }; + this.columns['category'].updateTd = this.columns['name'].updateTd; + this.columns['tags'].updateTd = this.columns['name'].updateTd; + + this.columns['name'].compareRows = function(row1, row2) { + const row1Val = this.getRowValue(row1); + const row2Val = this.getRowValue(row2); + return row1Val.localeCompare(row2Val, undefined, {numeric: true, sensitivity: 'base'}); + }; + this.columns['category'].compareRows = this.columns['name'].compareRows; + this.columns['tags'].compareRows = this.columns['name'].compareRows; + + // size + this.columns['size'].updateTd = function(td, row) { + const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false); + td.set('html', size); + td.set('title', size); + }; + + // progress + this.columns['progress'].updateTd = function(td, row) { + const progress = this.getRowValue(row); + let progressFormated = (progress * 100).round(1); + if (progressFormated == 100.0 && progress != 1.0) + progressFormated = 99.9; + + if (td.getChildren('div').length > 0) { + const div = td.getChildren('div')[0]; + if (td.resized) { + td.resized = false; + div.setWidth(ProgressColumnWidth - 5); + } + if (div.getValue() != progressFormated) + div.setValue(progressFormated); } else { - tr.inject(trs[rowPos], 'before'); - trs.splice(rowPos, 0, tr); + if (ProgressColumnWidth < 0) + ProgressColumnWidth = td.offsetWidth; + td.adopt(new window.qBittorrent.ProgressBar.ProgressBar(progressFormated.toFloat(), { + 'width': ProgressColumnWidth - 5 + })); + td.resized = false; } + }; - // Update context menu - if (this.contextMenu) - this.contextMenu.addTarget(tr); + this.columns['progress'].onResize = function(columnName) { + const pos = this.getColumnPos(columnName); + const trs = this.tableBody.getElements('tr'); + ProgressColumnWidth = -1; + for (let i = 0; i < trs.length; ++i) { + const td = trs[i].getElements('td')[pos]; + if (ProgressColumnWidth < 0) + ProgressColumnWidth = td.offsetWidth; + td.resized = true; + this.columns[columnName].updateTd(td, this.rows.get(trs[i].rowId)); + } + }.bind(this); - this.updateRow(tr, true); - } - } + // num_seeds + this.columns['num_seeds'].updateTd = function(td, row) { + const num_seeds = this.getRowValue(row, 0); + const num_complete = this.getRowValue(row, 1); + let html = num_seeds; + if (num_complete != -1) + html += ' (' + num_complete + ')'; + td.set('html', html); + td.set('title', html); + }; + this.columns['num_seeds'].compareRows = function(row1, row2) { + const num_seeds1 = this.getRowValue(row1, 0); + const num_complete1 = this.getRowValue(row1, 1); - let rowPos = rows.length; + const num_seeds2 = this.getRowValue(row2, 0); + const num_complete2 = this.getRowValue(row2, 1); - while ((rowPos < trs.length) && (trs.length > 0)) { - trs[trs.length - 1].dispose(); - trs.pop(); - } - }, + 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; + }; - setupTr: function(tr) {}, + // num_leechs + this.columns['num_leechs'].updateTd = this.columns['num_seeds'].updateTd; + this.columns['num_leechs'].compareRows = this.columns['num_seeds'].compareRows; - updateRow: function(tr, fullUpdate) { - const row = this.rows.get(tr.rowId); - const data = row[fullUpdate ? 'full_data' : 'data']; + // dlspeed + this.columns['dlspeed'].updateTd = function(td, row) { + const speed = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), true); + td.set('html', speed); + td.set('title', speed); + }; - const tds = tr.getElements('td'); - for (let i = 0; i < this.columns.length; ++i) { - if (data.hasOwnProperty(this.columns[i].dataProperties[0])) - this.columns[i].updateTd(tds[i], row); - } - row['data'] = {}; - }, - - removeRow: function(rowId) { - this.selectedRows.erase(rowId); - const tr = this.getTrByRowId(rowId); - if (tr !== null) { - tr.dispose(); - this.rows.erase(rowId); - return true; - } - return false; - }, - - clear: function() { - this.deselectAll(); - this.rows.empty(); - const trs = this.tableBody.getElements('tr'); - while (trs.length > 0) { - trs[trs.length - 1].dispose(); - trs.pop(); - } - }, - - selectedRowsIds: function() { - return this.selectedRows.slice(); - }, - - getRowIds: function() { - return this.rows.getKeys(); - }, -}); - -const TorrentsTable = new Class({ - Extends: DynamicTable, - - initColumns: function() { - this.newColumn('priority', '', '#', 30, true); - this.newColumn('state_icon', 'cursor: default', '', 22, true); - this.newColumn('name', '', 'QBT_TR(Name)QBT_TR[CONTEXT=TransferListModel]', 200, true); - this.newColumn('size', '', 'QBT_TR(Size)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('total_size', '', 'QBT_TR(Total Size)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('progress', '', 'QBT_TR(Done)QBT_TR[CONTEXT=TransferListModel]', 85, true); - this.newColumn('status', '', 'QBT_TR(Status)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('num_seeds', '', 'QBT_TR(Seeds)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('num_leechs', '', 'QBT_TR(Peers)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('dlspeed', '', 'QBT_TR(Down Speed)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('upspeed', '', 'QBT_TR(Up Speed)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('eta', '', 'QBT_TR(ETA)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('ratio', '', 'QBT_TR(Ratio)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('category', '', 'QBT_TR(Category)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('tags', '', 'QBT_TR(Tags)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('added_on', '', 'QBT_TR(Added On)QBT_TR[CONTEXT=TransferListModel]', 100, true); - this.newColumn('completion_on', '', 'QBT_TR(Completed On)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('tracker', '', 'QBT_TR(Tracker)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('dl_limit', '', 'QBT_TR(Down Limit)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('up_limit', '', 'QBT_TR(Up Limit)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('downloaded', '', 'QBT_TR(Downloaded)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('uploaded', '', 'QBT_TR(Uploaded)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('downloaded_session', '', 'QBT_TR(Session Download)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('uploaded_session', '', 'QBT_TR(Session Upload)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('amount_left', '', 'QBT_TR(Remaining)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('time_active', '', 'QBT_TR(Time Active)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('save_path', '', 'QBT_TR(Save path)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('completed', '', 'QBT_TR(Completed)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('max_ratio', '', 'QBT_TR(Ratio Limit)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('seen_complete', '', 'QBT_TR(Last Seen Complete)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('last_activity', '', 'QBT_TR(Last Activity)QBT_TR[CONTEXT=TransferListModel]', 100, false); - this.newColumn('availability', '', 'QBT_TR(Availability)QBT_TR[CONTEXT=TransferListModel]', 100, false); - - 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(); - }, - - initColumnsFunctions: function() { - - // state_icon - this.columns['state_icon'].updateTd = function(td, row) { - let state = this.getRowValue(row); - // normalize states - switch (state) { - case "forcedDL": - case "metaDL": - state = "downloading"; - break; - case "allocating": - state = "stalledDL"; - break; - case "forcedUP": - state = "uploading"; - break; - case "pausedDL": - state = "paused"; - break; - case "pausedUP": - state = "completed"; - break; - case "queuedDL": - case "queuedUP": - state = "queued"; - break; - case "checkingDL": - case "checkingUP": - case "queuedForChecking": - case "checkingResumeData": - case "moving": - state = "checking"; - break; - case "unknown": - case "missingFiles": - state = "error"; - break; - default: - break; // do nothing - } + // upspeed + this.columns['upspeed'].updateTd = this.columns['dlspeed'].updateTd; - const img_path = 'images/skin/' + state + '.svg'; + // eta + this.columns['eta'].updateTd = function(td, row) { + const eta = window.qBittorrent.Misc.friendlyDuration(this.getRowValue(row)); + td.set('html', eta); + td.set('title', eta); + }; - if (td.getChildren('img').length > 0) { - const img = td.getChildren('img')[0]; - if (img.src.indexOf(img_path) < 0) { - img.set('src', img_path); - img.set('title', state); + // ratio + this.columns['ratio'].updateTd = function(td, row) { + const ratio = this.getRowValue(row); + const string = (ratio === -1) ? '∞' : window.qBittorrent.Misc.toFixedPointString(ratio, 2); + td.set('html', string); + td.set('title', string); + }; + + // added on + this.columns['added_on'].updateTd = function(td, row) { + const date = new Date(this.getRowValue(row) * 1000).toLocaleString(); + td.set('html', date); + td.set('title', date); + }; + + // completion_on + this.columns['completion_on'].updateTd = function(td, row) { + const val = this.getRowValue(row); + if ((val === 0xffffffff) || (val < 0)) { + td.set('html', ''); + td.set('title', ''); } - } - else { - td.adopt(new Element('img', { - 'src': img_path, - 'class': 'stateIcon', - 'title': state - })); - } - }; + else { + const date = new Date(this.getRowValue(row) * 1000).toLocaleString(); + td.set('html', date); + td.set('title', date); + } + }; + + // seen_complete + this.columns['seen_complete'].updateTd = this.columns['completion_on'].updateTd; + + // dl_limit, up_limit + this.columns['dl_limit'].updateTd = function(td, row) { + const speed = this.getRowValue(row); + if (speed === 0) { + td.set('html', '∞'); + td.set('title', '∞'); + } + else { + const formattedSpeed = window.qBittorrent.Misc.friendlyUnit(speed, true); + td.set('html', formattedSpeed); + td.set('title', formattedSpeed); + } + }; + + this.columns['up_limit'].updateTd = this.columns['dl_limit'].updateTd; + + // downloaded, uploaded, downloaded_session, uploaded_session, amount_left, completed, total_size + this.columns['downloaded'].updateTd = this.columns['size'].updateTd; + this.columns['uploaded'].updateTd = this.columns['size'].updateTd; + this.columns['downloaded_session'].updateTd = this.columns['size'].updateTd; + this.columns['uploaded_session'].updateTd = this.columns['size'].updateTd; + this.columns['amount_left'].updateTd = this.columns['size'].updateTd; + this.columns['amount_left'].updateTd = this.columns['size'].updateTd; + this.columns['completed'].updateTd = this.columns['size'].updateTd; + this.columns['total_size'].updateTd = this.columns['size'].updateTd; + + // save_path, tracker + this.columns['save_path'].updateTd = this.columns['name'].updateTd; + this.columns['tracker'].updateTd = this.columns['name'].updateTd; + + // max_ratio + this.columns['max_ratio'].updateTd = this.columns['ratio'].updateTd; + + // last_activity + this.columns['last_activity'].updateTd = function(td, row) { + const val = this.getRowValue(row); + if (val < 1) { + td.set('html', '∞'); + td.set('title', '∞'); + } + else { + const formattedVal = 'QBT_TR(%1 ago)QBT_TR[CONTEXT=TransferListDelegate]'.replace('%1', window.qBittorrent.Misc.friendlyDuration((new Date()) / 1000 - val)); + td.set('html', formattedVal); + td.set('title', formattedVal); + } + }; + + // time active + this.columns['time_active'].updateTd = function(td, row) { + const time = window.qBittorrent.Misc.friendlyDuration(this.getRowValue(row)); + td.set('html', time); + td.set('title', time); + }; + + // availability + this.columns['availability'].updateTd = function(td, row) { + const value = window.qBittorrent.Misc.toFixedPointString(this.getRowValue(row), 3); + td.set('html', value); + td.set('title', value); + }; + }, - // status - this.columns['status'].updateTd = function(td, row) { - const state = this.getRowValue(row); - if (!state) return; + applyFilter: function(row, filterName, categoryHash, tagHash, filterTerms) { + const state = row['full_data'].state; + const name = row['full_data'].name.toLowerCase(); + let inactive = false; + let r; - let status; - switch (state) { - case "downloading": - status = "QBT_TR(Downloading)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "stalledDL": - status = "QBT_TR(Stalled)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "metaDL": - status = "QBT_TR(Downloading metadata)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "forcedDL": - status = "QBT_TR([F] Downloading)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "allocating": - status = "QBT_TR(Allocating)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "uploading": - case "stalledUP": - status = "QBT_TR(Seeding)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "forcedUP": - status = "QBT_TR([F] Seeding)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "queuedDL": - case "queuedUP": - status = "QBT_TR(Queued)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "checkingDL": - case "checkingUP": - status = "QBT_TR(Checking)QBT_TR[CONTEXT=TransferListDelegate]"; - break; - case "queuedForChecking": - status = "QBT_TR(Queued for checking)QBT_TR[CONTEXT=TransferListDelegate]"; + switch (filterName) { + case 'downloading': + if (state != 'downloading' && !~state.indexOf('DL')) + return false; break; - case "checkingResumeData": - status = "QBT_TR(Checking resume data)QBT_TR[CONTEXT=TransferListDelegate]"; + case 'seeding': + if (state != 'uploading' && state != 'forcedUP' && state != 'stalledUP' && state != 'queuedUP' && state != 'checkingUP') + return false; break; - case "pausedDL": - status = "QBT_TR(Paused)QBT_TR[CONTEXT=TransferListDelegate]"; + case 'completed': + if (state != 'uploading' && !~state.indexOf('UP')) + return false; break; - case "pausedUP": - status = "QBT_TR(Completed)QBT_TR[CONTEXT=TransferListDelegate]"; + case 'paused': + if (!~state.indexOf('paused')) + return false; break; - case "moving": - status = "QBT_TR(Moving)QBT_TR[CONTEXT=TransferListDelegate]"; + case 'resumed': + if (~state.indexOf('paused')) + return false; break; - case "missingFiles": - status = "QBT_TR(Missing Files)QBT_TR[CONTEXT=TransferListDelegate]"; + case 'inactive': + inactive = true; + // fallthrough + case 'active': + if (state == 'stalledDL') + r = (row['full_data'].upspeed > 0); + else + r = state == 'metaDL' || state == 'downloading' || state == 'forcedDL' || state == 'uploading' || state == 'forcedUP'; + if (r == inactive) + return false; break; - case "error": - status = "QBT_TR(Errored)QBT_TR[CONTEXT=TransferListDelegate]"; + case 'errored': + if (state != 'error' && state != "unknown" && state != "missingFiles") + return false; break; - default: - status = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; } - td.set('html', status); - td.set('title', status); - }; - - // priority - this.columns['priority'].updateTd = function(td, row) { - const queuePos = this.getRowValue(row); - const formattedQueuePos = (queuePos < 1) ? '*' : queuePos; - td.set('html', formattedQueuePos); - td.set('title', formattedQueuePos); - }; - - this.columns['priority'].compareRows = function(row1, row2) { - let row1_val = this.getRowValue(row1); - let 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; - }; - - // name, category, tags - this.columns['name'].updateTd = function(td, row) { - const name = escapeHtml(this.getRowValue(row)) - td.set('html', name); - td.set('title', name); - }; - this.columns['category'].updateTd = this.columns['name'].updateTd; - this.columns['tags'].updateTd = this.columns['name'].updateTd; - - this.columns['name'].compareRows = function(row1, row2) { - const row1Val = this.getRowValue(row1); - const row2Val = this.getRowValue(row2); - return row1Val.localeCompare(row2Val, undefined, {numeric: true, sensitivity: 'base'}); - }; - this.columns['category'].compareRows = this.columns['name'].compareRows; - this.columns['tags'].compareRows = this.columns['name'].compareRows; - - // size - this.columns['size'].updateTd = function(td, row) { - const size = friendlyUnit(this.getRowValue(row), false); - td.set('html', size); - td.set('title', size); - }; - - // progress - this.columns['progress'].updateTd = function(td, row) { - const progress = this.getRowValue(row); - let progressFormated = (progress * 100).round(1); - if (progressFormated == 100.0 && progress != 1.0) - progressFormated = 99.9; - - if (td.getChildren('div').length > 0) { - const div = td.getChildren('div')[0]; - if (td.resized) { - td.resized = false; - div.setWidth(ProgressColumnWidth - 5); + const categoryHashInt = parseInt(categoryHash); + if (!isNaN(categoryHashInt)) { + switch (categoryHashInt) { + case CATEGORIES_ALL: + break; // do nothing + case CATEGORIES_UNCATEGORIZED: + if (row['full_data'].category.length !== 0) + return false; + break; // do nothing + default: + if (categoryHashInt !== genHash(row['full_data'].category)) + return false; } - if (div.getValue() != progressFormated) - div.setValue(progressFormated); - } - else { - if (ProgressColumnWidth < 0) - ProgressColumnWidth = td.offsetWidth; - td.adopt(new ProgressBar(progressFormated.toFloat(), { - 'width': ProgressColumnWidth - 5 - })); - td.resized = false; } - }; - this.columns['progress'].onResize = function(columnName) { - const pos = this.getColumnPos(columnName); - const trs = this.tableBody.getElements('tr'); - ProgressColumnWidth = -1; - for (let i = 0; i < trs.length; ++i) { - const td = trs[i].getElements('td')[pos]; - if (ProgressColumnWidth < 0) - ProgressColumnWidth = td.offsetWidth; - td.resized = true; - this.columns[columnName].updateTd(td, this.rows.get(trs[i].rowId)); + const tagHashInt = parseInt(tagHash); + const isNumber = !isNaN(tagHashInt); + if (isNumber) { + switch (tagHashInt) { + case TAGS_ALL: + break; // do nothing + + case TAGS_UNTAGGED: + if (row['full_data'].tags.length !== 0) + return false; + break; // do nothing + + default: + let rowTags = row['full_data'].tags.split(', '); + rowTags = rowTags.map(function(tag) { + return genHash(tag); + }); + if (!rowTags.contains(tagHashInt)) + return false; + } } - }.bind(this); - - // num_seeds - this.columns['num_seeds'].updateTd = function(td, row) { - const num_seeds = this.getRowValue(row, 0); - const num_complete = this.getRowValue(row, 1); - let html = num_seeds; - if (num_complete != -1) - html += ' (' + num_complete + ')'; - td.set('html', html); - td.set('title', html); - }; - this.columns['num_seeds'].compareRows = function(row1, row2) { - const num_seeds1 = this.getRowValue(row1, 0); - const num_complete1 = this.getRowValue(row1, 1); - - const num_seeds2 = this.getRowValue(row2, 0); - const 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; + if ((filterTerms !== undefined) && (filterTerms !== null) + && (filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(name, filterTerms)) + return false; - // dlspeed - this.columns['dlspeed'].updateTd = function(td, row) { - const speed = friendlyUnit(this.getRowValue(row), true); - td.set('html', speed); - td.set('title', speed); - }; + return true; + }, - // upspeed - this.columns['upspeed'].updateTd = this.columns['dlspeed'].updateTd; + getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash) { + let cnt = 0; + const rows = this.rows.getValues(); - // eta - this.columns['eta'].updateTd = function(td, row) { - const eta = friendlyDuration(this.getRowValue(row)); - td.set('html', eta); - td.set('title', eta); - }; + for (let i = 0; i < rows.length; ++i) + if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) ++cnt; + return cnt; + }, - // ratio - this.columns['ratio'].updateTd = function(td, row) { - const ratio = this.getRowValue(row); - const string = (ratio === -1) ? '∞' : toFixedPointString(ratio, 2); - td.set('html', string); - td.set('title', string); - }; + getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash) { + const rowsHashes = []; + const rows = this.rows.getValues(); - // added on - this.columns['added_on'].updateTd = function(td, row) { - const date = new Date(this.getRowValue(row) * 1000).toLocaleString(); - td.set('html', date); - td.set('title', date); - }; + for (let i = 0; i < rows.length; ++i) + if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) + rowsHashes.push(rows[i]['rowId']); - // completion_on - this.columns['completion_on'].updateTd = function(td, row) { - const val = this.getRowValue(row); - if ((val === 0xffffffff) || (val < 0)) { - td.set('html', ''); - td.set('title', ''); - } - else { - const date = new Date(this.getRowValue(row) * 1000).toLocaleString(); - td.set('html', date); - td.set('title', date); - } - }; + return rowsHashes; + }, - // seen_complete - this.columns['seen_complete'].updateTd = this.columns['completion_on'].updateTd; + getFilteredAndSortedRows: function() { + const filteredRows = []; - // dl_limit, up_limit - this.columns['dl_limit'].updateTd = function(td, row) { - const speed = this.getRowValue(row); - if (speed === 0) { - td.set('html', '∞'); - td.set('title', '∞'); - } - else { - const formattedSpeed = friendlyUnit(speed, true); - td.set('html', formattedSpeed); - td.set('title', formattedSpeed); - } - }; + const rows = this.rows.getValues(); + const filterText = $('torrentsFilterInput').value.trim().toLowerCase(); + const filterTerms = (filterText.length > 0) ? filterText.split(" ") : null; - this.columns['up_limit'].updateTd = this.columns['dl_limit'].updateTd; - - // downloaded, uploaded, downloaded_session, uploaded_session, amount_left, completed, total_size - this.columns['downloaded'].updateTd = this.columns['size'].updateTd; - this.columns['uploaded'].updateTd = this.columns['size'].updateTd; - this.columns['downloaded_session'].updateTd = this.columns['size'].updateTd; - this.columns['uploaded_session'].updateTd = this.columns['size'].updateTd; - this.columns['amount_left'].updateTd = this.columns['size'].updateTd; - this.columns['amount_left'].updateTd = this.columns['size'].updateTd; - this.columns['completed'].updateTd = this.columns['size'].updateTd; - this.columns['total_size'].updateTd = this.columns['size'].updateTd; - - // save_path, tracker - this.columns['save_path'].updateTd = this.columns['name'].updateTd; - this.columns['tracker'].updateTd = this.columns['name'].updateTd; - - // max_ratio - this.columns['max_ratio'].updateTd = this.columns['ratio'].updateTd; - - // last_activity - this.columns['last_activity'].updateTd = function(td, row) { - const val = this.getRowValue(row); - if (val < 1) { - td.set('html', '∞'); - td.set('title', '∞'); - } - else { - const formattedVal = 'QBT_TR(%1 ago)QBT_TR[CONTEXT=TransferListDelegate]'.replace('%1', friendlyDuration((new Date()) / 1000 - val)); - td.set('html', formattedVal); - td.set('title', formattedVal); + for (let i = 0; i < rows.length; ++i) { + if (this.applyFilter(rows[i], selected_filter, selected_category, selectedTag, filterTerms)) { + filteredRows.push(rows[i]); + filteredRows[rows[i].rowId] = rows[i]; + } } - }; - - // time active - this.columns['time_active'].updateTd = function(td, row) { - const time = friendlyDuration(this.getRowValue(row)); - td.set('html', time); - td.set('title', time); - }; - // availability - this.columns['availability'].updateTd = function(td, row) { - const value = toFixedPointString(this.getRowValue(row), 3); - td.set('html', value); - td.set('title', value); - }; - }, - - applyFilter: function(row, filterName, categoryHash, tagHash, filterTerms) { - const state = row['full_data'].state; - const name = row['full_data'].name.toLowerCase(); - let inactive = false; - let r; - - switch (filterName) { - case 'downloading': - if (state != 'downloading' && !~state.indexOf('DL')) - return false; - break; - case 'seeding': - if (state != 'uploading' && state != 'forcedUP' && state != 'stalledUP' && state != 'queuedUP' && state != 'checkingUP') - return false; - break; - case 'completed': - if (state != 'uploading' && !~state.indexOf('UP')) - return false; - break; - case 'paused': - if (!~state.indexOf('paused')) - return false; - break; - case 'resumed': + filteredRows.sort(function(row1, row2) { + const column = this.columns[this.sortedColumn]; + const res = column.compareRows(row1, row2); + if (this.reverseSort === '0') + return res; + else + return -res; + }.bind(this)); + return filteredRows; + }, + + setupTr: function(tr) { + tr.addEvent('dblclick', function(e) { + e.stop(); + this._this.deselectAll(); + this._this.selectRow(this.rowId); + const row = this._this.rows.get(this.rowId); + const state = row['full_data'].state; if (~state.indexOf('paused')) - return false; - break; - case 'inactive': - inactive = true; - // fallthrough - case 'active': - if (state == 'stalledDL') - r = (row['full_data'].upspeed > 0); + startFN(); else - r = state == 'metaDL' || state == 'downloading' || state == 'forcedDL' || state == 'uploading' || state == 'forcedUP'; - if (r == inactive) - return false; - break; - case 'errored': - if (state != 'error' && state != "unknown" && state != "missingFiles") - return false; - break; - } - - const categoryHashInt = parseInt(categoryHash); - if (!isNaN(categoryHashInt)) { - switch (categoryHashInt) { - case CATEGORIES_ALL: - break; // do nothing - case CATEGORIES_UNCATEGORIZED: - if (row['full_data'].category.length !== 0) - return false; - break; // do nothing - default: - if (categoryHashInt !== genHash(row['full_data'].category)) - return false; - } - } - - const tagHashInt = parseInt(tagHash); - const isNumber = !isNaN(tagHashInt); - if (isNumber) { - switch (tagHashInt) { - case TAGS_ALL: - break; // do nothing + pauseFN(); + return true; + }); + tr.addClass("torrentsTableContextMenuTarget"); + }, - case TAGS_UNTAGGED: - if (row['full_data'].tags.length !== 0) - return false; - break; // do nothing + getCurrentTorrentHash: function() { + return this.getSelectedRowId(); + }, - default: - let rowTags = row['full_data'].tags.split(', '); - rowTags = rowTags.map(function(tag) { - return genHash(tag); - }); - if (!rowTags.contains(tagHashInt)) - return false; - } + onSelectedRowChanged: function() { + updatePropertiesPanel(); } + }); + + const TorrentPeersTable = new Class({ + Extends: DynamicTable, + + initColumns: function() { + this.newColumn('country', '', 'QBT_TR(Country)QBT_TR[CONTEXT=PeerListWidget]', 22, true); + this.newColumn('ip', '', 'QBT_TR(IP)QBT_TR[CONTEXT=PeerListWidget]', 80, true); + this.newColumn('port', '', 'QBT_TR(Port)QBT_TR[CONTEXT=PeerListWidget]', 35, true); + this.newColumn('connection', '', 'QBT_TR(Connection)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('flags', '', 'QBT_TR(Flags)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('client', '', 'QBT_TR(Client)QBT_TR[CONTEXT=PeerListWidget]', 140, true); + this.newColumn('progress', '', 'QBT_TR(Progress)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('dl_speed', '', 'QBT_TR(Down Speed)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('up_speed', '', 'QBT_TR(Up Speed)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('downloaded', '', 'QBT_TR(Downloaded)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('uploaded', '', 'QBT_TR(Uploaded)QBT_TR[CONTEXT=PeerListWidget]', 50, true); + this.newColumn('relevance', '', 'QBT_TR(Relevance)QBT_TR[CONTEXT=PeerListWidget]', 30, true); + this.newColumn('files', '', 'QBT_TR(Files)QBT_TR[CONTEXT=PeerListWidget]', 100, true); + + this.columns['country'].dataProperties.push('country_code'); + this.columns['flags'].dataProperties.push('flags_desc'); + this.initColumnsFunctions(); + }, + + initColumnsFunctions: function() { + + // country + + this.columns['country'].updateTd = function(td, row) { + const country = this.getRowValue(row, 0); + const country_code = this.getRowValue(row, 1); + + if (!country_code) { + if (td.getChildren('img').length > 0) + td.getChildren('img')[0].dispose(); + return; + } - if ((filterTerms !== undefined) && (filterTerms !== null) - && (filterTerms.length > 0) && !containsAllTerms(name, filterTerms)) - return false; - - return true; - }, - - getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash) { - let cnt = 0; - const rows = this.rows.getValues(); + const img_path = 'images/flags/' + country_code + '.svg'; - for (let i = 0; i < rows.length; ++i) - if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) ++cnt; - return cnt; - }, + if (td.getChildren('img').length > 0) { + const img = td.getChildren('img')[0]; + img.set('src', img_path); + img.set('class', 'flags'); + img.set('alt', country); + img.set('title', country); + } + else + td.adopt(new Element('img', { + 'src': img_path, + 'class': 'flags', + 'alt': country, + 'title': country + })); + }; - getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash) { - const rowsHashes = []; - const rows = this.rows.getValues(); + // ip - for (let i = 0; i < rows.length; ++i) - if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) - rowsHashes.push(rows[i]['rowId']); + this.columns['ip'].compareRows = function(row1, row2) { + const ip1 = this.getRowValue(row1); + const ip2 = this.getRowValue(row2); - return rowsHashes; - }, + const a = ip1.split("."); + const b = ip2.split("."); - getFilteredAndSortedRows: function() { - const filteredRows = []; + for (let i = 0; i < 4; ++i) { + if (a[i] != b[i]) + return a[i] - b[i]; + } - const rows = this.rows.getValues(); - const filterText = $('torrentsFilterInput').value.trim().toLowerCase(); - const filterTerms = (filterText.length > 0) ? filterText.split(" ") : null; + return 0; + }; - for (let i = 0; i < rows.length; ++i) { - if (this.applyFilter(rows[i], selected_filter, selected_category, selectedTag, filterTerms)) { - filteredRows.push(rows[i]); - filteredRows[rows[i].rowId] = rows[i]; - } - } + // progress, relevance - filteredRows.sort(function(row1, row2) { - const column = this.columns[this.sortedColumn]; - const res = column.compareRows(row1, row2); - if (this.reverseSort === '0') - return res; - else - return -res; - }.bind(this)); - return filteredRows; - }, - - setupTr: function(tr) { - tr.addEvent('dblclick', function(e) { - e.stop(); - this._this.deselectAll(); - this._this.selectRow(this.rowId); - const row = this._this.rows.get(this.rowId); - const state = row['full_data'].state; - if (~state.indexOf('paused')) - startFN(); - else - pauseFN(); - return true; - }); - tr.addClass("torrentsTableContextMenuTarget"); - }, - - getCurrentTorrentHash: function() { - return this.getSelectedRowId(); - }, - - onSelectedRowChanged: function() { - updatePropertiesPanel(); - } -}); - -const TorrentPeersTable = new Class({ - Extends: DynamicTable, - - initColumns: function() { - this.newColumn('country', '', 'QBT_TR(Country)QBT_TR[CONTEXT=PeerListWidget]', 22, true); - this.newColumn('ip', '', 'QBT_TR(IP)QBT_TR[CONTEXT=PeerListWidget]', 80, true); - this.newColumn('port', '', 'QBT_TR(Port)QBT_TR[CONTEXT=PeerListWidget]', 35, true); - this.newColumn('connection', '', 'QBT_TR(Connection)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('flags', '', 'QBT_TR(Flags)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('client', '', 'QBT_TR(Client)QBT_TR[CONTEXT=PeerListWidget]', 140, true); - this.newColumn('progress', '', 'QBT_TR(Progress)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('dl_speed', '', 'QBT_TR(Down Speed)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('up_speed', '', 'QBT_TR(Up Speed)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('downloaded', '', 'QBT_TR(Downloaded)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('uploaded', '', 'QBT_TR(Uploaded)QBT_TR[CONTEXT=PeerListWidget]', 50, true); - this.newColumn('relevance', '', 'QBT_TR(Relevance)QBT_TR[CONTEXT=PeerListWidget]', 30, true); - this.newColumn('files', '', 'QBT_TR(Files)QBT_TR[CONTEXT=PeerListWidget]', 100, true); - - this.columns['country'].dataProperties.push('country_code'); - this.columns['flags'].dataProperties.push('flags_desc'); - this.initColumnsFunctions(); - }, - - initColumnsFunctions: function() { - - // country - - this.columns['country'].updateTd = function(td, row) { - const country = this.getRowValue(row, 0); - const country_code = this.getRowValue(row, 1); - - if (!country_code) { - if (td.getChildren('img').length > 0) - td.getChildren('img')[0].dispose(); - return; - } + this.columns['progress'].updateTd = function(td, row) { + const progress = this.getRowValue(row); + let progressFormated = (progress * 100).round(1); + if (progressFormated == 100.0 && progress != 1.0) + progressFormated = 99.9; + progressFormated += "%"; + td.set('html', progressFormated); + td.set('title', progressFormated); + }; - const img_path = 'images/flags/' + country_code + '.svg'; + this.columns['relevance'].updateTd = this.columns['progress'].updateTd; - if (td.getChildren('img').length > 0) { - const img = td.getChildren('img')[0]; - img.set('src', img_path); - img.set('class', 'flags'); - img.set('alt', country); - img.set('title', country); - } - else - td.adopt(new Element('img', { - 'src': img_path, - 'class': 'flags', - 'alt': country, - 'title': country - })); - }; + // dl_speed, up_speed - // ip + this.columns['dl_speed'].updateTd = function(td, row) { + const speed = this.getRowValue(row); + if (speed === 0) { + td.set('html', ''); + td.set('title', ''); + } + else { + const formattedSpeed = window.qBittorrent.Misc.friendlyUnit(speed, true); + td.set('html', formattedSpeed); + td.set('title', formattedSpeed); + } + }; - this.columns['ip'].compareRows = function(row1, row2) { - const ip1 = this.getRowValue(row1); - const ip2 = this.getRowValue(row2); + this.columns['up_speed'].updateTd = this.columns['dl_speed'].updateTd; - const a = ip1.split("."); - const b = ip2.split("."); + // downloaded, uploaded - for (let i = 0; i < 4; ++i) { - if (a[i] != b[i]) - return a[i] - b[i]; - } + this.columns['downloaded'].updateTd = function(td, row) { + const downloaded = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false); + td.set('html', downloaded); + td.set('title', downloaded); + }; - return 0; - }; + this.columns['uploaded'].updateTd = this.columns['downloaded'].updateTd; - // progress, relevance + // flags - this.columns['progress'].updateTd = function(td, row) { - const progress = this.getRowValue(row); - let progressFormated = (progress * 100).round(1); - if (progressFormated == 100.0 && progress != 1.0) - progressFormated = 99.9; - progressFormated += "%"; - td.set('html', progressFormated); - td.set('title', progressFormated); - }; + this.columns['flags'].updateTd = function(td, row) { + td.innerHTML = this.getRowValue(row, 0); + td.title = this.getRowValue(row, 1); + }; - this.columns['relevance'].updateTd = this.columns['progress'].updateTd; + // files - // dl_speed, up_speed + this.columns['files'].updateTd = function(td, row) { + td.innerHTML = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row, 0).replace(/\n/g, ';')); + td.title = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row, 0)); + }; - this.columns['dl_speed'].updateTd = function(td, row) { - const speed = this.getRowValue(row); - if (speed === 0) { - td.set('html', ''); - td.set('title', ''); + } + }); + + const SearchResultsTable = new Class({ + Extends: DynamicTable, + + initColumns: function() { + this.newColumn('fileName', '', 'QBT_TR(Name)QBT_TR[CONTEXT=SearchResultsTable]', 500, true); + this.newColumn('fileSize', '', 'QBT_TR(Size)QBT_TR[CONTEXT=SearchResultsTable]', 100, true); + this.newColumn('nbSeeders', '', 'QBT_TR(Seeders)QBT_TR[CONTEXT=SearchResultsTable]', 100, true); + this.newColumn('nbLeechers', '', 'QBT_TR(Leechers)QBT_TR[CONTEXT=SearchResultsTable]', 100, true); + this.newColumn('siteUrl', '', 'QBT_TR(Search engine)QBT_TR[CONTEXT=SearchResultsTable]', 250, true); + + this.initColumnsFunctions(); + }, + + initColumnsFunctions: function() { + const displayText = function(td, row) { + const value = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row)); + td.set('html', value); + td.set('title', value); } - else { - const formattedSpeed = friendlyUnit(speed, true); - td.set('html', formattedSpeed); - td.set('title', formattedSpeed); + const displaySize = function(td, row) { + const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false); + td.set('html', size); + td.set('title', size); + } + const displayNum = function(td, row) { + const value = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row)); + const formattedValue = (value === "-1") ? "Unknown" : value; + td.set('html', formattedValue); + td.set('title', formattedValue); } - }; - - this.columns['up_speed'].updateTd = this.columns['dl_speed'].updateTd; - - // downloaded, uploaded - - this.columns['downloaded'].updateTd = function(td, row) { - const downloaded = friendlyUnit(this.getRowValue(row), false); - td.set('html', downloaded); - td.set('title', downloaded); - }; - - this.columns['uploaded'].updateTd = this.columns['downloaded'].updateTd; - // flags + this.columns['fileName'].updateTd = displayText; + this.columns['fileSize'].updateTd = displaySize; + this.columns['nbSeeders'].updateTd = displayNum; + this.columns['nbLeechers'].updateTd = displayNum; + this.columns['siteUrl'].updateTd = displayText; + }, + + getFilteredAndSortedRows: function() { + const getSizeFilters = function() { + let minSize = (window.qBittorrent.Search.searchSizeFilter.min > 0.00) ? (window.qBittorrent.Search.searchSizeFilter.min * Math.pow(1024, window.qBittorrent.Search.searchSizeFilter.minUnit)) : 0.00; + let maxSize = (window.qBittorrent.Search.searchSizeFilter.max > 0.00) ? (window.qBittorrent.Search.searchSizeFilter.max * Math.pow(1024, window.qBittorrent.Search.searchSizeFilter.maxUnit)) : 0.00; + + if ((minSize > maxSize) && (maxSize > 0.00)) { + const tmp = minSize; + minSize = maxSize; + maxSize = tmp; + } - this.columns['flags'].updateTd = function(td, row) { - td.innerHTML = this.getRowValue(row, 0); - td.title = this.getRowValue(row, 1); - }; + return { + min: minSize, + max: maxSize + } + }; - // files + const getSeedsFilters = function() { + let minSeeds = (window.qBittorrent.Search.searchSeedsFilter.min > 0) ? window.qBittorrent.Search.searchSeedsFilter.min : 0; + let maxSeeds = (window.qBittorrent.Search.searchSeedsFilter.max > 0) ? window.qBittorrent.Search.searchSeedsFilter.max : 0; - this.columns['files'].updateTd = function(td, row) { - td.innerHTML = escapeHtml(this.getRowValue(row, 0).replace(/\n/g, ';')); - td.title = escapeHtml(this.getRowValue(row, 0)); - }; + if ((minSeeds > maxSeeds) && (maxSeeds > 0)) { + const tmp = minSeeds; + minSeeds = maxSeeds; + maxSeeds = tmp; + } - } -}); + return { + min: minSeeds, + max: maxSeeds + } + }; -const SearchResultsTable = new Class({ - Extends: DynamicTable, + let filteredRows = []; + const rows = this.rows.getValues(); + const searchTerms = window.qBittorrent.Search.searchText.pattern.toLowerCase().split(" "); + const filterTerms = window.qBittorrent.Search.searchText.filterPattern.toLowerCase().split(" "); + const sizeFilters = getSizeFilters(); + const seedsFilters = getSeedsFilters(); + const searchInTorrentName = $('searchInTorrentName').get('value') === "names"; + + if (searchInTorrentName || (filterTerms.length > 0) || (window.qBittorrent.Search.searchSizeFilter.min > 0.00) || (window.qBittorrent.Search.searchSizeFilter.max > 0.00)) { + for (let i = 0; i < rows.length; ++i) { + const row = rows[i]; + + if (searchInTorrentName && !window.qBittorrent.Misc.containsAllTerms(row.full_data.fileName, searchTerms)) continue; + if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(row.full_data.fileName, filterTerms)) continue; + if ((sizeFilters.min > 0.00) && (row.full_data.fileSize < sizeFilters.min)) continue; + if ((sizeFilters.max > 0.00) && (row.full_data.fileSize > sizeFilters.max)) continue; + if ((seedsFilters.min > 0) && (row.full_data.nbSeeders < seedsFilters.min)) continue; + if ((seedsFilters.max > 0) && (row.full_data.nbSeeders > seedsFilters.max)) continue; + + filteredRows.push(row); + } + } + else { + filteredRows = rows; + } - initColumns: function() { - this.newColumn('fileName', '', 'QBT_TR(Name)QBT_TR[CONTEXT=SearchResultsTable]', 500, true); - this.newColumn('fileSize', '', 'QBT_TR(Size)QBT_TR[CONTEXT=SearchResultsTable]', 100, true); - this.newColumn('nbSeeders', '', 'QBT_TR(Seeders)QBT_TR[CONTEXT=SearchResultsTable]', 100, true); - this.newColumn('nbLeechers', '', 'QBT_TR(Leechers)QBT_TR[CONTEXT=SearchResultsTable]', 100, true); - this.newColumn('siteUrl', '', 'QBT_TR(Search engine)QBT_TR[CONTEXT=SearchResultsTable]', 250, true); + filteredRows.sort(function(row1, row2) { + const column = this.columns[this.sortedColumn]; + const res = column.compareRows(row1, row2); + if (this.reverseSort === '0') + return res; + else + return -res; + }.bind(this)); - this.initColumnsFunctions(); - }, + return filteredRows; + }, - initColumnsFunctions: function() { - const displayText = function(td, row) { - const value = escapeHtml(this.getRowValue(row)); - td.set('html', value); - td.set('title', value); + setupTr: function(tr) { + tr.addClass("searchTableRow"); } - const displaySize = function(td, row) { - const size = friendlyUnit(this.getRowValue(row), false); - td.set('html', size); - td.set('title', size); - } - const displayNum = function(td, row) { - const value = escapeHtml(this.getRowValue(row)); - const formattedValue = (value === "-1") ? "Unknown" : value; - td.set('html', formattedValue); - td.set('title', formattedValue); - } - - this.columns['fileName'].updateTd = displayText; - this.columns['fileSize'].updateTd = displaySize; - this.columns['nbSeeders'].updateTd = displayNum; - this.columns['nbLeechers'].updateTd = displayNum; - this.columns['siteUrl'].updateTd = displayText; - }, - - getFilteredAndSortedRows: function() { - const getSizeFilters = function() { - let minSize = (searchSizeFilter.min > 0.00) ? (searchSizeFilter.min * Math.pow(1024, searchSizeFilter.minUnit)) : 0.00; - let maxSize = (searchSizeFilter.max > 0.00) ? (searchSizeFilter.max * Math.pow(1024, searchSizeFilter.maxUnit)) : 0.00; - - if ((minSize > maxSize) && (maxSize > 0.00)) { - const tmp = minSize; - minSize = maxSize; - maxSize = tmp; - } + }); - return { - min: minSize, - max: maxSize - } - }; + const SearchPluginsTable = new Class({ + Extends: DynamicTable, - const getSeedsFilters = function() { - let minSeeds = (searchSeedsFilter.min > 0) ? searchSeedsFilter.min : 0; - let maxSeeds = (searchSeedsFilter.max > 0) ? searchSeedsFilter.max : 0; + initColumns: function() { + this.newColumn('fullName', '', 'QBT_TR(Name)QBT_TR[CONTEXT=SearchPluginsTable]', 175, true); + this.newColumn('version', '', 'QBT_TR(Version)QBT_TR[CONTEXT=SearchPluginsTable]', 100, true); + this.newColumn('url', '', 'QBT_TR(Url)QBT_TR[CONTEXT=SearchPluginsTable]', 175, true); + this.newColumn('enabled', '', 'QBT_TR(Enabled)QBT_TR[CONTEXT=SearchPluginsTable]', 100, true); - if ((minSeeds > maxSeeds) && (maxSeeds > 0)) { - const tmp = minSeeds; - minSeeds = maxSeeds; - maxSeeds = tmp; - } + this.initColumnsFunctions(); + }, - return { - min: minSeeds, - max: maxSeeds + initColumnsFunctions: function() { + const displayText = function(td, row) { + const value = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row)); + td.set('html', value); + td.set('title', value); } - }; - let filteredRows = []; - const rows = this.rows.getValues(); - const searchTerms = searchPattern.toLowerCase().split(" "); - const filterTerms = searchFilterPattern.toLowerCase().split(" "); - const sizeFilters = getSizeFilters(); - const seedsFilters = getSeedsFilters(); - const searchInTorrentName = $('searchInTorrentName').get('value') === "names"; - - if (searchInTorrentName || (filterTerms.length > 0) || (searchSizeFilter.min > 0.00) || (searchSizeFilter.max > 0.00)) { - for (let i = 0; i < rows.length; ++i) { - const row = rows[i]; - - if (searchInTorrentName && !containsAllTerms(row.full_data.fileName, searchTerms)) continue; - if ((filterTerms.length > 0) && !containsAllTerms(row.full_data.fileName, filterTerms)) continue; - if ((sizeFilters.min > 0.00) && (row.full_data.fileSize < sizeFilters.min)) continue; - if ((sizeFilters.max > 0.00) && (row.full_data.fileSize > sizeFilters.max)) continue; - if ((seedsFilters.min > 0) && (row.full_data.nbSeeders < seedsFilters.min)) continue; - if ((seedsFilters.max > 0) && (row.full_data.nbSeeders > seedsFilters.max)) continue; + this.columns['fullName'].updateTd = displayText; + this.columns['version'].updateTd = displayText; + this.columns['url'].updateTd = displayText; + this.columns['enabled'].updateTd = function(td, row) { + const value = this.getRowValue(row); + if (value) { + td.set('html', "Yes"); + td.set('title', "Yes"); + td.getParent("tr").addClass("green"); + td.getParent("tr").removeClass("red"); + } + else { + td.set('html', "No"); + td.set('title', "No"); + td.getParent("tr").addClass("red"); + td.getParent("tr").removeClass("green"); + } + }; + }, - filteredRows.push(row); - } - } - else { - filteredRows = rows; + setupTr: function(tr) { + tr.addClass("searchPluginsTableRow"); } + }); + + const TorrentTrackersTable = new Class({ + Extends: DynamicTable, + + initColumns: function() { + this.newColumn('tier', '', 'QBT_TR(Tier)QBT_TR[CONTEXT=TrackerListWidget]', 35, true); + this.newColumn('url', '', 'QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget]', 250, true); + this.newColumn('status', '', 'QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]', 125, true); + this.newColumn('peers', '', 'QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); + this.newColumn('seeds', '', 'QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); + this.newColumn('leeches', '', 'QBT_TR(Leeches)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); + this.newColumn('downloaded', '', 'QBT_TR(Downloaded)QBT_TR[CONTEXT=TrackerListWidget]', 100, true); + this.newColumn('message', '', 'QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]', 250, true); + }, + }); + + const TorrentFilesTable = new Class({ + Extends: DynamicTable, + + filterTerms: [], + prevFilterTerms: [], + prevRowsString: null, + prevFilteredRows: [], + prevSortedColumn: null, + prevReverseSort: null, + fileTree: new window.qBittorrent.FileTree.FileTree(), + + populateTable: function(root) { + this.fileTree.setRoot(root); + root.children.each(function(node) { + this._addNodeToTable(node, 0); + }.bind(this)); + }, - filteredRows.sort(function(row1, row2) { - const column = this.columns[this.sortedColumn]; - const res = column.compareRows(row1, row2); - if (this.reverseSort === '0') - return res; - else - return -res; - }.bind(this)); - - return filteredRows; - }, - - setupTr: function(tr) { - tr.addClass("searchTableRow"); - } -}); - -const SearchPluginsTable = new Class({ - Extends: DynamicTable, - - initColumns: function() { - this.newColumn('fullName', '', 'QBT_TR(Name)QBT_TR[CONTEXT=SearchPluginsTable]', 175, true); - this.newColumn('version', '', 'QBT_TR(Version)QBT_TR[CONTEXT=SearchPluginsTable]', 100, true); - this.newColumn('url', '', 'QBT_TR(Url)QBT_TR[CONTEXT=SearchPluginsTable]', 175, true); - this.newColumn('enabled', '', 'QBT_TR(Enabled)QBT_TR[CONTEXT=SearchPluginsTable]', 100, true); - - this.initColumnsFunctions(); - }, - - initColumnsFunctions: function() { - const displayText = function(td, row) { - const value = escapeHtml(this.getRowValue(row)); - td.set('html', value); - td.set('title', value); - } + _addNodeToTable: function(node, depth) { + node.depth = depth; - this.columns['fullName'].updateTd = displayText; - this.columns['version'].updateTd = displayText; - this.columns['url'].updateTd = displayText; - this.columns['enabled'].updateTd = function(td, row) { - const value = this.getRowValue(row); - if (value) { - td.set('html', "Yes"); - td.set('title', "Yes"); - td.getParent("tr").addClass("green"); - td.getParent("tr").removeClass("red"); + if (node.isFolder) { + const data = { + rowId: node.rowId, + size: node.size, + checked: node.checked, + remaining: node.remaining, + progress: node.progress, + priority: window.qBittorrent.PropFiles.normalizePriority(node.priority), + availability: node.availability, + fileId: -1, + name: node.name + }; + + node.data = data; + node.full_data = data; + this.updateRowData(data); } else { - td.set('html', "No"); - td.set('title', "No"); - td.getParent("tr").addClass("red"); - td.getParent("tr").removeClass("green"); + node.data.rowId = node.rowId; + node.full_data = node.data; + this.updateRowData(node.data); } - }; - }, - - setupTr: function(tr) { - tr.addClass("searchPluginsTableRow"); - } -}); - -const TorrentTrackersTable = new Class({ - Extends: DynamicTable, - - initColumns: function() { - this.newColumn('tier', '', 'QBT_TR(Tier)QBT_TR[CONTEXT=TrackerListWidget]', 35, true); - this.newColumn('url', '', 'QBT_TR(URL)QBT_TR[CONTEXT=TrackerListWidget]', 250, true); - this.newColumn('status', '', 'QBT_TR(Status)QBT_TR[CONTEXT=TrackerListWidget]', 125, true); - this.newColumn('peers', '', 'QBT_TR(Peers)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); - this.newColumn('seeds', '', 'QBT_TR(Seeds)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); - this.newColumn('leeches', '', 'QBT_TR(Leeches)QBT_TR[CONTEXT=TrackerListWidget]', 75, true); - this.newColumn('downloaded', '', 'QBT_TR(Downloaded)QBT_TR[CONTEXT=TrackerListWidget]', 100, true); - this.newColumn('message', '', 'QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]', 250, true); - }, -}); - -const TorrentFilesTable = new Class({ - Extends: DynamicTable, - - filterTerms: [], - prevFilterTerms: [], - prevRowsString: null, - prevFilteredRows: [], - prevSortedColumn: null, - prevReverseSort: null, - fileTree: new FileTree(), - - populateTable: function(root) { - this.fileTree.setRoot(root); - root.children.each(function(node) { - this._addNodeToTable(node, 0); - }.bind(this)); - }, - - _addNodeToTable: function(node, depth) { - node.depth = depth; - - if (node.isFolder) { - const data = { - rowId: node.rowId, - size: node.size, - checked: node.checked, - remaining: node.remaining, - progress: node.progress, - priority: normalizePriority(node.priority), - availability: node.availability, - fileId: -1, - name: node.name - }; - - node.data = data; - node.full_data = data; - this.updateRowData(data); - } - else { - node.data.rowId = node.rowId; - node.full_data = node.data; - this.updateRowData(node.data); - } - - node.children.each(function(child) { - this._addNodeToTable(child, depth + 1); - }.bind(this)); - }, - - getRoot: function() { - return this.fileTree.getRoot(); - }, - - getNode: function(rowId) { - return this.fileTree.getNode(rowId); - }, - - getRow: function(node) { - const rowId = this.fileTree.getRowId(node); - return this.rows.get(rowId); - }, - - 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() { - const that = this; - const displaySize = function(td, row) { - const size = friendlyUnit(this.getRowValue(row), false); - td.set('html', size); - td.set('title', size); - } - const displayPercentage = function(td, row) { - const value = friendlyPercentage(this.getRowValue(row)); - td.set('html', value); - td.set('title', value); - }; - this.columns['name'].updateTd = function(td, row) { - const id = row.rowId; - const fileNameId = 'filesTablefileName' + id; - const node = that.getNode(id); + node.children.each(function(child) { + this._addNodeToTable(child, depth + 1); + }.bind(this)); + }, + + getRoot: function() { + return this.fileTree.getRoot(); + }, + + getNode: function(rowId) { + return this.fileTree.getNode(rowId); + }, + + getRow: function(node) { + const rowId = this.fileTree.getRowId(node); + return this.rows.get(rowId); + }, + + 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() { + const that = this; + const displaySize = function(td, row) { + const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false); + td.set('html', size); + td.set('title', size); + } + const displayPercentage = function(td, row) { + const value = window.qBittorrent.Misc.friendlyPercentage(this.getRowValue(row)); + td.set('html', value); + td.set('title', value); + }; - if (node.isFolder) { - const value = this.getRowValue(row); - const collapseIconId = 'filesTableCollapseIcon' + id; - const dirImgId = 'filesTableDirImg' + id; - if ($(dirImgId)) { - // just update file name - $(fileNameId).textContent = escapeHtml(value); + this.columns['name'].updateTd = function(td, row) { + const id = row.rowId; + const fileNameId = 'filesTablefileName' + id; + const node = that.getNode(id); + + if (node.isFolder) { + const value = this.getRowValue(row); + const collapseIconId = 'filesTableCollapseIcon' + id; + const dirImgId = 'filesTableDirImg' + id; + if ($(dirImgId)) { + // just update file name + $(fileNameId).textContent = window.qBittorrent.Misc.escapeHtml(value); + } + else { + const collapseIcon = new Element('img', { + src: 'images/qbt-theme/go-down.svg', + styles: { + 'margin-left': (node.depth * 20) + }, + class: "filesTableCollapseIcon", + id: collapseIconId, + "data-id": id, + onclick: "qBittorrent.PropFiles.collapseIconClicked(this)" + }); + const span = new Element('span', { + text: window.qBittorrent.Misc.escapeHtml(value), + id: fileNameId + }); + const dirImg = new Element('img', { + src: 'images/qbt-theme/inode-directory.svg', + styles: { + 'width': 15, + 'padding-right': 5, + 'margin-bottom': -3 + }, + id: dirImgId + }); + const html = collapseIcon.outerHTML + dirImg.outerHTML + span.outerHTML; + td.set('html', html); + } } else { - const collapseIcon = new Element('img', { - src: 'images/qbt-theme/go-down.svg', - styles: { - 'margin-left': (node.depth * 20) - }, - class: "filesTableCollapseIcon", - id: collapseIconId, - "data-id": id, - onclick: "collapseIconClicked(this)" - }); + const value = this.getRowValue(row); const span = new Element('span', { - text: escapeHtml(value), - id: fileNameId - }); - const dirImg = new Element('img', { - src: 'images/qbt-theme/inode-directory.svg', + text: window.qBittorrent.Misc.escapeHtml(value), + id: fileNameId, styles: { - 'width': 15, - 'padding-right': 5, - 'margin-bottom': -3 - }, - id: dirImgId + 'margin-left': ((node.depth + 1) * 20) + } }); - const html = collapseIcon.outerHTML + dirImg.outerHTML + span.outerHTML; - td.set('html', html); + td.set('html', span.outerHTML); } - } - else { - const value = this.getRowValue(row); - const span = new Element('span', { - text: escapeHtml(value), - id: fileNameId, - styles: { - 'margin-left': ((node.depth + 1) * 20) - } - }); - td.set('html', span.outerHTML); - } - }; + }; - this.columns['checked'].updateTd = function(td, row) { - const id = row.rowId; - const value = this.getRowValue(row); + this.columns['checked'].updateTd = function(td, row) { + const id = row.rowId; + const value = this.getRowValue(row); - if (isDownloadCheckboxExists(id)) { - updateDownloadCheckbox(id, value); - } - else { - const treeImg = new Element('img', { - src: 'images/L.gif', - styles: { - 'margin-bottom': -2 - } - }); - td.adopt(treeImg, createDownloadCheckbox(id, row.full_data.fileId, value)); - } - }; + if (window.qBittorrent.PropFiles.isDownloadCheckboxExists(id)) { + window.qBittorrent.PropFiles.updateDownloadCheckbox(id, value); + } + else { + const treeImg = new Element('img', { + src: 'images/L.gif', + styles: { + 'margin-bottom': -2 + } + }); + td.adopt(treeImg, window.qBittorrent.PropFiles.createDownloadCheckbox(id, row.full_data.fileId, value)); + } + }; - this.columns['size'].updateTd = displaySize; + this.columns['size'].updateTd = displaySize; - this.columns['progress'].updateTd = function(td, row) { - const id = row.rowId; - const value = this.getRowValue(row); + this.columns['progress'].updateTd = function(td, row) { + const id = row.rowId; + const value = this.getRowValue(row); - const progressBar = $('pbf_' + id); - if (progressBar === null) { - td.adopt(new ProgressBar(value.toFloat(), { - id: 'pbf_' + id, - width: 80 - })); - } - else { - progressBar.setValue(value.toFloat()); - } - }; + const progressBar = $('pbf_' + id); + if (progressBar === null) { + td.adopt(new window.qBittorrent.ProgressBar.ProgressBar(value.toFloat(), { + id: 'pbf_' + id, + width: 80 + })); + } + else { + progressBar.setValue(value.toFloat()); + } + }; - this.columns['priority'].updateTd = function(td, row) { - const id = row.rowId; - const value = this.getRowValue(row); + this.columns['priority'].updateTd = function(td, row) { + const id = row.rowId; + const value = this.getRowValue(row); - if (isPriorityComboExists(id)) - updatePriorityCombo(id, value); - else - td.adopt(createPriorityCombo(id, row.full_data.fileId, value)); - }; + if (window.qBittorrent.PropFiles.isPriorityComboExists(id)) + window.qBittorrent.PropFiles.updatePriorityCombo(id, value); + else + td.adopt(window.qBittorrent.PropFiles.createPriorityCombo(id, row.full_data.fileId, value)); + }; - this.columns['remaining'].updateTd = displaySize; - this.columns['availability'].updateTd = displayPercentage; - }, + this.columns['remaining'].updateTd = displaySize; + this.columns['availability'].updateTd = displayPercentage; + }, - altRow: function() { - let addClass = false; - const trs = this.tableBody.getElements('tr'); - trs.each(function(tr) { - if (tr.hasClass("invisible")) - return; + altRow: function() { + let addClass = false; + const trs = this.tableBody.getElements('tr'); + trs.each(function(tr) { + if (tr.hasClass("invisible")) + return; - if (addClass){ - tr.addClass("alt"); - tr.removeClass("nonAlt"); - } - else { - tr.removeClass("alt"); - tr.addClass("nonAlt"); - } - addClass = !addClass; - }.bind(this)); - }, - - _sortNodesByColumn: function(nodes, column) { - nodes.sort(function(row1, row2) { - // list folders before files when sorting by name - if (column.name === "name") { - const node1 = this.getNode(row1.data.rowId); - const node2 = this.getNode(row2.data.rowId); - if (node1.isFolder && !node2.isFolder) - return -1; - if (node2.isFolder && !node1.isFolder) - return 1; - } + if (addClass){ + tr.addClass("alt"); + tr.removeClass("nonAlt"); + } + else { + tr.removeClass("alt"); + tr.addClass("nonAlt"); + } + addClass = !addClass; + }.bind(this)); + }, + + _sortNodesByColumn: function(nodes, column) { + nodes.sort(function(row1, row2) { + // list folders before files when sorting by name + if (column.name === "name") { + const node1 = this.getNode(row1.data.rowId); + const node2 = this.getNode(row2.data.rowId); + if (node1.isFolder && !node2.isFolder) + return -1; + if (node2.isFolder && !node1.isFolder) + return 1; + } - const res = column.compareRows(row1, row2); - return (this.reverseSort === '0') ? res : -res; - }.bind(this)); + const res = column.compareRows(row1, row2); + return (this.reverseSort === '0') ? res : -res; + }.bind(this)); - nodes.each(function(node) { - if (node.children.length > 0) - this._sortNodesByColumn(node.children, column); - }.bind(this)); - }, + nodes.each(function(node) { + if (node.children.length > 0) + this._sortNodesByColumn(node.children, column); + }.bind(this)); + }, - _filterNodes: function(node, filterTerms, filteredRows) { - if (node.isFolder) { - const childAdded = node.children.reduce(function (acc, child) { - // we must execute the function before ORing w/ acc or we'll stop checking child nodes after the first successful match - return (this._filterNodes(child, filterTerms, filteredRows) || acc); - }.bind(this), false); + _filterNodes: function(node, filterTerms, filteredRows) { + if (node.isFolder) { + const childAdded = node.children.reduce(function (acc, child) { + // we must execute the function before ORing w/ acc or we'll stop checking child nodes after the first successful match + return (this._filterNodes(child, filterTerms, filteredRows) || acc); + }.bind(this), false); + + if (childAdded) { + const row = this.getRow(node); + filteredRows.push(row); + return true; + } + } - if (childAdded) { + if (window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) { const row = this.getRow(node); filteredRows.push(row); return true; } - } - if (containsAllTerms(node.name, filterTerms)) { - const row = this.getRow(node); - filteredRows.push(row); - return true; - } + return false; + }, - return false; - }, + setFilter: function(text) { + const filterTerms = text.trim().toLowerCase().split(' '); + if ((filterTerms.length === 1) && (filterTerms[0] === '')) + this.filterTerms = []; + else + this.filterTerms = filterTerms; + }, - setFilter: function(text) { - const filterTerms = text.trim().toLowerCase().split(' '); - if ((filterTerms.length === 1) && (filterTerms[0] === '')) - this.filterTerms = []; - else - this.filterTerms = filterTerms; - }, + getFilteredAndSortedRows: function() { + if (this.getRoot() === null) + return []; - getFilteredAndSortedRows: function() { - if (this.getRoot() === null) - return []; + const generateRowsSignature = function(rows) { + const rowsData = rows.map(function(row) { + return row.full_data; + }); + return JSON.stringify(rowsData); + }; - const generateRowsSignature = function(rows) { - const rowsData = rows.map(function(row) { - return row.full_data; - }); - return JSON.stringify(rowsData); - }; + const getFilteredRows = function() { + if (this.filterTerms.length === 0) { + const nodeArray = this.fileTree.toArray(); + const filteredRows = nodeArray.map(function(node) { + return this.getRow(node); + }.bind(this)); + return filteredRows; + } - const getFilteredRows = function() { - if (this.filterTerms.length === 0) { - const nodeArray = this.fileTree.toArray(); - const filteredRows = nodeArray.map(function(node) { - return this.getRow(node); + const filteredRows = []; + this.getRoot().children.each(function(child) { + this._filterNodes(child, this.filterTerms, filteredRows); }.bind(this)); + filteredRows.reverse(); return filteredRows; + }.bind(this); + + const hasRowsChanged = function(rowsString, prevRowsStringString) { + const rowsChanged = (rowsString !== prevRowsStringString); + const isFilterTermsChanged = this.filterTerms.reduce(function(acc, term, index) { + return (acc || (term !== this.prevFilterTerms[index])); + }.bind(this), false); + const isFilterChanged = ((this.filterTerms.length !== this.prevFilterTerms.length) + || ((this.filterTerms.length > 0) && isFilterTermsChanged)); + const isSortedColumnChanged = (this.prevSortedColumn !== this.sortedColumn); + const isReverseSortChanged = (this.prevReverseSort !== this.reverseSort); + + return (rowsChanged || isFilterChanged || isSortedColumnChanged || isReverseSortChanged); + }.bind(this); + + const rowsString = generateRowsSignature(this.rows); + if (!hasRowsChanged(rowsString, this.prevRowsString)) { + return this.prevFilteredRows; } - const filteredRows = []; - this.getRoot().children.each(function(child) { - this._filterNodes(child, this.filterTerms, filteredRows); - }.bind(this)); - filteredRows.reverse(); + // sort, then filter + const column = this.columns[this.sortedColumn]; + this._sortNodesByColumn(this.getRoot().children, column); + const filteredRows = getFilteredRows(); + + this.prevFilterTerms = this.filterTerms; + this.prevRowsString = rowsString; + this.prevFilteredRows = filteredRows; + this.prevSortedColumn = this.sortedColumn; + this.prevReverseSort = this.reverseSort; return filteredRows; - }.bind(this); - - const hasRowsChanged = function(rowsString, prevRowsStringString) { - const rowsChanged = (rowsString !== prevRowsStringString); - const isFilterTermsChanged = this.filterTerms.reduce(function(acc, term, index) { - return (acc || (term !== this.prevFilterTerms[index])); - }.bind(this), false); - const isFilterChanged = ((this.filterTerms.length !== this.prevFilterTerms.length) - || ((this.filterTerms.length > 0) && isFilterTermsChanged)); - const isSortedColumnChanged = (this.prevSortedColumn !== this.sortedColumn); - const isReverseSortChanged = (this.prevReverseSort !== this.reverseSort); - - return (rowsChanged || isFilterChanged || isSortedColumnChanged || isReverseSortChanged); - }.bind(this); - - const rowsString = generateRowsSignature(this.rows); - if (!hasRowsChanged(rowsString, this.prevRowsString)) { - return this.prevFilteredRows; + }, + + setIgnored: function(rowId, ignore) { + const row = this.rows.get(rowId); + if (ignore) + row.full_data.remaining = 0; + else + row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100))); } + }); - // sort, then filter - const column = this.columns[this.sortedColumn]; - this._sortNodesByColumn(this.getRoot().children, column); - const filteredRows = getFilteredRows(); - - this.prevFilterTerms = this.filterTerms; - this.prevRowsString = rowsString; - this.prevFilteredRows = filteredRows; - this.prevSortedColumn = this.sortedColumn; - this.prevReverseSort = this.reverseSort; - return filteredRows; - }, - - setIgnored: function(rowId, ignore) { - const row = this.rows.get(rowId); - if (ignore) - row.full_data.remaining = 0; - else - row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100))); - } -}); + return exports(); +})(); /*************************************************************/ diff --git a/src/webui/www/private/scripts/file-tree.js b/src/webui/www/private/scripts/file-tree.js index c84738f29..9fbeebc01 100644 --- a/src/webui/www/private/scripts/file-tree.js +++ b/src/webui/www/private/scripts/file-tree.js @@ -28,149 +28,167 @@ 'use strict'; -const FilePriority = { - "Ignored": 0, - "Normal": 1, - "High": 6, - "Maximum": 7, - "Mixed": -1 -}; -Object.freeze(FilePriority); - -const TriState = { - "Unchecked": 0, - "Checked": 1, - "Partial": 2 -}; -Object.freeze(TriState); - -const FileTree = new Class({ - root: null, - nodeMap: {}, - - setRoot: function(root) { - this.root = root; - this.generateNodeMap(root); - - if (this.root.isFolder) - this.root.calculateSize(); - }, - - getRoot: function() { - return this.root; - }, - - generateNodeMap: function(node) { - // don't store root node in map - if (node.root !== null) { - this.nodeMap[node.rowId] = node; - } - - node.children.each(function(child) { - this.generateNodeMap(child); - }.bind(this)); - }, - - getNode: function(rowId) { - return (this.nodeMap[rowId] === undefined) - ? null - : this.nodeMap[rowId]; - }, - - getRowId: function(node) { - return node.rowId; - }, - - /** - * Returns the nodes in dfs order - */ - toArray: function() { - const nodes = []; - this.root.children.each(function(node) { - this._getArrayOfNodes(node, nodes); - }.bind(this)); - return nodes; - }, - - _getArrayOfNodes: function(node, array) { - array.push(node); - node.children.each(function(child) { - this._getArrayOfNodes(child, array); - }.bind(this)); - } -}); - -const FileNode = new Class({ - name: "", - rowId: null, - size: 0, - checked: TriState.Unchecked, - remaining: 0, - progress: 0, - priority: FilePriority.Normal, - availability: 0, - depth: 0, - root: null, - data: null, - isFolder: false, - children: [], -}); - -const FolderNode = new Class({ - Extends: FileNode, - - initialize: function() { - this.isFolder = true; - }, - - addChild(node) { - this.children.push(node); - }, - - /** - * Recursively calculate size of node and its children - */ - calculateSize: function() { - let size = 0; - let remaining = 0; - let progress = 0; - let availability = 0; - let checked = TriState.Unchecked; - let priority = FilePriority.Normal; - - let isFirstFile = true; - - this.children.each(function(node) { - if (node.isFolder) - node.calculateSize(); - - size += node.size; - - if (isFirstFile) { - priority = node.priority; - checked = node.checked; - isFirstFile = false; - } - else { - if (priority !== node.priority) - priority = FilePriority.Mixed; - if (checked !== node.checked) - checked = TriState.Partial; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.FileTree = (function() { + const exports = function() { + return { + FilePriority: FilePriority, + TriState: TriState, + FileTree: FileTree, + FileNode: FileNode, + FolderNode: FolderNode, + }; + }; + + const FilePriority = { + "Ignored": 0, + "Normal": 1, + "High": 6, + "Maximum": 7, + "Mixed": -1 + }; + Object.freeze(FilePriority); + + const TriState = { + "Unchecked": 0, + "Checked": 1, + "Partial": 2 + }; + Object.freeze(TriState); + + const FileTree = new Class({ + root: null, + nodeMap: {}, + + setRoot: function(root) { + this.root = root; + this.generateNodeMap(root); + + if (this.root.isFolder) + this.root.calculateSize(); + }, + + getRoot: function() { + return this.root; + }, + + generateNodeMap: function(node) { + // don't store root node in map + if (node.root !== null) { + this.nodeMap[node.rowId] = node; } - const isIgnored = (node.priority === FilePriority.Ignored); - if (!isIgnored) { - remaining += node.remaining; - progress += (node.progress * node.size); - availability += (node.availability * node.size); - } - }.bind(this)); - - this.size = size; - this.remaining = remaining; - this.checked = checked; - this.progress = (progress / size); - this.priority = priority; - this.availability = (availability / size); - } -}); + node.children.each(function(child) { + this.generateNodeMap(child); + }.bind(this)); + }, + + getNode: function(rowId) { + return (this.nodeMap[rowId] === undefined) + ? null + : this.nodeMap[rowId]; + }, + + getRowId: function(node) { + return node.rowId; + }, + + /** + * Returns the nodes in dfs order + */ + toArray: function() { + const nodes = []; + this.root.children.each(function(node) { + this._getArrayOfNodes(node, nodes); + }.bind(this)); + return nodes; + }, + + _getArrayOfNodes: function(node, array) { + array.push(node); + node.children.each(function(child) { + this._getArrayOfNodes(child, array); + }.bind(this)); + } + }); + + const FileNode = new Class({ + name: "", + rowId: null, + size: 0, + checked: TriState.Unchecked, + remaining: 0, + progress: 0, + priority: FilePriority.Normal, + availability: 0, + depth: 0, + root: null, + data: null, + isFolder: false, + children: [], + }); + + const FolderNode = new Class({ + Extends: FileNode, + + initialize: function() { + this.isFolder = true; + }, + + addChild(node) { + this.children.push(node); + }, + + /** + * Recursively calculate size of node and its children + */ + calculateSize: function() { + let size = 0; + let remaining = 0; + let progress = 0; + let availability = 0; + let checked = TriState.Unchecked; + let priority = FilePriority.Normal; + + let isFirstFile = true; + + this.children.each(function(node) { + if (node.isFolder) + node.calculateSize(); + + size += node.size; + + if (isFirstFile) { + priority = node.priority; + checked = node.checked; + isFirstFile = false; + } + else { + if (priority !== node.priority) + priority = FilePriority.Mixed; + if (checked !== node.checked) + checked = TriState.Partial; + } + + const isIgnored = (node.priority === FilePriority.Ignored); + if (!isIgnored) { + remaining += node.remaining; + progress += (node.progress * node.size); + availability += (node.availability * node.size); + } + }.bind(this)); + + this.size = size; + this.remaining = remaining; + this.checked = checked; + this.progress = (progress / size); + this.priority = priority; + this.availability = (availability / size); + } + }); + + return exports(); +})(); diff --git a/src/webui/www/private/scripts/filesystem.js b/src/webui/www/private/scripts/filesystem.js index 5a9df7d8e..0cd87241f 100644 --- a/src/webui/www/private/scripts/filesystem.js +++ b/src/webui/www/private/scripts/filesystem.js @@ -30,32 +30,49 @@ // This file is the JavaScript implementation of base/utils/fs.cpp -const QB_EXT = '.!qB'; -const PathSeparator = '/'; - -/** - * Returns the file extension part of a file name. - */ -function fileExtension(filename) { - const name = filename.endsWith(QB_EXT) - ? filename.substring(0, filename.length - QB_EXT.length) - : filename; - const pointIndex = name.lastIndexOf('.'); - if (pointIndex === -1) - return ''; - return name.substring(pointIndex + 1); +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; } -function fileName(filepath) { - const slashIndex = filepath.lastIndexOf(PathSeparator); - if (slashIndex === -1) - return filepath; - return filepath.substring(slashIndex + 1); -} +window.qBittorrent.Filesystem = (function() { + const exports = function() { + return { + PathSeparator: PathSeparator, + fileExtension: fileExtension, + fileName: fileName, + folderName: folderName + }; + }; -function folderName(filepath) { - const slashIndex = filepath.lastIndexOf(PathSeparator); - if (slashIndex === -1) - return filepath; - return filepath.substring(0, slashIndex); -} + const QB_EXT = '.!qB'; + const PathSeparator = '/'; + + /** + * Returns the file extension part of a file name. + */ + const fileExtension = function(filename) { + const name = filename.endsWith(QB_EXT) + ? filename.substring(0, filename.length - QB_EXT.length) + : filename; + const pointIndex = name.lastIndexOf('.'); + if (pointIndex === -1) + return ''; + return name.substring(pointIndex + 1); + }; + + const fileName = function(filepath) { + const slashIndex = filepath.lastIndexOf(PathSeparator); + if (slashIndex === -1) + return filepath; + return filepath.substring(slashIndex + 1); + }; + + const folderName = function(filepath) { + const slashIndex = filepath.lastIndexOf(PathSeparator); + if (slashIndex === -1) + return filepath; + return filepath.substring(0, slashIndex); + }; + + return exports(); +})(); diff --git a/src/webui/www/private/scripts/misc.js b/src/webui/www/private/scripts/misc.js index 32f4dc24d..1671f1525 100644 --- a/src/webui/www/private/scripts/misc.js +++ b/src/webui/www/private/scripts/misc.js @@ -28,166 +28,188 @@ 'use strict'; -/* - * JS counterpart of the function in src/misc.cpp - */ -function friendlyUnit(value, isSpeed) { - const units = [ - "QBT_TR(B)QBT_TR[CONTEXT=misc]", - "QBT_TR(KiB)QBT_TR[CONTEXT=misc]", - "QBT_TR(MiB)QBT_TR[CONTEXT=misc]", - "QBT_TR(GiB)QBT_TR[CONTEXT=misc]", - "QBT_TR(TiB)QBT_TR[CONTEXT=misc]", - "QBT_TR(PiB)QBT_TR[CONTEXT=misc]", - "QBT_TR(EiB)QBT_TR[CONTEXT=misc]" - ]; - - if ((value === undefined) || (value === null) || (value < 0)) - return "QBT_TR(Unknown)QBT_TR[CONTEXT=misc]"; - - let i = 0; - while (value >= 1024.0 && i < 6) { - value /= 1024.0; - ++i; - } +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} - function friendlyUnitPrecision(sizeUnit) { - if (sizeUnit <= 2) return 1; // KiB, MiB - else if (sizeUnit === 3) return 2; // GiB - else return 3; // TiB, PiB, EiB - } +window.qBittorrent.Misc = (function() { + const exports = function() { + return { + friendlyUnit: friendlyUnit, + friendlyDuration: friendlyDuration, + friendlyPercentage: friendlyPercentage, + friendlyFloat: friendlyFloat, + parseHtmlLinks: parseHtmlLinks, + escapeHtml: escapeHtml, + safeTrim: safeTrim, + toFixedPointString: toFixedPointString, + containsAllTerms: containsAllTerms + }; + }; + + /* + * JS counterpart of the function in src/misc.cpp + */ + const friendlyUnit = function(value, isSpeed) { + const units = [ + "QBT_TR(B)QBT_TR[CONTEXT=misc]", + "QBT_TR(KiB)QBT_TR[CONTEXT=misc]", + "QBT_TR(MiB)QBT_TR[CONTEXT=misc]", + "QBT_TR(GiB)QBT_TR[CONTEXT=misc]", + "QBT_TR(TiB)QBT_TR[CONTEXT=misc]", + "QBT_TR(PiB)QBT_TR[CONTEXT=misc]", + "QBT_TR(EiB)QBT_TR[CONTEXT=misc]" + ]; + + if ((value === undefined) || (value === null) || (value < 0)) + return "QBT_TR(Unknown)QBT_TR[CONTEXT=misc]"; + + let i = 0; + while (value >= 1024.0 && i < 6) { + value /= 1024.0; + ++i; + } - let ret; - if (i === 0) - ret = value + " " + units[i]; - else { - const precision = friendlyUnitPrecision(i); - const offset = Math.pow(10, precision); - // Don't round up - ret = (Math.floor(offset * value) / offset).toFixed(precision) + " " + units[i]; - } + function friendlyUnitPrecision(sizeUnit) { + if (sizeUnit <= 2) return 1; // KiB, MiB + else if (sizeUnit === 3) return 2; // GiB + else return 3; // TiB, PiB, EiB + } - if (isSpeed) - ret += "QBT_TR(/s)QBT_TR[CONTEXT=misc]"; - return ret; -} + let ret; + if (i === 0) + ret = value + " " + units[i]; + else { + const precision = friendlyUnitPrecision(i); + const offset = Math.pow(10, precision); + // Don't round up + ret = (Math.floor(offset * value) / offset).toFixed(precision) + " " + units[i]; + } -/* - * JS counterpart of the function in src/misc.cpp - */ -function friendlyDuration(seconds) { - const MAX_ETA = 8640000; - if (seconds < 0 || seconds >= MAX_ETA) - return "∞"; - if (seconds === 0) - return "0"; - if (seconds < 60) - return "QBT_TR(< 1m)QBT_TR[CONTEXT=misc]"; - let minutes = seconds / 60; - if (minutes < 60) - return "QBT_TR(%1m)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(minutes)); - let hours = minutes / 60; - minutes = minutes % 60; - if (hours < 24) - return "QBT_TR(%1h %2m)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(hours)).replace("%2", parseInt(minutes)); - const days = hours / 24; - hours = hours % 24; - if (days < 100) - return "QBT_TR(%1d %2h)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(days)).replace("%2", parseInt(hours)); - return "∞"; -} + if (isSpeed) + ret += "QBT_TR(/s)QBT_TR[CONTEXT=misc]"; + return ret; + } -function friendlyPercentage(value) { - let percentage = (value * 100).round(1); - if (isNaN(percentage) || (percentage < 0)) - percentage = 0; - if (percentage > 100) - percentage = 100; - return percentage.toFixed(1) + "%"; -} + /* + * JS counterpart of the function in src/misc.cpp + */ + const friendlyDuration = function(seconds) { + const MAX_ETA = 8640000; + if (seconds < 0 || seconds >= MAX_ETA) + return "∞"; + if (seconds === 0) + return "0"; + if (seconds < 60) + return "QBT_TR(< 1m)QBT_TR[CONTEXT=misc]"; + let minutes = seconds / 60; + if (minutes < 60) + return "QBT_TR(%1m)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(minutes)); + let hours = minutes / 60; + minutes = minutes % 60; + if (hours < 24) + return "QBT_TR(%1h %2m)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(hours)).replace("%2", parseInt(minutes)); + const days = hours / 24; + hours = hours % 24; + if (days < 100) + return "QBT_TR(%1d %2h)QBT_TR[CONTEXT=misc]".replace("%1", parseInt(days)).replace("%2", parseInt(hours)); + return "∞"; + } -function friendlyFloat(value, precision) { - return parseFloat(value).toFixed(precision); -} + const friendlyPercentage = function(value) { + let percentage = (value * 100).round(1); + if (isNaN(percentage) || (percentage < 0)) + percentage = 0; + if (percentage > 100) + percentage = 100; + return percentage.toFixed(1) + "%"; + } -/* - * From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString - */ -if (!Date.prototype.toISOString) { - (function() { + const friendlyFloat = function(value, precision) { + return parseFloat(value).toFixed(precision); + } - function pad(number) { - if (number < 10) { - return '0' + number; + /* + * From: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString + */ + if (!Date.prototype.toISOString) { + (function() { + + function pad(number) { + if (number < 10) { + return '0' + number; + } + return number; } - return number; - } - - Date.prototype.toISOString = function() { - return this.getUTCFullYear() - + '-' + pad(this.getUTCMonth() + 1) - + '-' + pad(this.getUTCDate()) - + 'T' + pad(this.getUTCHours()) - + ':' + pad(this.getUTCMinutes()) - + ':' + pad(this.getUTCSeconds()) - + '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) - + 'Z'; - }; - }()); -} + Date.prototype.toISOString = function() { + return this.getUTCFullYear() + + '-' + pad(this.getUTCMonth() + 1) + + '-' + pad(this.getUTCDate()) + + 'T' + pad(this.getUTCHours()) + + ':' + pad(this.getUTCMinutes()) + + ':' + pad(this.getUTCSeconds()) + + '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice(2, 5) + + 'Z'; + }; + + }()); + } -/* - * JS counterpart of the function in src/misc.cpp - */ -function parseHtmlLinks(text) { - const exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; - return text.replace(exp, "$1"); -} + /* + * JS counterpart of the function in src/misc.cpp + */ + const parseHtmlLinks = function(text) { + const exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + return text.replace(exp, "$1"); + } -function escapeHtml(str) { - const div = document.createElement('div'); - div.appendChild(document.createTextNode(str)); - return div.innerHTML; -} + const escapeHtml = function(str) { + const div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } -function safeTrim(value) { - try { - return value.trim(); + const safeTrim = function(value) { + try { + return value.trim(); + } + catch (e) { + if (e instanceof TypeError) + return ""; + throw e; + } } - catch (e) { - if (e instanceof TypeError) - return ""; - throw e; + + const toFixedPointString = function(number, digits) { + // Do not round up number + const power = Math.pow(10, digits); + return (Math.floor(power * number) / power).toFixed(digits); } -} -function toFixedPointString(number, digits) { - // Do not round up number - const power = Math.pow(10, digits); - return (Math.floor(power * number) / power).toFixed(digits); -} + /** + * + * @param {String} text the text to search + * @param {Array} terms terms to search for within the text + * @returns {Boolean} true if all terms match the text, false otherwise + */ + const containsAllTerms = function(text, terms) { + const textToSearch = text.toLowerCase(); + return terms.every((function(term) { + const isTermRequired = (term[0] === '+'); + const isTermExcluded = (term[0] === '-'); + if (isTermRequired || isTermExcluded) { + // ignore lonely +/- + if (term.length === 1) + return true; + + term = term.substring(1); + } -/** - * - * @param {String} text the text to search - * @param {Array} terms terms to search for within the text - * @returns {Boolean} true if all terms match the text, false otherwise - */ -function containsAllTerms(text, terms) { - const textToSearch = text.toLowerCase(); - return terms.every((function(term) { - const isTermRequired = (term[0] === '+'); - const isTermExcluded = (term[0] === '-'); - if (isTermRequired || isTermExcluded) { - // ignore lonely +/- - if (term.length === 1) - return true; - - term = term.substring(1); - } + const textContainsTerm = (textToSearch.indexOf(term) !== -1); + return isTermExcluded ? !textContainsTerm : textContainsTerm; + })); + } - const textContainsTerm = (textToSearch.indexOf(term) !== -1); - return isTermExcluded ? !textContainsTerm : textContainsTerm; - })); -} + return exports(); +})(); diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index 9bae8c74f..7941b2168 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -38,7 +38,7 @@ ----------------------------------------------------------------- */ 'use strict'; -const LocalPreferences = new LocalPreferencesClass(); +const LocalPreferences = new window.qBittorrent.LocalPreferences.LocalPreferencesClass(); let saveWindowSize = function() {}; let loadWindowWidth = function() {}; diff --git a/src/webui/www/private/scripts/preferences.js b/src/webui/www/private/scripts/preferences.js index 9d21fa4ab..87606bad7 100644 --- a/src/webui/www/private/scripts/preferences.js +++ b/src/webui/www/private/scripts/preferences.js @@ -28,20 +28,34 @@ 'use strict'; -const LocalPreferencesClass = new Class({ - get: function(key, defaultValue) { - const value = localStorage.getItem(key); - return ((value === null) && (defaultValue !== undefined)) - ? defaultValue - : value; - }, +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} - set: function(key, value) { - try { - localStorage.setItem(key, value); - } - catch (err) { - console.error(err); +window.qBittorrent.LocalPreferences = (function() { + const exports = function() { + return { + LocalPreferencesClass: LocalPreferencesClass + }; + }; + + const LocalPreferencesClass = new Class({ + get: function(key, defaultValue) { + const value = localStorage.getItem(key); + return ((value === null) && (defaultValue !== undefined)) + ? defaultValue + : value; + }, + + set: function(key, value) { + try { + localStorage.setItem(key, value); + } + catch (err) { + console.error(err); + } } - } -}) + }) + + return exports(); +})(); diff --git a/src/webui/www/private/scripts/progressbar.js b/src/webui/www/private/scripts/progressbar.js index 959c78be5..18c252840 100644 --- a/src/webui/www/private/scripts/progressbar.js +++ b/src/webui/www/private/scripts/progressbar.js @@ -28,113 +28,126 @@ 'use strict'; -const ProgressBar = new Class({ - initialize: function(value, parameters) { - const vals = { - 'id': 'progressbar_' + (ProgressBars++), - 'value': $pick(value, 0), - 'width': 0, - 'height': 0, - 'darkbg': '#006', - 'darkfg': '#fff', - 'lightbg': '#fff', - 'lightfg': '#000' +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.ProgressBar = (function() { + const exports = function() { + return { + ProgressBar: ProgressBar }; - if (parameters && $type(parameters) == 'object') $extend(vals, parameters); - if (vals.height < 12) vals.height = 12; - const obj = new Element('div', { - 'id': vals.id, - 'class': 'progressbar_wrapper', - 'styles': { - 'border': '1px solid #000', - 'width': vals.width, - 'height': vals.height, - 'position': 'relative', - 'margin': '0 auto' - } - }); - obj.vals = vals; - obj.vals.value = $pick(value, 0); // Fix by Chris - obj.vals.dark = new Element('div', { - 'id': vals.id + '_dark', - 'class': 'progressbar_dark', - 'styles': { - 'width': vals.width, - 'height': vals.height, - 'background': vals.darkbg, - 'color': vals.darkfg, - 'position': 'absolute', - 'text-align': 'center', - 'left': 0, - 'top': 0, - 'line-height': vals.height - } - }); - obj.vals.light = new Element('div', { - 'id': vals.id + '_light', - 'class': 'progressbar_light', - 'styles': { - 'width': vals.width, - 'height': vals.height, - 'background': vals.lightbg, - 'color': vals.lightfg, - 'position': 'absolute', - 'text-align': 'center', - 'left': 0, - 'top': 0, - 'line-height': vals.height - } - }); - obj.appendChild(obj.vals.dark); - obj.appendChild(obj.vals.light); - obj.getValue = ProgressBar_getValue; - obj.setValue = ProgressBar_setValue; - obj.setWidth = ProgressBar_setWidth; - if (vals.width) obj.setValue(vals.value); - else setTimeout('ProgressBar_checkForParent("' + obj.id + '")', 1); - return obj; - } -}); + }; -function ProgressBar_getValue() { - return this.vals.value; -} + let ProgressBars = 0; + const ProgressBar = new Class({ + initialize: function(value, parameters) { + const vals = { + 'id': 'progressbar_' + (ProgressBars++), + 'value': $pick(value, 0), + 'width': 0, + 'height': 0, + 'darkbg': '#006', + 'darkfg': '#fff', + 'lightbg': '#fff', + 'lightfg': '#000' + }; + if (parameters && $type(parameters) == 'object') $extend(vals, parameters); + if (vals.height < 12) vals.height = 12; + const obj = new Element('div', { + 'id': vals.id, + 'class': 'progressbar_wrapper', + 'styles': { + 'border': '1px solid #000', + 'width': vals.width, + 'height': vals.height, + 'position': 'relative', + 'margin': '0 auto' + } + }); + obj.vals = vals; + obj.vals.value = $pick(value, 0); // Fix by Chris + obj.vals.dark = new Element('div', { + 'id': vals.id + '_dark', + 'class': 'progressbar_dark', + 'styles': { + 'width': vals.width, + 'height': vals.height, + 'background': vals.darkbg, + 'color': vals.darkfg, + 'position': 'absolute', + 'text-align': 'center', + 'left': 0, + 'top': 0, + 'line-height': vals.height + } + }); + obj.vals.light = new Element('div', { + 'id': vals.id + '_light', + 'class': 'progressbar_light', + 'styles': { + 'width': vals.width, + 'height': vals.height, + 'background': vals.lightbg, + 'color': vals.lightfg, + 'position': 'absolute', + 'text-align': 'center', + 'left': 0, + 'top': 0, + 'line-height': vals.height + } + }); + obj.appendChild(obj.vals.dark); + obj.appendChild(obj.vals.light); + obj.getValue = ProgressBar_getValue; + obj.setValue = ProgressBar_setValue; + obj.setWidth = ProgressBar_setWidth; + if (vals.width) obj.setValue(vals.value); + else setTimeout('ProgressBar_checkForParent("' + obj.id + '")', 1); + return obj; + } + }); -function ProgressBar_setValue(value) { - value = parseFloat(value); - if (isNaN(value)) value = 0; - if (value > 100) value = 100; - if (value < 0) value = 0; - this.vals.value = value; - this.vals.dark.empty(); - this.vals.light.empty(); - this.vals.dark.appendText(value.round(1).toFixed(1) + '%'); - this.vals.light.appendText(value.round(1).toFixed(1) + '%'); - const r = parseInt(this.vals.width * (value / 100)); - this.vals.dark.setStyle('clip', 'rect(0,' + r + 'px,' + this.vals.height + 'px,0)'); - this.vals.light.setStyle('clip', 'rect(0,' + this.vals.width + 'px,' + this.vals.height + 'px,' + r + 'px)'); -} + function ProgressBar_getValue() { + return this.vals.value; + } -function ProgressBar_setWidth(value) { - if (this.vals.width !== value) { - this.vals.width = value; - this.setStyle('width', value); - this.vals.dark.setStyle('width', value); - this.vals.light.setStyle('width', value); - this.setValue(this.vals.value); + function ProgressBar_setValue(value) { + value = parseFloat(value); + if (isNaN(value)) value = 0; + if (value > 100) value = 100; + if (value < 0) value = 0; + this.vals.value = value; + this.vals.dark.empty(); + this.vals.light.empty(); + this.vals.dark.appendText(value.round(1).toFixed(1) + '%'); + this.vals.light.appendText(value.round(1).toFixed(1) + '%'); + const r = parseInt(this.vals.width * (value / 100)); + this.vals.dark.setStyle('clip', 'rect(0,' + r + 'px,' + this.vals.height + 'px,0)'); + this.vals.light.setStyle('clip', 'rect(0,' + this.vals.width + 'px,' + this.vals.height + 'px,' + r + 'px)'); } -} -function ProgressBar_checkForParent(id) { - const obj = $(id); - if (!obj) return; - if (!obj.parentNode) return setTimeout('ProgressBar_checkForParent("' + id + '")', 1); - obj.setStyle('width', '100%'); - const w = obj.offsetWidth; - obj.vals.dark.setStyle('width', w); - obj.vals.light.setStyle('width', w); - obj.vals.width = w; - obj.setValue(obj.vals.value); -} + function ProgressBar_setWidth(value) { + if (this.vals.width !== value) { + this.vals.width = value; + this.setStyle('width', value); + this.vals.dark.setStyle('width', value); + this.vals.light.setStyle('width', value); + this.setValue(this.vals.value); + } + } + + function ProgressBar_checkForParent(id) { + const obj = $(id); + if (!obj) return; + if (!obj.parentNode) return setTimeout('ProgressBar_checkForParent("' + id + '")', 1); + obj.setStyle('width', '100%'); + const w = obj.offsetWidth; + obj.vals.dark.setStyle('width', w); + obj.vals.light.setStyle('width', w); + obj.vals.width = w; + obj.setValue(obj.vals.value); + } -let ProgressBars = 0; + return exports(); +})(); diff --git a/src/webui/www/private/scripts/prop-files.js b/src/webui/www/private/scripts/prop-files.js index 485b54f2a..cee8ef857 100644 --- a/src/webui/www/private/scripts/prop-files.js +++ b/src/webui/www/private/scripts/prop-files.js @@ -28,647 +28,672 @@ 'use strict'; -let is_seed = true; -this.current_hash = ""; - -const normalizePriority = function(priority) { - switch (priority) { - case FilePriority.Ignored: - case FilePriority.Normal: - case FilePriority.High: - case FilePriority.Maximum: - case FilePriority.Mixed: - return priority; - default: - return FilePriority.Normal; - } -}; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -const getAllChildren = function(id, fileId) { - const node = torrentFilesTable.getNode(id); - if (!node.isFolder) { +window.qBittorrent.PropFiles = (function() { + const exports = function() { return { - rowIds: [id], - fileIds: [fileId] + normalizePriority: normalizePriority, + isDownloadCheckboxExists: isDownloadCheckboxExists, + createDownloadCheckbox: createDownloadCheckbox, + updateDownloadCheckbox: updateDownloadCheckbox, + isPriorityComboExists: isPriorityComboExists, + createPriorityCombo: createPriorityCombo, + updatePriorityCombo: updatePriorityCombo, + updateData: updateData, + collapseIconClicked: collapseIconClicked }; - } - - const rowIds = []; - const fileIds = []; + }; - const getChildFiles = function(node) { - if (node.isFolder) { - node.children.each(function(child) { - getChildFiles(child); - }); + const torrentFilesTable = new window.qBittorrent.DynamicTable.TorrentFilesTable(); + const FilePriority = window.qBittorrent.FileTree.FilePriority; + const TriState = window.qBittorrent.FileTree.TriState; + let is_seed = true; + let current_hash = ""; + + const normalizePriority = function(priority) { + switch (priority) { + case FilePriority.Ignored: + case FilePriority.Normal: + case FilePriority.High: + case FilePriority.Maximum: + case FilePriority.Mixed: + return priority; + default: + return FilePriority.Normal; } - else { - rowIds.push(node.data.rowId); - fileIds.push(node.data.fileId); + }; + + const getAllChildren = function(id, fileId) { + const node = torrentFilesTable.getNode(id); + if (!node.isFolder) { + return { + rowIds: [id], + fileIds: [fileId] + }; } + + const rowIds = []; + const fileIds = []; + + const getChildFiles = function(node) { + if (node.isFolder) { + node.children.each(function(child) { + getChildFiles(child); + }); + } + else { + rowIds.push(node.data.rowId); + fileIds.push(node.data.fileId); + } + }; + + node.children.each(function(child) { + getChildFiles(child); + }); + + return { + rowIds: rowIds, + fileIds: fileIds + }; }; - node.children.each(function(child) { - getChildFiles(child); - }); + const fileCheckboxClicked = function(e) { + e.stopPropagation(); - return { - rowIds: rowIds, - fileIds: fileIds + const checkbox = e.target; + const priority = checkbox.checked ? FilePriority.Normal : FilePriority.Ignored; + const id = checkbox.get('data-id'); + const fileId = checkbox.get('data-file-id'); + + const rows = getAllChildren(id, fileId); + + setFilePriority(rows.rowIds, rows.fileIds, priority); + updateGlobalCheckbox(); }; -}; -const fileCheckboxClicked = function(e) { - e.stopPropagation(); + const fileComboboxChanged = function(e) { + const combobox = e.target; + const priority = combobox.value; + const id = combobox.get('data-id'); + const fileId = combobox.get('data-file-id'); + + const rows = getAllChildren(id, fileId); - const checkbox = e.target; - const priority = checkbox.checked ? FilePriority.Normal : FilePriority.Ignored; - const id = checkbox.get('data-id'); - const fileId = checkbox.get('data-file-id'); + setFilePriority(rows.rowIds, rows.fileIds, priority); + updateGlobalCheckbox(); + }; + + const isDownloadCheckboxExists = function(id) { + return ($('cbPrio' + id) !== null); + }; - const rows = getAllChildren(id, fileId); + const createDownloadCheckbox = function(id, fileId, checked) { + const checkbox = new Element('input'); + checkbox.set('type', 'checkbox'); + checkbox.set('id', 'cbPrio' + id); + checkbox.set('data-id', id); + checkbox.set('data-file-id', fileId); + checkbox.set('class', 'DownloadedCB'); + checkbox.addEvent('click', fileCheckboxClicked); + + updateCheckbox(checkbox, checked); + return checkbox; + }; - setFilePriority(rows.rowIds, rows.fileIds, priority); - updateGlobalCheckbox(); -}; + const updateDownloadCheckbox = function(id, checked) { + const checkbox = $('cbPrio' + id); + updateCheckbox(checkbox, checked); + }; -const fileComboboxChanged = function(e) { - const combobox = e.target; - const priority = combobox.value; - const id = combobox.get('data-id'); - const fileId = combobox.get('data-file-id'); + const updateCheckbox = function(checkbox, checked) { + switch (checked) { + case TriState.Checked: + setCheckboxChecked(checkbox); + break; + case TriState.Unchecked: + setCheckboxUnchecked(checkbox); + break; + case TriState.Partial: + setCheckboxPartial(checkbox); + break; + } + } - const rows = getAllChildren(id, fileId); + const isPriorityComboExists = function(id) { + return ($('comboPrio' + id) !== null); + }; - setFilePriority(rows.rowIds, rows.fileIds, priority); - updateGlobalCheckbox(); -}; + const createPriorityOptionElement = function(priority, selected, html) { + const elem = new Element('option'); + elem.set('value', priority.toString()); + elem.set('html', html); + if (selected) + elem.setAttribute('selected', ''); + return elem; + }; + + const createPriorityCombo = function(id, fileId, selectedPriority) { + const select = new Element('select'); + select.set('id', 'comboPrio' + id); + select.set('data-id', id); + select.set('data-file-id', fileId); + 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); + + // "Mixed" priority is for display only; it shouldn't be selectable + const mixedPriorityOption = createPriorityOptionElement(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), 'QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]'); + mixedPriorityOption.set('disabled', true); + mixedPriorityOption.injectInside(select); + + return select; + }; + + const updatePriorityCombo = function(id, selectedPriority) { + const combobox = $('comboPrio' + id); + + if (parseInt(combobox.value) !== selectedPriority) + selectComboboxPriority(combobox, selectedPriority); + + if (combobox.disabled !== is_seed) + combobox.disabled = is_seed; + }; + + const selectComboboxPriority = function(combobox, priority) { + const options = combobox.options; + for (let i = 0; i < options.length; ++i) { + const option = options[i]; + if (parseInt(option.value) === priority) + option.setAttribute('selected', ''); + else + option.removeAttribute('selected'); + } + + combobox.value = priority; + }; -const isDownloadCheckboxExists = function(id) { - return ($('cbPrio' + id) !== null); -}; + const switchCheckboxState = function(e) { + e.stopPropagation(); -const createDownloadCheckbox = function(id, fileId, checked) { - const checkbox = new Element('input'); - checkbox.set('type', 'checkbox'); - checkbox.set('id', 'cbPrio' + id); - checkbox.set('data-id', id); - checkbox.set('data-file-id', fileId); - checkbox.set('class', 'DownloadedCB'); - checkbox.addEvent('click', fileCheckboxClicked); + const rowIds = []; + const fileIds = []; + let priority = FilePriority.Ignored; + const checkbox = $('tristate_cb'); - updateCheckbox(checkbox, checked); - return checkbox; -}; + if (checkbox.state === "checked") { + setCheckboxUnchecked(checkbox); + // set file priority for all checked to Ignored + torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) { + const rowId = row.rowId; + const fileId = row.full_data.fileId; + const isChecked = (row.full_data.checked === TriState.Checked); + const isFolder = (fileId === -1); + if (!isFolder && isChecked) { + rowIds.push(rowId); + fileIds.push(fileId); + } + }); + } + else { + setCheckboxChecked(checkbox); + priority = FilePriority.Normal; + // set file priority for all unchecked to Normal + torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) { + const rowId = row.rowId; + const fileId = row.full_data.fileId; + const isUnchecked = (row.full_data.checked === TriState.Unchecked); + const isFolder = (fileId === -1); + if (!isFolder && isUnchecked) { + rowIds.push(rowId); + fileIds.push(fileId); + } + }); + } -const updateDownloadCheckbox = function(id, checked) { - const checkbox = $('cbPrio' + id); - updateCheckbox(checkbox, checked); -}; + if (rowIds.length > 0) + setFilePriority(rowIds, fileIds, priority); + }; -const updateCheckbox = function(checkbox, checked) { - switch (checked) { - case TriState.Checked: + const updateGlobalCheckbox = function() { + const checkbox = $('tristate_cb'); + if (isAllCheckboxesChecked()) setCheckboxChecked(checkbox); - break; - case TriState.Unchecked: + else if (isAllCheckboxesUnchecked()) setCheckboxUnchecked(checkbox); - break; - case TriState.Partial: + else setCheckboxPartial(checkbox); - break; - } -} + }; -const isPriorityComboExists = function(id) { - return ($('comboPrio' + id) !== null); -}; - -const createPriorityOptionElement = function(priority, selected, html) { - const elem = new Element('option'); - elem.set('value', priority.toString()); - elem.set('html', html); - if (selected) - elem.setAttribute('selected', ''); - return elem; -}; - -const createPriorityCombo = function(id, fileId, selectedPriority) { - const select = new Element('select'); - select.set('id', 'comboPrio' + id); - select.set('data-id', id); - select.set('data-file-id', fileId); - 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); - - // "Mixed" priority is for display only; it shouldn't be selectable - const mixedPriorityOption = createPriorityOptionElement(FilePriority.Mixed, (FilePriority.Mixed === selectedPriority), 'QBT_TR(Mixed)QBT_TR[CONTEXT=PropListDelegate]'); - mixedPriorityOption.set('disabled', true); - mixedPriorityOption.injectInside(select); - - return select; -}; - -const updatePriorityCombo = function(id, selectedPriority) { - const combobox = $('comboPrio' + id); - - if (parseInt(combobox.value) !== selectedPriority) - selectComboboxPriority(combobox, selectedPriority); - - if (combobox.disabled !== is_seed) - combobox.disabled = is_seed; -}; - -const selectComboboxPriority = function(combobox, priority) { - const options = combobox.options; - for (let i = 0; i < options.length; ++i) { - const option = options[i]; - if (parseInt(option.value) === priority) - option.setAttribute('selected', ''); - else - option.removeAttribute('selected'); - } + const setCheckboxChecked = function(checkbox) { + checkbox.state = "checked"; + checkbox.indeterminate = false; + checkbox.checked = true; + }; - combobox.value = priority; -}; - -const switchCheckboxState = function(e) { - e.stopPropagation(); - - const rowIds = []; - const fileIds = []; - let priority = FilePriority.Ignored; - const checkbox = $('tristate_cb'); - - if (checkbox.state === "checked") { - setCheckboxUnchecked(checkbox); - // set file priority for all checked to Ignored - torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) { - const rowId = row.rowId; - const fileId = row.full_data.fileId; - const isChecked = (row.full_data.checked === TriState.Checked); - const isFolder = (fileId === -1); - if (!isFolder && isChecked) { - rowIds.push(rowId); - fileIds.push(fileId); - } - }); - } - else { - setCheckboxChecked(checkbox); - priority = FilePriority.Normal; - // set file priority for all unchecked to Normal - torrentFilesTable.getFilteredAndSortedRows().forEach(function(row) { - const rowId = row.rowId; - const fileId = row.full_data.fileId; - const isUnchecked = (row.full_data.checked === TriState.Unchecked); - const isFolder = (fileId === -1); - if (!isFolder && isUnchecked) { - rowIds.push(rowId); - fileIds.push(fileId); - } - }); - } + const setCheckboxUnchecked = function(checkbox) { + checkbox.state = "unchecked"; + checkbox.indeterminate = false; + checkbox.checked = false; + }; - if (rowIds.length > 0) - setFilePriority(rowIds, fileIds, priority); -}; - -const updateGlobalCheckbox = function() { - const checkbox = $('tristate_cb'); - if (isAllCheckboxesChecked()) - setCheckboxChecked(checkbox); - else if (isAllCheckboxesUnchecked()) - setCheckboxUnchecked(checkbox); - else - setCheckboxPartial(checkbox); -}; - -const setCheckboxChecked = function(checkbox) { - checkbox.state = "checked"; - checkbox.indeterminate = false; - checkbox.checked = true; -}; - -const setCheckboxUnchecked = function(checkbox) { - checkbox.state = "unchecked"; - checkbox.indeterminate = false; - checkbox.checked = false; -}; - -const setCheckboxPartial = function(checkbox) { - checkbox.state = "partial"; - checkbox.indeterminate = true; -}; - -const isAllCheckboxesChecked = function() { - const checkboxes = $$('input.DownloadedCB'); - for (let i = 0; i < checkboxes.length; ++i) { - if (!checkboxes[i].checked) - return false; - } - return true; -}; - -const isAllCheckboxesUnchecked = function() { - const checkboxes = $$('input.DownloadedCB'); - for (let i = 0; i < checkboxes.length; ++i) { - if (checkboxes[i].checked) - return false; - } - return true; -}; - -const setFilePriority = function(ids, fileIds, priority) { - if (current_hash === "") return; - - clearTimeout(loadTorrentFilesDataTimer); - new Request({ - url: 'api/v2/torrents/filePrio', - method: 'post', - data: { - 'hash': current_hash, - 'id': fileIds.join('|'), - 'priority': priority - }, - onComplete: function() { - loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000); - } - }).send(); + const setCheckboxPartial = function(checkbox) { + checkbox.state = "partial"; + checkbox.indeterminate = true; + }; - const ignore = (priority === FilePriority.Ignored); - ids.forEach(function(_id) { - torrentFilesTable.setIgnored(_id, ignore); + const isAllCheckboxesChecked = function() { + const checkboxes = $$('input.DownloadedCB'); + for (let i = 0; i < checkboxes.length; ++i) { + if (!checkboxes[i].checked) + return false; + } + return true; + }; - const combobox = $('comboPrio' + _id); - if (combobox !== null) - selectComboboxPriority(combobox, priority); - }); + const isAllCheckboxesUnchecked = function() { + const checkboxes = $$('input.DownloadedCB'); + for (let i = 0; i < checkboxes.length; ++i) { + if (checkboxes[i].checked) + return false; + } + return true; + }; - torrentFilesTable.updateTable(false); -}; + const setFilePriority = function(ids, fileIds, priority) { + if (current_hash === "") return; -let loadTorrentFilesDataTimer; -const loadTorrentFilesData = function() { - if ($('prop_files').hasClass('invisible') - || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { - // Tab changed, don't do anything - return; - } - const new_hash = torrentsTable.getCurrentTorrentHash(); - if (new_hash === "") { - torrentFilesTable.clear(); clearTimeout(loadTorrentFilesDataTimer); - loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); - return; - } - let loadedNewTorrent = false; - if (new_hash != current_hash) { - torrentFilesTable.clear(); - current_hash = new_hash; - loadedNewTorrent = true; - } - const url = new URI('api/v2/torrents/files?hash=' + current_hash); - new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onComplete: function() { - clearTimeout(loadTorrentFilesDataTimer); - loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); - }, - onSuccess: function(files) { - clearTimeout(torrentFilesFilterInputTimer); - - if (files.length === 0) { - torrentFilesTable.clear(); - } - else { - handleNewTorrentFiles(files); - if (loadedNewTorrent) - collapseAllNodes(); + new Request({ + url: 'api/v2/torrents/filePrio', + method: 'post', + data: { + 'hash': current_hash, + 'id': fileIds.join('|'), + 'priority': priority + }, + onComplete: function() { + loadTorrentFilesDataTimer = loadTorrentFilesData.delay(1000); } - } - }).send(); -}; - -updateTorrentFilesData = function() { - clearTimeout(loadTorrentFilesDataTimer); - loadTorrentFilesData(); -}; - -const handleNewTorrentFiles = function(files) { - is_seed = (files.length > 0) ? files[0].is_seed : true; - - const rows = files.map(function(file, index) { - let progress = (file.progress * 100).round(1); - if ((progress === 100) && (file.progress < 1)) - progress = 99.9; - - const name = escapeHtml(file.name); - const ignore = (file.priority === FilePriority.Ignored); - const checked = (ignore ? TriState.Unchecked : TriState.Checked); - const remaining = (ignore ? 0 : (file.size * (1.0 - file.progress))); - const row = { - fileId: index, - checked: checked, - fileName: name, - name: fileName(name), - size: file.size, - progress: progress, - priority: normalizePriority(file.priority), - remaining: remaining, - availability: file.availability - }; + }).send(); - return row; - }); + const ignore = (priority === FilePriority.Ignored); + ids.forEach(function(_id) { + torrentFilesTable.setIgnored(_id, ignore); - addRowsToTable(rows); - updateGlobalCheckbox(); -}; + const combobox = $('comboPrio' + _id); + if (combobox !== null) + selectComboboxPriority(combobox, priority); + }); -const addRowsToTable = function(rows) { - const selectedFiles = torrentFilesTable.selectedRowsIds(); - let rowId = 0; + torrentFilesTable.updateTable(false); + }; - const rootNode = new FolderNode(); + let loadTorrentFilesDataTimer; + const loadTorrentFilesData = function() { + if ($('prop_files').hasClass('invisible') + || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { + // Tab changed, don't do anything + return; + } + const new_hash = torrentsTable.getCurrentTorrentHash(); + if (new_hash === "") { + torrentFilesTable.clear(); + clearTimeout(loadTorrentFilesDataTimer); + loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); + return; + } + let loadedNewTorrent = false; + if (new_hash != current_hash) { + torrentFilesTable.clear(); + current_hash = new_hash; + loadedNewTorrent = true; + } + const url = new URI('api/v2/torrents/files?hash=' + current_hash); + new Request.JSON({ + url: url, + noCache: true, + method: 'get', + onComplete: function() { + clearTimeout(loadTorrentFilesDataTimer); + loadTorrentFilesDataTimer = loadTorrentFilesData.delay(5000); + }, + onSuccess: function(files) { + clearTimeout(torrentFilesFilterInputTimer); + + if (files.length === 0) { + torrentFilesTable.clear(); + } + else { + handleNewTorrentFiles(files); + if (loadedNewTorrent) + collapseAllNodes(); + } + } + }).send(); + }; - rows.forEach(function(row) { - let parent = rootNode; - const pathFolders = row.fileName.split(PathSeparator); - pathFolders.pop(); - pathFolders.forEach(function(folderName) { - if (folderName === '.unwanted') - return; + const updateData = function() { + clearTimeout(loadTorrentFilesDataTimer); + loadTorrentFilesData(); + }; + + const handleNewTorrentFiles = function(files) { + is_seed = (files.length > 0) ? files[0].is_seed : true; + + const rows = files.map(function(file, index) { + let progress = (file.progress * 100).round(1); + if ((progress === 100) && (file.progress < 1)) + progress = 99.9; + + const name = window.qBittorrent.Misc.escapeHtml(file.name); + const ignore = (file.priority === FilePriority.Ignored); + const checked = (ignore ? TriState.Unchecked : TriState.Checked); + const remaining = (ignore ? 0 : (file.size * (1.0 - file.progress))); + const row = { + fileId: index, + checked: checked, + fileName: name, + name: window.qBittorrent.Filesystem.fileName(name), + size: file.size, + progress: progress, + priority: normalizePriority(file.priority), + remaining: remaining, + availability: file.availability + }; + + return row; + }); + + addRowsToTable(rows); + updateGlobalCheckbox(); + }; - let parentNode = null; - if (parent.children !== null) { - for (let i = 0; i < parent.children.length; ++i) { - const childFolder = parent.children[i]; - if (childFolder.name === folderName) { - parentNode = childFolder; - break; + const addRowsToTable = function(rows) { + const selectedFiles = torrentFilesTable.selectedRowsIds(); + let rowId = 0; + + const rootNode = new window.qBittorrent.FileTree.FolderNode(); + + rows.forEach(function(row) { + let parent = rootNode; + const pathFolders = row.fileName.split(window.qBittorrent.Filesystem.PathSeparator); + pathFolders.pop(); + pathFolders.forEach(function(folderName) { + if (folderName === '.unwanted') + return; + + let parentNode = null; + if (parent.children !== null) { + for (let i = 0; i < parent.children.length; ++i) { + const childFolder = parent.children[i]; + if (childFolder.name === folderName) { + parentNode = childFolder; + break; + } } } - } - if (parentNode === null) { - parentNode = new FolderNode(); - parentNode.name = folderName; - parentNode.rowId = rowId; - parentNode.root = parent; - parent.addChild(parentNode); - - ++rowId; - } + if (parentNode === null) { + parentNode = new window.qBittorrent.FileTree.FolderNode(); + parentNode.name = folderName; + parentNode.rowId = rowId; + parentNode.root = parent; + parent.addChild(parentNode); + + ++rowId; + } - parent = parentNode; - }); + parent = parentNode; + }); - const isChecked = row.checked ? TriState.Checked : TriState.Unchecked; - const remaining = (row.priority === FilePriority.Ignored) ? 0 : row.remaining; - const childNode = new FileNode(); - childNode.name = row.name; - childNode.rowId = rowId; - childNode.size = row.size; - childNode.checked = isChecked; - childNode.remaining = remaining; - childNode.progress = row.progress; - childNode.priority = row.priority; - childNode.availability = row.availability; - childNode.root = parent; - childNode.data = row; - parent.addChild(childNode); - - ++rowId; - }.bind(this)); - - torrentFilesTable.populateTable(rootNode); - torrentFilesTable.updateTable(false); - torrentFilesTable.altRow(); - - if (selectedFiles.length > 0) - torrentFilesTable.reselectRows(selectedFiles); -}; - -const collapseIconClicked = function(event) { - const id = event.get("data-id"); - const node = torrentFilesTable.getNode(id); - const isCollapsed = (event.parentElement.get("data-collapsed") === "true"); - - if (isCollapsed) - expandNode(node); - else - collapseNode(node); -}; - -const filesPriorityMenuClicked = function(priority) { - const selectedRows = torrentFilesTable.selectedRowsIds(); - if (selectedRows.length === 0) return; - - const rowIds = []; - const fileIds = []; - selectedRows.forEach(function(rowId) { - const elem = $('comboPrio' + rowId); - rowIds.push(rowId); - fileIds.push(elem.get("data-file-id")); - }); + const isChecked = row.checked ? TriState.Checked : TriState.Unchecked; + const remaining = (row.priority === FilePriority.Ignored) ? 0 : row.remaining; + const childNode = new window.qBittorrent.FileTree.FileNode(); + childNode.name = row.name; + childNode.rowId = rowId; + childNode.size = row.size; + childNode.checked = isChecked; + childNode.remaining = remaining; + childNode.progress = row.progress; + childNode.priority = row.priority; + childNode.availability = row.availability; + childNode.root = parent; + childNode.data = row; + parent.addChild(childNode); + + ++rowId; + }.bind(this)); + + torrentFilesTable.populateTable(rootNode); + torrentFilesTable.updateTable(false); + torrentFilesTable.altRow(); + + if (selectedFiles.length > 0) + torrentFilesTable.reselectRows(selectedFiles); + }; - const uniqueRowIds = {}; - const uniqueFileIds = {}; - for (let i = 0; i < rowIds.length; ++i) { - const rows = getAllChildren(rowIds[i], fileIds[i]); - rows.rowIds.forEach(function(rowId) { - uniqueRowIds[rowId] = true; - }); - rows.fileIds.forEach(function(fileId) { - uniqueFileIds[fileId] = true; + const collapseIconClicked = function(event) { + const id = event.get("data-id"); + const node = torrentFilesTable.getNode(id); + const isCollapsed = (event.parentElement.get("data-collapsed") === "true"); + + if (isCollapsed) + expandNode(node); + else + collapseNode(node); + }; + + const filesPriorityMenuClicked = function(priority) { + const selectedRows = torrentFilesTable.selectedRowsIds(); + if (selectedRows.length === 0) return; + + const rowIds = []; + const fileIds = []; + selectedRows.forEach(function(rowId) { + const elem = $('comboPrio' + rowId); + rowIds.push(rowId); + fileIds.push(elem.get("data-file-id")); }); - } - setFilePriority(Object.keys(uniqueRowIds), Object.keys(uniqueFileIds), priority); -}; + const uniqueRowIds = {}; + const uniqueFileIds = {}; + for (let i = 0; i < rowIds.length; ++i) { + const rows = getAllChildren(rowIds[i], fileIds[i]); + rows.rowIds.forEach(function(rowId) { + uniqueRowIds[rowId] = true; + }); + rows.fileIds.forEach(function(fileId) { + uniqueFileIds[fileId] = true; + }); + } -const torrentFilesContextMenu = new ContextMenu({ - targets: '#torrentFilesTableDiv tr', - menu: 'torrentFilesMenu', - actions: { + setFilePriority(Object.keys(uniqueRowIds), Object.keys(uniqueFileIds), priority); + }; - FilePrioIgnore: function(element, ref) { - filesPriorityMenuClicked(FilePriority.Ignored); - }, - FilePrioNormal: function(element, ref) { - filesPriorityMenuClicked(FilePriority.Normal); + const torrentFilesContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ + targets: '#torrentFilesTableDiv tr', + menu: 'torrentFilesMenu', + actions: { + + FilePrioIgnore: function(element, ref) { + filesPriorityMenuClicked(FilePriority.Ignored); + }, + FilePrioNormal: function(element, ref) { + filesPriorityMenuClicked(FilePriority.Normal); + }, + FilePrioHigh: function(element, ref) { + filesPriorityMenuClicked(FilePriority.High); + }, + FilePrioMaximum: function(element, ref) { + filesPriorityMenuClicked(FilePriority.Maximum); + } }, - FilePrioHigh: function(element, ref) { - filesPriorityMenuClicked(FilePriority.High); + offsets: { + x: -15, + y: 2 }, - FilePrioMaximum: function(element, ref) { - filesPriorityMenuClicked(FilePriority.Maximum); + onShow: function() { + if (is_seed) + this.hideItem('FilePrio'); + else + this.showItem('FilePrio'); } - }, - offsets: { - x: -15, - y: 2 - }, - onShow: function() { - if (is_seed) - this.hideItem('FilePrio'); - else - this.showItem('FilePrio'); + }); + + torrentFilesTable.setup('torrentFilesTableDiv', 'torrentFilesTableFixedHeaderDiv', torrentFilesContextMenu); + // inject checkbox into table header + const tableHeaders = $$('#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th'); + if (tableHeaders.length > 0) { + const checkbox = new Element('input'); + checkbox.set('type', 'checkbox'); + checkbox.set('id', 'tristate_cb'); + checkbox.addEvent('click', switchCheckboxState); + + const checkboxTH = tableHeaders[0]; + checkbox.injectInside(checkboxTH); } -}); - -torrentFilesTable.setup('torrentFilesTableDiv', 'torrentFilesTableFixedHeaderDiv', torrentFilesContextMenu); -// inject checkbox into table header -const tableHeaders = $$('#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th'); -if (tableHeaders.length > 0) { - const checkbox = new Element('input'); - checkbox.set('type', 'checkbox'); - checkbox.set('id', 'tristate_cb'); - checkbox.addEvent('click', switchCheckboxState); - - const checkboxTH = tableHeaders[0]; - checkbox.injectInside(checkboxTH); -} -// default sort by name column -if (torrentFilesTable.getSortedColumn() === null) - torrentFilesTable.setSortedColumn('name'); - -let prevTorrentFilesFilterValue; -let torrentFilesFilterInputTimer = null; -// listen for changes to torrentFilesFilterInput -$('torrentFilesFilterInput').addEvent('input', function() { - const value = $('torrentFilesFilterInput').get("value"); - if (value !== prevTorrentFilesFilterValue) { - prevTorrentFilesFilterValue = value; - torrentFilesTable.setFilter(value); - clearTimeout(torrentFilesFilterInputTimer); - torrentFilesFilterInputTimer = setTimeout(function() { - if (current_hash === "") return; - torrentFilesTable.updateTable(false); - - if (value.trim() === "") - collapseAllNodes(); - else - expandAllNodes(); - }, 400); + // default sort by name column + if (torrentFilesTable.getSortedColumn() === null) + torrentFilesTable.setSortedColumn('name'); + + let prevTorrentFilesFilterValue; + let torrentFilesFilterInputTimer = null; + // listen for changes to torrentFilesFilterInput + $('torrentFilesFilterInput').addEvent('input', function() { + const value = $('torrentFilesFilterInput').get("value"); + if (value !== prevTorrentFilesFilterValue) { + prevTorrentFilesFilterValue = value; + torrentFilesTable.setFilter(value); + clearTimeout(torrentFilesFilterInputTimer); + torrentFilesFilterInputTimer = setTimeout(function() { + if (current_hash === "") return; + torrentFilesTable.updateTable(false); + + if (value.trim() === "") + collapseAllNodes(); + else + expandAllNodes(); + }, 400); + } + }); + + /** + * Show/hide a node's row + */ + const _hideNode = function(node, shouldHide) { + const span = $('filesTablefileName' + node.rowId); + // span won't exist if row has been filtered out + if (span === null) + return; + const rowElem = span.parentElement.parentElement; + if (shouldHide) + rowElem.addClass("invisible"); + else + rowElem.removeClass("invisible"); } -}); -/** - * Show/hide a node's row - */ -const _hideNode = function(node, shouldHide) { - const span = $('filesTablefileName' + node.rowId); - // span won't exist if row has been filtered out - if (span === null) - return; - const rowElem = span.parentElement.parentElement; - if (shouldHide) - rowElem.addClass("invisible"); - else - rowElem.removeClass("invisible"); -} + /** + * Update a node's collapsed state and icon + */ + const _updateNodeState = function(node, isCollapsed) { + const span = $('filesTablefileName' + node.rowId); + // span won't exist if row has been filtered out + if (span === null) + return; + const td = span.parentElement; + const rowElem = td.parentElement; -/** - * Update a node's collapsed state and icon - */ -const _updateNodeState = function(node, isCollapsed) { - const span = $('filesTablefileName' + node.rowId); - // span won't exist if row has been filtered out - if (span === null) - return; - const td = span.parentElement; - const rowElem = td.parentElement; - - // store collapsed state - td.set("data-collapsed", isCollapsed); - - // rotate the collapse icon - const collapseIcon = td.getElementsByClassName("filesTableCollapseIcon")[0]; - if (isCollapsed) - collapseIcon.addClass("rotate"); - else - collapseIcon.removeClass("rotate"); -} + // store collapsed state + td.set("data-collapsed", isCollapsed); -const _isCollapsed = function(node) { - const span = $('filesTablefileName' + node.rowId); - if (span === null) - return true; + // rotate the collapse icon + const collapseIcon = td.getElementsByClassName("filesTableCollapseIcon")[0]; + if (isCollapsed) + collapseIcon.addClass("rotate"); + else + collapseIcon.removeClass("rotate"); + } - const td = span.parentElement; - return (td.get("data-collapsed") === "true"); -}; + const _isCollapsed = function(node) { + const span = $('filesTablefileName' + node.rowId); + if (span === null) + return true; -const expandNode = function(node) { - _collapseNode(node, false, false, false); - torrentFilesTable.altRow(); -}; + const td = span.parentElement; + return (td.get("data-collapsed") === "true"); + }; -const collapseNode = function(node) { - _collapseNode(node, true, false, false); - torrentFilesTable.altRow(); -}; + const expandNode = function(node) { + _collapseNode(node, false, false, false); + torrentFilesTable.altRow(); + }; -const expandAllNodes = function() { - const root = torrentFilesTable.getRoot(); - root.children.each(function(node) { - node.children.each(function(child) { - _collapseNode(child, false, true, false); + const collapseNode = function(node) { + _collapseNode(node, true, false, false); + torrentFilesTable.altRow(); + }; + + const expandAllNodes = function() { + const root = torrentFilesTable.getRoot(); + root.children.each(function(node) { + node.children.each(function(child) { + _collapseNode(child, false, true, false); + }); }); - }); - torrentFilesTable.altRow(); -}; + torrentFilesTable.altRow(); + }; -const collapseAllNodes = function() { - const root = torrentFilesTable.getRoot(); - root.children.each(function(node) { - node.children.each(function(child) { - _collapseNode(child, true, true, false); + const collapseAllNodes = function() { + const root = torrentFilesTable.getRoot(); + root.children.each(function(node) { + node.children.each(function(child) { + _collapseNode(child, true, true, false); + }); }); - }); - torrentFilesTable.altRow(); -} + torrentFilesTable.altRow(); + } -/** - * Collapses a folder node with the option to recursively collapse all children - * @param {FolderNode} node the node to collapse/expand - * @param {boolean} shouldCollapse true if the node should be collapsed, false if it should be expanded - * @param {boolean} applyToChildren true if the node's children should also be collapsed, recursively - * @param {boolean} isChildNode true if the current node is a child of the original node we collapsed/expanded - */ -const _collapseNode = function(node, shouldCollapse, applyToChildren, isChildNode) { - if (!node.isFolder) - return; + /** + * Collapses a folder node with the option to recursively collapse all children + * @param {FolderNode} node the node to collapse/expand + * @param {boolean} shouldCollapse true if the node should be collapsed, false if it should be expanded + * @param {boolean} applyToChildren true if the node's children should also be collapsed, recursively + * @param {boolean} isChildNode true if the current node is a child of the original node we collapsed/expanded + */ + const _collapseNode = function(node, shouldCollapse, applyToChildren, isChildNode) { + if (!node.isFolder) + return; - const shouldExpand = !shouldCollapse; - const isNodeCollapsed = _isCollapsed(node); - const nodeInCorrectState = ((shouldCollapse && isNodeCollapsed) || (shouldExpand && !isNodeCollapsed)); - const canSkipNode = (isChildNode && (!applyToChildren || nodeInCorrectState)); - if (!isChildNode || applyToChildren || !canSkipNode) - _updateNodeState(node, shouldCollapse); + const shouldExpand = !shouldCollapse; + const isNodeCollapsed = _isCollapsed(node); + const nodeInCorrectState = ((shouldCollapse && isNodeCollapsed) || (shouldExpand && !isNodeCollapsed)); + const canSkipNode = (isChildNode && (!applyToChildren || nodeInCorrectState)); + if (!isChildNode || applyToChildren || !canSkipNode) + _updateNodeState(node, shouldCollapse); - node.children.each(function(child) { - _hideNode(child, shouldCollapse); + node.children.each(function(child) { + _hideNode(child, shouldCollapse); - if (!child.isFolder) - return; + if (!child.isFolder) + return; - // don't expand children that have been independently collapsed, unless applyToChildren is true - const shouldExpandChildren = (shouldExpand && applyToChildren); - const isChildCollapsed = _isCollapsed(child); - if (!shouldExpandChildren && isChildCollapsed) - return; + // don't expand children that have been independently collapsed, unless applyToChildren is true + const shouldExpandChildren = (shouldExpand && applyToChildren); + const isChildCollapsed = _isCollapsed(child); + if (!shouldExpandChildren && isChildCollapsed) + return; - _collapseNode(child, shouldCollapse, applyToChildren, true); - }); -}; + _collapseNode(child, shouldCollapse, applyToChildren, true); + }); + }; + + return exports(); +})(); diff --git a/src/webui/www/private/scripts/prop-general.js b/src/webui/www/private/scripts/prop-general.js index 2349c6b7a..445893b88 100644 --- a/src/webui/www/private/scripts/prop-general.js +++ b/src/webui/www/private/scripts/prop-general.js @@ -28,172 +28,186 @@ 'use strict'; -const clearData = function() { - $('time_elapsed').set('html', ''); - $('eta').set('html', ''); - $('nb_connections').set('html', ''); - $('total_downloaded').set('html', ''); - $('total_uploaded').set('html', ''); - $('dl_speed').set('html', ''); - $('up_speed').set('html', ''); - $('dl_limit').set('html', ''); - $('up_limit').set('html', ''); - $('total_wasted').set('html', ''); - $('seeds').set('html', ''); - $('peers').set('html', ''); - $('share_ratio').set('html', ''); - $('reannounce').set('html', ''); - $('last_seen').set('html', ''); - $('total_size').set('html', ''); - $('pieces').set('html', ''); - $('created_by').set('html', ''); - $('addition_date').set('html', ''); - $('completion_date').set('html', ''); - $('creation_date').set('html', ''); - $('torrent_hash').set('html', ''); - $('save_path').set('html', ''); - $('comment').set('html', ''); -}; - -let loadTorrentDataTimer; -const loadTorrentData = function() { - if ($('prop_general').hasClass('invisible') - || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { - // Tab changed, don't do anything - return; - } - const current_hash = torrentsTable.getCurrentTorrentHash(); - if (current_hash === "") { - clearData(); - clearTimeout(loadTorrentDataTimer); - loadTorrentDataTimer = loadTorrentData.delay(5000); - return; - } - // Display hash - $('torrent_hash').set('html', current_hash); - const url = new URI('api/v2/torrents/properties?hash=' + current_hash); - new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onFailure: function() { - $('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]'); - clearTimeout(loadTorrentDataTimer); - loadTorrentDataTimer = loadTorrentData.delay(10000); - }, - onSuccess: function(data) { - $('error_div').set('html', ''); - if (data) { - let temp; - // Update Torrent data - if (data.seeding_time > 0) - temp = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", friendlyDuration(data.time_elapsed)) - .replace("%2", friendlyDuration(data.seeding_time)); - else - temp = friendlyDuration(data.time_elapsed); - $('time_elapsed').set('html', temp); - - $('eta').set('html', friendlyDuration(data.eta)); - - temp = "QBT_TR(%1 (%2 max))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", data.nb_connections) - .replace("%2", data.nb_connections_limit < 0 ? "∞" : data.nb_connections_limit); - $('nb_connections').set('html', temp); - - temp = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", friendlyUnit(data.total_downloaded)) - .replace("%2", friendlyUnit(data.total_downloaded_session)); - $('total_downloaded').set('html', temp); - - temp = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", friendlyUnit(data.total_uploaded)) - .replace("%2", friendlyUnit(data.total_uploaded_session)); - $('total_uploaded').set('html', temp); - - temp = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", friendlyUnit(data.dl_speed, true)) - .replace("%2", friendlyUnit(data.dl_speed_avg, true)); - $('dl_speed').set('html', temp); - - temp = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", friendlyUnit(data.up_speed, true)) - .replace("%2", friendlyUnit(data.up_speed_avg, true)); - $('up_speed').set('html', temp); - - temp = (data.dl_limit == -1 ? "∞" : friendlyUnit(data.dl_limit, true)); - $('dl_limit').set('html', temp); - - temp = (data.up_limit == -1 ? "∞" : friendlyUnit(data.up_limit, true)); - $('up_limit').set('html', temp); - - $('total_wasted').set('html', friendlyUnit(data.total_wasted)); - - temp = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", data.seeds) - .replace("%2", data.seeds_total); - $('seeds').set('html', temp); - - temp = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", data.peers) - .replace("%2", data.peers_total); - $('peers').set('html', temp); - - $('share_ratio').set('html', data.share_ratio.toFixed(2)); - - $('reannounce').set('html', friendlyDuration(data.reannounce)); - - if (data.last_seen != -1) - temp = new Date(data.last_seen * 1000).toLocaleString(); - else - temp = "QBT_TR(Never)QBT_TR[CONTEXT=PropertiesWidget]"; - $('last_seen').set('html', temp); - - $('total_size').set('html', friendlyUnit(data.total_size)); - - if (data.pieces_num != -1) - temp = "QBT_TR(%1 x %2 (have %3))QBT_TR[CONTEXT=PropertiesWidget]" - .replace("%1", data.pieces_num) - .replace("%2", friendlyUnit(data.piece_size)) - .replace("%3", data.pieces_have); - else - temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; - $('pieces').set('html', temp); - - $('created_by').set('html', escapeHtml(data.created_by)); - if (data.addition_date != -1) - temp = new Date(data.addition_date * 1000).toLocaleString(); - else - temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; - - $('addition_date').set('html', temp); - if (data.completion_date != -1) - temp = new Date(data.completion_date * 1000).toLocaleString(); - else - temp = ""; - - $('completion_date').set('html', temp); - - if (data.creation_date != -1) - temp = new Date(data.creation_date * 1000).toLocaleString(); - else - temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; - $('creation_date').set('html', temp); - - $('save_path').set('html', data.save_path); - - $('comment').set('html', parseHtmlLinks(escapeHtml(data.comment))); - } - else { - clearData(); - } +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.PropGeneral = (function() { + const exports = function() { + return { + updateData: updateData + }; + }; + + const clearData = function() { + $('time_elapsed').set('html', ''); + $('eta').set('html', ''); + $('nb_connections').set('html', ''); + $('total_downloaded').set('html', ''); + $('total_uploaded').set('html', ''); + $('dl_speed').set('html', ''); + $('up_speed').set('html', ''); + $('dl_limit').set('html', ''); + $('up_limit').set('html', ''); + $('total_wasted').set('html', ''); + $('seeds').set('html', ''); + $('peers').set('html', ''); + $('share_ratio').set('html', ''); + $('reannounce').set('html', ''); + $('last_seen').set('html', ''); + $('total_size').set('html', ''); + $('pieces').set('html', ''); + $('created_by').set('html', ''); + $('addition_date').set('html', ''); + $('completion_date').set('html', ''); + $('creation_date').set('html', ''); + $('torrent_hash').set('html', ''); + $('save_path').set('html', ''); + $('comment').set('html', ''); + }; + + let loadTorrentDataTimer; + const loadTorrentData = function() { + if ($('prop_general').hasClass('invisible') + || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { + // Tab changed, don't do anything + return; + } + const current_hash = torrentsTable.getCurrentTorrentHash(); + if (current_hash === "") { + clearData(); clearTimeout(loadTorrentDataTimer); loadTorrentDataTimer = loadTorrentData.delay(5000); + return; } - }).send(); -}; + // Display hash + $('torrent_hash').set('html', current_hash); + const url = new URI('api/v2/torrents/properties?hash=' + current_hash); + new Request.JSON({ + url: url, + noCache: true, + method: 'get', + onFailure: function() { + $('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]'); + clearTimeout(loadTorrentDataTimer); + loadTorrentDataTimer = loadTorrentData.delay(10000); + }, + onSuccess: function(data) { + $('error_div').set('html', ''); + if (data) { + let temp; + // Update Torrent data + if (data.seeding_time > 0) + temp = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", window.qBittorrent.Misc.friendlyDuration(data.time_elapsed)) + .replace("%2", window.qBittorrent.Misc.friendlyDuration(data.seeding_time)); + else + temp = window.qBittorrent.Misc.friendlyDuration(data.time_elapsed); + $('time_elapsed').set('html', temp); + + $('eta').set('html', window.qBittorrent.Misc.friendlyDuration(data.eta)); + + temp = "QBT_TR(%1 (%2 max))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", data.nb_connections) + .replace("%2", data.nb_connections_limit < 0 ? "∞" : data.nb_connections_limit); + $('nb_connections').set('html', temp); + + temp = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded)) + .replace("%2", window.qBittorrent.Misc.friendlyUnit(data.total_downloaded_session)); + $('total_downloaded').set('html', temp); + + temp = "QBT_TR(%1 (%2 this session))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded)) + .replace("%2", window.qBittorrent.Misc.friendlyUnit(data.total_uploaded_session)); + $('total_uploaded').set('html', temp); + + temp = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", window.qBittorrent.Misc.friendlyUnit(data.dl_speed, true)) + .replace("%2", window.qBittorrent.Misc.friendlyUnit(data.dl_speed_avg, true)); + $('dl_speed').set('html', temp); + + temp = "QBT_TR(%1 (%2 avg.))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", window.qBittorrent.Misc.friendlyUnit(data.up_speed, true)) + .replace("%2", window.qBittorrent.Misc.friendlyUnit(data.up_speed_avg, true)); + $('up_speed').set('html', temp); + + temp = (data.dl_limit == -1 ? "∞" : window.qBittorrent.Misc.friendlyUnit(data.dl_limit, true)); + $('dl_limit').set('html', temp); + + temp = (data.up_limit == -1 ? "∞" : window.qBittorrent.Misc.friendlyUnit(data.up_limit, true)); + $('up_limit').set('html', temp); + + $('total_wasted').set('html', window.qBittorrent.Misc.friendlyUnit(data.total_wasted)); + + temp = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", data.seeds) + .replace("%2", data.seeds_total); + $('seeds').set('html', temp); + + temp = "QBT_TR(%1 (%2 total))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", data.peers) + .replace("%2", data.peers_total); + $('peers').set('html', temp); + + $('share_ratio').set('html', data.share_ratio.toFixed(2)); + + $('reannounce').set('html', window.qBittorrent.Misc.friendlyDuration(data.reannounce)); + + if (data.last_seen != -1) + temp = new Date(data.last_seen * 1000).toLocaleString(); + else + temp = "QBT_TR(Never)QBT_TR[CONTEXT=PropertiesWidget]"; + $('last_seen').set('html', temp); + + $('total_size').set('html', window.qBittorrent.Misc.friendlyUnit(data.total_size)); + + if (data.pieces_num != -1) + temp = "QBT_TR(%1 x %2 (have %3))QBT_TR[CONTEXT=PropertiesWidget]" + .replace("%1", data.pieces_num) + .replace("%2", window.qBittorrent.Misc.friendlyUnit(data.piece_size)) + .replace("%3", data.pieces_have); + else + temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; + $('pieces').set('html', temp); + + $('created_by').set('html', window.qBittorrent.Misc.escapeHtml(data.created_by)); + if (data.addition_date != -1) + temp = new Date(data.addition_date * 1000).toLocaleString(); + else + temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; + + $('addition_date').set('html', temp); + if (data.completion_date != -1) + temp = new Date(data.completion_date * 1000).toLocaleString(); + else + temp = ""; + + $('completion_date').set('html', temp); + + if (data.creation_date != -1) + temp = new Date(data.creation_date * 1000).toLocaleString(); + else + temp = "QBT_TR(Unknown)QBT_TR[CONTEXT=HttpServer]"; + $('creation_date').set('html', temp); + + $('save_path').set('html', data.save_path); + + $('comment').set('html', window.qBittorrent.Misc.parseHtmlLinks(window.qBittorrent.Misc.escapeHtml(data.comment))); + } + else { + clearData(); + } + clearTimeout(loadTorrentDataTimer); + loadTorrentDataTimer = loadTorrentData.delay(5000); + } + }).send(); + }; + + const updateData = function() { + clearTimeout(loadTorrentDataTimer); + loadTorrentData(); + }; -updateTorrentData = function() { - clearTimeout(loadTorrentDataTimer); - loadTorrentData(); -}; + return exports(); +})(); diff --git a/src/webui/www/private/scripts/prop-peers.js b/src/webui/www/private/scripts/prop-peers.js index 08f10f5f4..c1bba676a 100644 --- a/src/webui/www/private/scripts/prop-peers.js +++ b/src/webui/www/private/scripts/prop-peers.js @@ -28,144 +28,160 @@ 'use strict'; -let loadTorrentPeersTimer; -let syncTorrentPeersLastResponseId = 0; -let show_flags = true; -const loadTorrentPeersData = function() { - if ($('prop_peers').hasClass('invisible') - || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { - syncTorrentPeersLastResponseId = 0; - torrentPeersTable.clear(); - return; - } - const current_hash = torrentsTable.getCurrentTorrentHash(); - if (current_hash === "") { - syncTorrentPeersLastResponseId = 0; - torrentPeersTable.clear(); - clearTimeout(loadTorrentPeersTimer); - loadTorrentPeersTimer = loadTorrentPeersData.delay(getSyncMainDataInterval()); - return; - } - const url = new URI('api/v2/sync/torrentPeers'); - url.setData('rid', syncTorrentPeersLastResponseId); - url.setData('hash', current_hash); - new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onComplete: function() { +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.PropPeers = (function() { + const exports = function() { + return { + updateData: updateData + } + }; + + const torrentPeersTable = new window.qBittorrent.DynamicTable.TorrentPeersTable(); + let loadTorrentPeersTimer; + let syncTorrentPeersLastResponseId = 0; + let show_flags = true; + + const loadTorrentPeersData = function() { + if ($('prop_peers').hasClass('invisible') + || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { + syncTorrentPeersLastResponseId = 0; + torrentPeersTable.clear(); + return; + } + const current_hash = torrentsTable.getCurrentTorrentHash(); + if (current_hash === "") { + syncTorrentPeersLastResponseId = 0; + torrentPeersTable.clear(); clearTimeout(loadTorrentPeersTimer); loadTorrentPeersTimer = loadTorrentPeersData.delay(getSyncMainDataInterval()); - }, - onSuccess: function(response) { - $('error_div').set('html', ''); - if (response) { - const full_update = (response['full_update'] === true); - if (full_update) - torrentPeersTable.clear(); - if (response['rid']) - syncTorrentPeersLastResponseId = response['rid']; - if (response['peers']) { - for (const key in response['peers']) { - response['peers'][key]['rowId'] = key; + return; + } + const url = new URI('api/v2/sync/torrentPeers'); + url.setData('rid', syncTorrentPeersLastResponseId); + url.setData('hash', current_hash); + new Request.JSON({ + url: url, + noCache: true, + method: 'get', + onComplete: function() { + clearTimeout(loadTorrentPeersTimer); + loadTorrentPeersTimer = loadTorrentPeersData.delay(getSyncMainDataInterval()); + }, + onSuccess: function(response) { + $('error_div').set('html', ''); + if (response) { + const full_update = (response['full_update'] === true); + if (full_update) + torrentPeersTable.clear(); + if (response['rid']) + syncTorrentPeersLastResponseId = response['rid']; + if (response['peers']) { + for (const key in response['peers']) { + response['peers'][key]['rowId'] = key; - if (response['peers'][key]['client']) - response['peers'][key]['client'] = escapeHtml(response['peers'][key]['client']); + if (response['peers'][key]['client']) + response['peers'][key]['client'] = window.qBittorrent.Misc.escapeHtml(response['peers'][key]['client']); - torrentPeersTable.updateRowData(response['peers'][key]); + torrentPeersTable.updateRowData(response['peers'][key]); + } } - } - if (response['peers_removed']) { - response['peers_removed'].each(function(hash) { - torrentPeersTable.removeRow(hash); - }); - } - torrentPeersTable.updateTable(full_update); - torrentPeersTable.altRow(); - - if (response['show_flags']) { - if (show_flags != response['show_flags']) { - show_flags = response['show_flags']; - torrentPeersTable.columns['country'].force_hide = !show_flags; - torrentPeersTable.updateColumn('country'); + if (response['peers_removed']) { + response['peers_removed'].each(function(hash) { + torrentPeersTable.removeRow(hash); + }); } + torrentPeersTable.updateTable(full_update); + torrentPeersTable.altRow(); + + if (response['show_flags']) { + if (show_flags != response['show_flags']) { + show_flags = response['show_flags']; + torrentPeersTable.columns['country'].force_hide = !show_flags; + torrentPeersTable.updateColumn('country'); + } + } + } + else { + torrentPeersTable.clear(); } } - else { - torrentPeersTable.clear(); + }).send(); + }; + + const updateData = function() { + clearTimeout(loadTorrentPeersTimer); + loadTorrentPeersData(); + }; + + const torrentPeersContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ + targets: '#torrentPeersTableDiv', + menu: 'torrentPeersMenu', + actions: { + addPeer: function(element, ref) { + const hash = torrentsTable.getCurrentTorrentHash(); + if (!hash) + return; + + new MochaUI.Window({ + id: 'addPeersPage', + title: "QBT_TR(Add Peers)QBT_TR[CONTEXT=PeersAdditionDialog]", + loadMethod: 'iframe', + contentURL: 'addpeers.html?hash=' + hash, + scrollbars: false, + resizable: false, + maximizable: false, + paddingVertical: 0, + paddingHorizontal: 0, + width: 350, + height: 240 + }); + }, + banPeer: function(element, ref) { + const selectedPeers = torrentPeersTable.selectedRowsIds(); + if (selectedPeers.length === 0) + return; + + if (confirm('QBT_TR(Are you sure you want to permanently ban the selected peers?)QBT_TR[CONTEXT=PeerListWidget]')) { + new Request({ + url: 'api/v2/torrents/banPeers', + noCache: true, + method: 'post', + data: { + hash: torrentsTable.getCurrentTorrentHash(), + peers: selectedPeers.join('|') + } + }).send(); + } } - } - }).send(); -}; - -updateTorrentPeersData = function() { - clearTimeout(loadTorrentPeersTimer); - loadTorrentPeersData(); -}; - -const torrentPeersContextMenu = new ContextMenu({ - targets: '#torrentPeersTableDiv', - menu: 'torrentPeersMenu', - actions: { - addPeer: function(element, ref) { - const hash = torrentsTable.getCurrentTorrentHash(); - if (!hash) - return; - - new MochaUI.Window({ - id: 'addPeersPage', - title: "QBT_TR(Add Peers)QBT_TR[CONTEXT=PeersAdditionDialog]", - loadMethod: 'iframe', - contentURL: 'addpeers.html?hash=' + hash, - scrollbars: false, - resizable: false, - maximizable: false, - paddingVertical: 0, - paddingHorizontal: 0, - width: 350, - height: 240 - }); }, - banPeer: function(element, ref) { + offsets: { + x: -15, + y: 2 + }, + onShow: function() { const selectedPeers = torrentPeersTable.selectedRowsIds(); - if (selectedPeers.length === 0) - return; - - if (confirm('QBT_TR(Are you sure you want to permanently ban the selected peers?)QBT_TR[CONTEXT=PeerListWidget]')) { - new Request({ - url: 'api/v2/torrents/banPeers', - noCache: true, - method: 'post', - data: { - hash: torrentsTable.getCurrentTorrentHash(), - peers: selectedPeers.join('|') - } - }).send(); + + if (selectedPeers.length >= 1) { + this.showItem('copyPeer'); + this.showItem('banPeer'); + } + else { + this.hideItem('copyPeer'); + this.hideItem('banPeer'); } } - }, - offsets: { - x: -15, - y: 2 - }, - onShow: function() { - const selectedPeers = torrentPeersTable.selectedRowsIds(); - - if (selectedPeers.length >= 1) { - this.showItem('copyPeer'); - this.showItem('banPeer'); - } - else { - this.hideItem('copyPeer'); - this.hideItem('banPeer'); + }); + + new ClipboardJS('#CopyPeerInfo', { + text: function(trigger) { + return torrentPeersTable.selectedRowsIds().join("\n"); } - } -}); + }); -new ClipboardJS('#CopyPeerInfo', { - text: function(trigger) { - return torrentPeersTable.selectedRowsIds().join("\n"); - } -}); + torrentPeersTable.setup('torrentPeersTableDiv', 'torrentPeersTableFixedHeaderDiv', torrentPeersContextMenu); -torrentPeersTable.setup('torrentPeersTableDiv', 'torrentPeersTableFixedHeaderDiv', torrentPeersContextMenu); + return exports(); +})(); diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index 2aace0505..315ee8b73 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -28,195 +28,211 @@ 'use strict'; -this.current_hash = ""; - -let loadTrackersDataTimer; -const loadTrackersData = function() { - if ($('prop_trackers').hasClass('invisible') - || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { - // Tab changed, don't do anything - return; - } - const new_hash = torrentsTable.getCurrentTorrentHash(); - if (new_hash === "") { - torrentTrackersTable.clear(); - clearTimeout(loadTrackersDataTimer); - loadTrackersDataTimer = loadTrackersData.delay(10000); - return; - } - if (new_hash != current_hash) { - torrentTrackersTable.clear(); - current_hash = new_hash; - } - const url = new URI('api/v2/torrents/trackers?hash=' + current_hash); - new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onComplete: function() { +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} + +window.qBittorrent.PropTrackers = (function() { + const exports = function() { + return { + updateData: updateData + }; + }; + + let current_hash = ""; + + const torrentTrackersTable = new window.qBittorrent.DynamicTable.TorrentTrackersTable(); + let loadTrackersDataTimer; + + const loadTrackersData = function() { + if ($('prop_trackers').hasClass('invisible') + || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { + // Tab changed, don't do anything + return; + } + const new_hash = torrentsTable.getCurrentTorrentHash(); + if (new_hash === "") { + torrentTrackersTable.clear(); clearTimeout(loadTrackersDataTimer); loadTrackersDataTimer = loadTrackersData.delay(10000); - }, - onSuccess: function(trackers) { - const selectedTrackers = torrentTrackersTable.selectedRowsIds(); + return; + } + if (new_hash != current_hash) { torrentTrackersTable.clear(); + current_hash = new_hash; + } + const url = new URI('api/v2/torrents/trackers?hash=' + current_hash); + new Request.JSON({ + url: url, + noCache: true, + method: 'get', + onComplete: function() { + clearTimeout(loadTrackersDataTimer); + loadTrackersDataTimer = loadTrackersData.delay(10000); + }, + onSuccess: function(trackers) { + const selectedTrackers = torrentTrackersTable.selectedRowsIds(); + torrentTrackersTable.clear(); + + if (trackers) { + trackers.each(function(tracker) { + const url = window.qBittorrent.Misc.escapeHtml(tracker.url); + let status; + switch (tracker.status) { + case 0: + status = "QBT_TR(Disabled)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + case 1: + status = "QBT_TR(Not contacted yet)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + case 2: + status = "QBT_TR(Working)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + case 3: + status = "QBT_TR(Updating...)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + case 4: + status = "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]"; + break; + } + + const row = { + rowId: url, + tier: tracker.tier, + url: url, + status: status, + peers: tracker.num_peers, + seeds: (tracker.num_seeds >= 0) ? tracker.num_seeds : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", + leeches: (tracker.num_leeches >= 0) ? tracker.num_leeches : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", + downloaded: (tracker.num_downloaded >= 0) ? tracker.num_downloaded : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", + message: window.qBittorrent.Misc.escapeHtml(tracker.msg) + }; + + torrentTrackersTable.updateRowData(row); + }); + + torrentTrackersTable.updateTable(false); + torrentTrackersTable.altRow(); + + if (selectedTrackers.length > 0) + torrentTrackersTable.reselectRows(selectedTrackers); + } + } + }).send(); + }; - if (trackers) { - trackers.each(function(tracker) { - const url = escapeHtml(tracker.url); - let status; - switch (tracker.status) { - case 0: - status = "QBT_TR(Disabled)QBT_TR[CONTEXT=TrackerListWidget]"; - break; - case 1: - status = "QBT_TR(Not contacted yet)QBT_TR[CONTEXT=TrackerListWidget]"; - break; - case 2: - status = "QBT_TR(Working)QBT_TR[CONTEXT=TrackerListWidget]"; - break; - case 3: - status = "QBT_TR(Updating...)QBT_TR[CONTEXT=TrackerListWidget]"; - break; - case 4: - status = "QBT_TR(Not working)QBT_TR[CONTEXT=TrackerListWidget]"; - break; - } - - const row = { - rowId: url, - tier: tracker.tier, - url: url, - status: status, - peers: tracker.num_peers, - seeds: (tracker.num_seeds >= 0) ? tracker.num_seeds : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", - leeches: (tracker.num_leeches >= 0) ? tracker.num_leeches : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", - downloaded: (tracker.num_downloaded >= 0) ? tracker.num_downloaded : "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]", - message: escapeHtml(tracker.msg) - }; - - torrentTrackersTable.updateRowData(row); - }); - - torrentTrackersTable.updateTable(false); - torrentTrackersTable.altRow(); - - if (selectedTrackers.length > 0) - torrentTrackersTable.reselectRows(selectedTrackers); + const updateData = function() { + clearTimeout(loadTrackersDataTimer); + loadTrackersData(); + }; + + const torrentTrackersContextMenu = new window.qBittorrent.ContextMenu.ContextMenu({ + targets: '#torrentTrackersTableDiv', + menu: 'torrentTrackersMenu', + actions: { + AddTracker: function(element, ref) { + addTrackerFN(); + }, + EditTracker: function(element, ref) { + // only allow editing of one row + element.firstChild.click(); + editTrackerFN(element); + }, + RemoveTracker: function(element, ref) { + removeTrackerFN(element); } - } - }).send(); -}; - -updateTrackersData = function() { - clearTimeout(loadTrackersDataTimer); - loadTrackersData(); -}; - -const torrentTrackersContextMenu = new ContextMenu({ - targets: '#torrentTrackersTableDiv', - menu: 'torrentTrackersMenu', - actions: { - AddTracker: function(element, ref) { - addTrackerFN(); }, - EditTracker: function(element, ref) { - // only allow editing of one row - element.firstChild.click(); - editTrackerFN(element); + offsets: { + x: -15, + y: 2 }, - RemoveTracker: function(element, ref) { - removeTrackerFN(element); + onShow: function() { + const selectedTrackers = torrentTrackersTable.selectedRowsIds(); + const containsStaticTracker = selectedTrackers.some(function(tracker) { + return (tracker.indexOf("** [") === 0); + }); + + if (containsStaticTracker || (selectedTrackers.length === 0)) { + this.hideItem('EditTracker'); + this.hideItem('RemoveTracker'); + this.hideItem('CopyTrackerUrl'); + } + else { + this.showItem('EditTracker'); + this.showItem('RemoveTracker'); + this.showItem('CopyTrackerUrl'); + } } - }, - offsets: { - x: -15, - y: 2 - }, - onShow: function() { - const selectedTrackers = torrentTrackersTable.selectedRowsIds(); - const containsStaticTracker = selectedTrackers.some(function(tracker) { - return (tracker.indexOf("** [") === 0); + }); + + const addTrackerFN = function() { + if (current_hash.length === 0) return; + new MochaUI.Window({ + id: 'trackersPage', + title: "QBT_TR(Trackers addition dialog)QBT_TR[CONTEXT=TrackersAdditionDialog]", + loadMethod: 'iframe', + contentURL: 'addtrackers.html?hash=' + current_hash, + scrollbars: true, + resizable: false, + maximizable: false, + closable: true, + paddingVertical: 0, + paddingHorizontal: 0, + width: 500, + height: 250, + onCloseComplete: function() { + updateData(); + } + }); + }; + + const editTrackerFN = function(element) { + if (current_hash.length === 0) return; + + const trackerUrl = encodeURIComponent(element.childNodes[1].innerText); + new MochaUI.Window({ + id: 'trackersPage', + title: "QBT_TR(Tracker editing)QBT_TR[CONTEXT=TrackerListWidget]", + loadMethod: 'iframe', + contentURL: 'edittracker.html?hash=' + current_hash + '&url=' + trackerUrl, + scrollbars: true, + resizable: false, + maximizable: false, + closable: true, + paddingVertical: 0, + paddingHorizontal: 0, + width: 500, + height: 150, + onCloseComplete: function() { + updateData(); + } }); + }; - if (containsStaticTracker || (selectedTrackers.length === 0)) { - this.hideItem('EditTracker'); - this.hideItem('RemoveTracker'); - this.hideItem('CopyTrackerUrl'); - } - else { - this.showItem('EditTracker'); - this.showItem('RemoveTracker'); - this.showItem('CopyTrackerUrl'); - } - } -}); - -const addTrackerFN = function() { - if (current_hash.length === 0) return; - new MochaUI.Window({ - id: 'trackersPage', - title: "QBT_TR(Trackers addition dialog)QBT_TR[CONTEXT=TrackersAdditionDialog]", - loadMethod: 'iframe', - contentURL: 'addtrackers.html?hash=' + current_hash, - scrollbars: true, - resizable: false, - maximizable: false, - closable: true, - paddingVertical: 0, - paddingHorizontal: 0, - width: 500, - height: 250, - onCloseComplete: function() { - updateTrackersData(); - } - }); -}; - -const editTrackerFN = function(element) { - if (current_hash.length === 0) return; - - const trackerUrl = encodeURIComponent(element.childNodes[1].innerText); - new MochaUI.Window({ - id: 'trackersPage', - title: "QBT_TR(Tracker editing)QBT_TR[CONTEXT=TrackerListWidget]", - loadMethod: 'iframe', - contentURL: 'edittracker.html?hash=' + current_hash + '&url=' + trackerUrl, - scrollbars: true, - resizable: false, - maximizable: false, - closable: true, - paddingVertical: 0, - paddingHorizontal: 0, - width: 500, - height: 150, - onCloseComplete: function() { - updateTrackersData(); + const removeTrackerFN = function(element) { + if (current_hash.length === 0) return; + + const selectedTrackers = torrentTrackersTable.selectedRowsIds(); + new Request({ + url: 'api/v2/torrents/removeTrackers', + method: 'post', + data: { + hash: current_hash, + urls: selectedTrackers.join("|") + }, + onSuccess: function() { + updateData(); + } + }).send(); + }; + + new ClipboardJS('#CopyTrackerUrl', { + text: function(trigger) { + return torrentTrackersTable.selectedRowsIds().join("\n"); } }); -}; - -const removeTrackerFN = function(element) { - if (current_hash.length === 0) return; - - const selectedTrackers = torrentTrackersTable.selectedRowsIds(); - new Request({ - url: 'api/v2/torrents/removeTrackers', - method: 'post', - data: { - hash: current_hash, - urls: selectedTrackers.join("|") - }, - onSuccess: function() { - updateTrackersData(); - } - }).send(); -}; -new ClipboardJS('#CopyTrackerUrl', { - text: function(trigger) { - return torrentTrackersTable.selectedRowsIds().join("\n"); - } -}); + torrentTrackersTable.setup('torrentTrackersTableDiv', 'torrentTrackersTableFixedHeaderDiv', torrentTrackersContextMenu); -torrentTrackersTable.setup('torrentTrackersTableDiv', 'torrentTrackersTableFixedHeaderDiv', torrentTrackersContextMenu); + return exports(); +})(); diff --git a/src/webui/www/private/scripts/prop-webseeds.js b/src/webui/www/private/scripts/prop-webseeds.js index bf270bfa0..6f6884d03 100644 --- a/src/webui/www/private/scripts/prop-webseeds.js +++ b/src/webui/www/private/scripts/prop-webseeds.js @@ -28,112 +28,126 @@ 'use strict'; -const webseedsDynTable = new Class({ +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} - initialize: function() {}, +window.qBittorrent.PropWebseeds = (function() { + const exports = function() { + return { + updateData: updateData + }; + }; - setup: function(table) { - this.table = $(table); - this.rows = new Hash(); - }, + const webseedsDynTable = new Class({ - removeRow: function(url) { - if (this.rows.has(url)) { - const tr = this.rows.get(url); - tr.dispose(); - this.rows.erase(url); - return true; - } - return false; - }, + initialize: function() {}, - removeAllRows: function() { - this.rows.each(function(tr, url) { - this.removeRow(url); - }.bind(this)); - }, - - updateRow: function(tr, row) { - const tds = tr.getElements('td'); - for (let i = 0; i < row.length; ++i) { - tds[i].set('html', row[i]); - } - return true; - }, + setup: function(table) { + this.table = $(table); + this.rows = new Hash(); + }, - insertRow: function(row) { - const url = row[0]; - if (this.rows.has(url)) { - const tableRow = this.rows.get(url); - this.updateRow(tableRow, row); - return; - } - //this.removeRow(id); - const tr = new Element('tr'); - this.rows.set(url, tr); - for (let i = 0; i < row.length; ++i) { - const td = new Element('td'); - td.set('html', row[i]); - td.injectInside(tr); - } - tr.injectInside(this.table); - }, -}); + removeRow: function(url) { + if (this.rows.has(url)) { + const tr = this.rows.get(url); + tr.dispose(); + this.rows.erase(url); + return true; + } + return false; + }, -this.current_hash = ""; + removeAllRows: function() { + this.rows.each(function(tr, url) { + this.removeRow(url); + }.bind(this)); + }, -let loadWebSeedsDataTimer; -const loadWebSeedsData = function() { - if ($('prop_webseeds').hasClass('invisible') - || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { - // Tab changed, don't do anything - return; - } - const new_hash = torrentsTable.getCurrentTorrentHash(); - if (new_hash === "") { - wsTable.removeAllRows(); - clearTimeout(loadWebSeedsDataTimer); - loadWebSeedsDataTimer = loadWebSeedsData.delay(10000); - return; - } - if (new_hash != current_hash) { - wsTable.removeAllRows(); - current_hash = new_hash; - } - const url = new URI('api/v2/torrents/webseeds?hash=' + current_hash); - new Request.JSON({ - url: url, - noCache: true, - method: 'get', - onFailure: function() { - $('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]'); - clearTimeout(loadWebSeedsDataTimer); - loadWebSeedsDataTimer = loadWebSeedsData.delay(20000); + updateRow: function(tr, row) { + const tds = tr.getElements('td'); + for (let i = 0; i < row.length; ++i) { + tds[i].set('html', row[i]); + } + return true; }, - onSuccess: function(webseeds) { - $('error_div').set('html', ''); - if (webseeds) { - // Update WebSeeds data - webseeds.each(function(webseed) { - const row = []; - row.length = 1; - row[0] = webseed.url; - wsTable.insertRow(row); - }); + + insertRow: function(row) { + const url = row[0]; + if (this.rows.has(url)) { + const tableRow = this.rows.get(url); + this.updateRow(tableRow, row); + return; } - else { - wsTable.removeAllRows(); + //this.removeRow(id); + const tr = new Element('tr'); + this.rows.set(url, tr); + for (let i = 0; i < row.length; ++i) { + const td = new Element('td'); + td.set('html', row[i]); + td.injectInside(tr); } + tr.injectInside(this.table); + }, + }); + + let current_hash = ""; + + let loadWebSeedsDataTimer; + const loadWebSeedsData = function() { + if ($('prop_webseeds').hasClass('invisible') + || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { + // Tab changed, don't do anything + return; + } + const new_hash = torrentsTable.getCurrentTorrentHash(); + if (new_hash === "") { + wsTable.removeAllRows(); clearTimeout(loadWebSeedsDataTimer); loadWebSeedsDataTimer = loadWebSeedsData.delay(10000); + return; + } + if (new_hash != current_hash) { + wsTable.removeAllRows(); + current_hash = new_hash; } - }).send(); -}; + const url = new URI('api/v2/torrents/webseeds?hash=' + current_hash); + new Request.JSON({ + url: url, + noCache: true, + method: 'get', + onFailure: function() { + $('error_div').set('html', 'QBT_TR(qBittorrent client is not reachable)QBT_TR[CONTEXT=HttpServer]'); + clearTimeout(loadWebSeedsDataTimer); + loadWebSeedsDataTimer = loadWebSeedsData.delay(20000); + }, + onSuccess: function(webseeds) { + $('error_div').set('html', ''); + if (webseeds) { + // Update WebSeeds data + webseeds.each(function(webseed) { + const row = []; + row.length = 1; + row[0] = webseed.url; + wsTable.insertRow(row); + }); + } + else { + wsTable.removeAllRows(); + } + clearTimeout(loadWebSeedsDataTimer); + loadWebSeedsDataTimer = loadWebSeedsData.delay(10000); + } + }).send(); + }; + + const updateData = function() { + clearTimeout(loadWebSeedsDataTimer); + loadWebSeedsData(); + }; -updateWebSeedsData = function() { - clearTimeout(loadWebSeedsDataTimer); - loadWebSeedsData(); -}; + const wsTable = new webseedsDynTable(); + wsTable.setup($('webseedsTable')); -const wsTable = new webseedsDynTable(); -wsTable.setup($('webseedsTable')); + return exports(); +})(); diff --git a/src/webui/www/private/setlocation.html b/src/webui/www/private/setlocation.html index 9446d166f..d42a7b137 100644 --- a/src/webui/www/private/setlocation.html +++ b/src/webui/www/private/setlocation.html @@ -33,7 +33,7 @@ const path = new URI().getData('path'); // set text field to current value if (path) - $('setLocation').value = escapeHtml(decodeURIComponent(path)); + $('setLocation').value = window.qBittorrent.Misc.escapeHtml(decodeURIComponent(path)); $('setLocation').focus(); $('setLocationButton').addEvent('click', function(e) { diff --git a/src/webui/www/private/shareratio.html b/src/webui/www/private/shareratio.html index f93a36117..b3166af2f 100644 --- a/src/webui/www/private/shareratio.html +++ b/src/webui/www/private/shareratio.html @@ -37,9 +37,9 @@ const origValues = new URI().getData('orig').split('|'); const values = { - ratioLimit: friendlyFloat(origValues[0], 2), + ratioLimit: window.qBittorrent.Misc.friendlyFloat(origValues[0], 2), seedingTimeLimit: parseInt(origValues[1]), - maxRatio: friendlyFloat(origValues[2], 2), + maxRatio: window.qBittorrent.Misc.friendlyFloat(origValues[2], 2), maxSeedingTime: parseInt(origValues[3]) }; diff --git a/src/webui/www/private/upload.html b/src/webui/www/private/upload.html index adbf64d94..6592bdd4a 100644 --- a/src/webui/www/private/upload.html +++ b/src/webui/www/private/upload.html @@ -23,7 +23,7 @@ - @@ -51,7 +51,7 @@
- diff --git a/src/webui/www/private/views/about.html b/src/webui/www/private/views/about.html index b540b9146..873b64af0 100644 --- a/src/webui/www/private/views/about.html +++ b/src/webui/www/private/views/about.html @@ -681,22 +681,24 @@ diff --git a/src/webui/www/private/views/aboutToolbar.html b/src/webui/www/private/views/aboutToolbar.html index a5fb62c5c..e1be22621 100644 --- a/src/webui/www/private/views/aboutToolbar.html +++ b/src/webui/www/private/views/aboutToolbar.html @@ -13,35 +13,37 @@ diff --git a/src/webui/www/private/views/filters.html b/src/webui/www/private/views/filters.html index 61b33b933..34f234397 100644 --- a/src/webui/www/private/views/filters.html +++ b/src/webui/www/private/views/filters.html @@ -32,79 +32,94 @@ diff --git a/src/webui/www/private/views/installsearchplugin.html b/src/webui/www/private/views/installsearchplugin.html index 4dc6c631d..c10291fb8 100644 --- a/src/webui/www/private/views/installsearchplugin.html +++ b/src/webui/www/private/views/installsearchplugin.html @@ -20,8 +20,8 @@
- - + +
@@ -29,41 +29,55 @@ diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index e999ab0aa..a09b0881d 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -85,7 +85,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -103,7 +103,7 @@ - + @@ -128,13 +128,13 @@
- - Add + Add
@@ -144,7 +144,7 @@
- + @@ -178,7 +178,7 @@
- +
@@ -204,7 +204,7 @@
- +
@@ -242,14 +242,14 @@
- +
- +
@@ -259,28 +259,28 @@
@@ -296,7 +296,7 @@
- +
- +
- +
- + - @@ -327,7 +327,7 @@
- + @@ -356,7 +356,7 @@
- +
@@ -414,7 +414,7 @@
- +
@@ -488,7 +488,7 @@
- +
@@ -519,7 +519,7 @@
- + @@ -556,7 +556,7 @@
- + @@ -564,7 +564,7 @@
- + @@ -588,7 +588,7 @@
- + @@ -673,7 +673,7 @@
- + @@ -722,7 +722,7 @@
- +
@@ -737,7 +737,7 @@
- +
@@ -758,7 +758,7 @@
- +
@@ -777,14 +777,14 @@
- + - +
@@ -1079,1019 +1079,1062 @@ -
+
diff --git a/src/webui/www/private/views/preferencesToolbar.html b/src/webui/www/private/views/preferencesToolbar.html index 9308e71c2..dcbeb2748 100644 --- a/src/webui/www/private/views/preferencesToolbar.html +++ b/src/webui/www/private/views/preferencesToolbar.html @@ -14,31 +14,33 @@ diff --git a/src/webui/www/private/views/properties.html b/src/webui/www/private/views/properties.html index d1f55cf38..771a26b3a 100644 --- a/src/webui/www/private/views/properties.html +++ b/src/webui/www/private/views/properties.html @@ -155,7 +155,9 @@ diff --git a/src/webui/www/private/views/search.html b/src/webui/www/private/views/search.html index 20ecc1813..f9f052a24 100644 --- a/src/webui/www/private/views/search.html +++ b/src/webui/www/private/views/search.html @@ -59,9 +59,9 @@
- - - + + +
@@ -85,19 +85,19 @@
- QBT_TR(Seeds:)QBT_TR[CONTEXT=SearchEngineWidget] - + to - + QBT_TR(Size:)QBT_TR[CONTEXT=SearchEngineWidget] - - + to - - +