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 - - +