From 1439bcc8644ebc5e32f8aff282d576d7ce8e40d6 Mon Sep 17 00:00:00 2001 From: Thomas Piccirello Date: Sun, 11 Aug 2019 23:53:20 -0700 Subject: [PATCH] Move JavaScript code into explicit namespaces This cleans up the global namespace by explicitly exporting shared values. All html and JavaScript files have been converted to use explicit exports except for client.js and mocha-init.js --- src/webui/www/private/addpeers.html | 2 +- src/webui/www/private/download.html | 4 +- src/webui/www/private/newcategory.html | 14 +- src/webui/www/private/newtag.html | 4 +- src/webui/www/private/scripts/client.js | 83 +- src/webui/www/private/scripts/contextmenu.js | 920 +++-- src/webui/www/private/scripts/download.js | 175 +- src/webui/www/private/scripts/dynamicTable.js | 3677 +++++++++-------- src/webui/www/private/scripts/file-tree.js | 272 +- src/webui/www/private/scripts/filesystem.js | 69 +- src/webui/www/private/scripts/misc.js | 332 +- src/webui/www/private/scripts/mocha-init.js | 2 +- src/webui/www/private/scripts/preferences.js | 44 +- src/webui/www/private/scripts/progressbar.js | 221 +- src/webui/www/private/scripts/prop-files.js | 1201 +++--- src/webui/www/private/scripts/prop-general.js | 338 +- src/webui/www/private/scripts/prop-peers.js | 270 +- .../www/private/scripts/prop-trackers.js | 362 +- .../www/private/scripts/prop-webseeds.js | 204 +- src/webui/www/private/setlocation.html | 2 +- src/webui/www/private/shareratio.html | 4 +- src/webui/www/private/upload.html | 4 +- src/webui/www/private/views/about.html | 32 +- src/webui/www/private/views/aboutToolbar.html | 52 +- src/webui/www/private/views/filters.html | 145 +- .../private/views/installsearchplugin.html | 82 +- src/webui/www/private/views/preferences.html | 2011 ++++----- .../www/private/views/preferencesToolbar.html | 54 +- src/webui/www/private/views/properties.html | 8 +- src/webui/www/private/views/search.html | 1029 ++--- .../www/private/views/searchplugins.html | 275 +- src/webui/www/private/views/transferlist.html | 150 +- 32 files changed, 6204 insertions(+), 5838 deletions(-) diff --git a/src/webui/www/private/addpeers.html b/src/webui/www/private/addpeers.html index e90ac099c..93b774e5f 100644 --- a/src/webui/www/private/addpeers.html +++ b/src/webui/www/private/addpeers.html @@ -61,7 +61,7 @@

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

- +
diff --git a/src/webui/www/private/download.html b/src/webui/www/private/download.html index 918a264db..5dfe84cc8 100644 --- a/src/webui/www/private/download.html +++ b/src/webui/www/private/download.html @@ -27,7 +27,7 @@ - @@ -63,7 +63,7 @@
- diff --git a/src/webui/www/private/newcategory.html b/src/webui/www/private/newcategory.html index b2cf42d88..8e2135ab2 100644 --- a/src/webui/www/private/newcategory.html +++ b/src/webui/www/private/newcategory.html @@ -30,18 +30,18 @@ }).activate(); window.addEvent('domready', function() { - const uriAction = safeTrim(new URI().getData('action')); - const uriHashes = safeTrim(new URI().getData('hashes')); - const uriCategoryName = safeTrim(new URI().getData('categoryName')); - const uriSavePath = safeTrim(new URI().getData('savePath')); + const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action')); + const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes')); + const uriCategoryName = window.qBittorrent.Misc.safeTrim(new URI().getData('categoryName')); + const uriSavePath = window.qBittorrent.Misc.safeTrim(new URI().getData('savePath')); if (uriAction === "edit") { if (!uriCategoryName) return false; $('categoryName').set('disabled', true); - $('categoryName').set('value', escapeHtml(uriCategoryName)); - $('savePath').set('value', escapeHtml(uriSavePath)); + $('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName)); + $('savePath').set('value', window.qBittorrent.Misc.escapeHtml(uriSavePath)); $('savePath').focus(); } else { @@ -90,7 +90,7 @@ }).send(); }, onError: function() { - alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + escapeHtml(categoryName)); + alert("QBT_TR(Unable to create category)QBT_TR[CONTEXT=HttpServer] " + window.qBittorrent.Misc.escapeHtml(categoryName)); } }).send(); break; diff --git a/src/webui/www/private/newtag.html b/src/webui/www/private/newtag.html index d1b7d4a3a..30ab09529 100644 --- a/src/webui/www/private/newtag.html +++ b/src/webui/www/private/newtag.html @@ -30,8 +30,8 @@ }).activate(); window.addEvent('domready', function() { - const uriAction = safeTrim(new URI().getData('action')); - const uriHashes = safeTrim(new URI().getData('hashes')); + const uriAction = window.qBittorrent.Misc.safeTrim(new URI().getData('action')); + const uriHashes = window.qBittorrent.Misc.safeTrim(new URI().getData('hashes')); if (uriAction === 'create') $('legendText').innerText = 'QBT_TR(Tag:)QBT_TR[CONTEXT=TagFilterWidget]'; diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 87fca4150..826228738 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -24,21 +24,10 @@ 'use strict'; -this.torrentsTable = new TorrentsTable(); -const torrentTrackersTable = new TorrentTrackersTable(); -const torrentPeersTable = new TorrentPeersTable(); -const torrentFilesTable = new TorrentFilesTable(); -const searchResultsTable = new SearchResultsTable(); -const searchPluginsTable = new SearchPluginsTable(); +this.torrentsTable = new window.qBittorrent.DynamicTable.TorrentsTable(); let updatePropertiesPanel = function() {}; -let updateTorrentData = function() {}; -let updateTrackersData = function() {}; -let updateTorrentPeersData = function() {}; -let updateWebSeedsData = function() {}; -let updateTorrentFilesData = function() {}; - this.updateMainData = function() {}; let alternativeSpeedLimits = false; let queueing_enabled = true; @@ -365,12 +354,12 @@ window.addEvent('load', function() { const create_link = function(hash, text, count) { const html = '' + '' - + escapeHtml(text) + ' (' + count + ')' + ''; + + window.qBittorrent.Misc.escapeHtml(text) + ' (' + count + ')' + ''; const el = new Element('li', { id: hash, html: html }); - categoriesFilterContextMenu.addTarget(el); + window.qBittorrent.Filters.categoriesFilterContextMenu.addTarget(el); return el; }; @@ -422,12 +411,12 @@ window.addEvent('load', function() { const createLink = function(hash, text, count) { const html = '' + '' - + escapeHtml(text) + ' (' + count + ')' + ''; + + window.qBittorrent.Misc.escapeHtml(text) + ' (' + count + ')' + ''; const el = new Element('li', { id: hash, html: html }); - tagsFilterContextMenu.addTarget(el); + window.qBittorrent.Filters.tagsFilterContextMenu.addTarget(el); return el; }; @@ -579,11 +568,11 @@ window.addEvent('load', function() { updateFiltersList(); if (update_categories) { updateCategoryList(); - torrentsTableContextMenu.updateCategoriesSubMenu(category_list); + window.qBittorrent.TransferList.contextMenu.updateCategoriesSubMenu(category_list); } if (updateTags) { updateTagList(); - torrentsTableContextMenu.updateTagsSubMenu(tagList); + window.qBittorrent.TransferList.contextMenu.updateTagsSubMenu(tagList); } if (full_update) @@ -603,39 +592,39 @@ window.addEvent('load', function() { }; const processServerState = function() { - let transfer_info = friendlyUnit(serverState.dl_info_speed, true); + let transfer_info = window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_speed, true); if (serverState.dl_rate_limit > 0) - transfer_info += " [" + friendlyUnit(serverState.dl_rate_limit, true) + "]"; - transfer_info += " (" + friendlyUnit(serverState.dl_info_data, false) + ")"; + transfer_info += " [" + window.qBittorrent.Misc.friendlyUnit(serverState.dl_rate_limit, true) + "]"; + transfer_info += " (" + window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_data, false) + ")"; $("DlInfos").set('html', transfer_info); - transfer_info = friendlyUnit(serverState.up_info_speed, true); + transfer_info = window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true); if (serverState.up_rate_limit > 0) - transfer_info += " [" + friendlyUnit(serverState.up_rate_limit, true) + "]"; - transfer_info += " (" + friendlyUnit(serverState.up_info_data, false) + ")"; + transfer_info += " [" + window.qBittorrent.Misc.friendlyUnit(serverState.up_rate_limit, true) + "]"; + transfer_info += " (" + window.qBittorrent.Misc.friendlyUnit(serverState.up_info_data, false) + ")"; $("UpInfos").set('html', transfer_info); if (speedInTitle) { - document.title = "QBT_TR([D: %1, U: %2] qBittorrent %3)QBT_TR[CONTEXT=MainWindow]".replace("%1", friendlyUnit(serverState.dl_info_speed, true)).replace("%2", friendlyUnit(serverState.up_info_speed, true)).replace("%3", qbtVersion()); + document.title = "QBT_TR([D: %1, U: %2] qBittorrent %3)QBT_TR[CONTEXT=MainWindow]".replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_speed, true)).replace("%2", window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true)).replace("%3", qbtVersion()); document.title += " QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]"; } else document.title = ("qBittorrent " + qbtVersion() + " QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]"); - $('freeSpaceOnDisk').set('html', 'QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]'.replace("%1", friendlyUnit(serverState.free_space_on_disk))); + $('freeSpaceOnDisk').set('html', 'QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]'.replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.free_space_on_disk))); $('DHTNodes').set('html', 'QBT_TR(DHT: %1 nodes)QBT_TR[CONTEXT=StatusBar]'.replace("%1", serverState.dht_nodes)); // Statistics dialog if (document.getElementById("statisticspage")) { - $('AlltimeDL').set('html', friendlyUnit(serverState.alltime_dl, false)); - $('AlltimeUL').set('html', friendlyUnit(serverState.alltime_ul, false)); - $('TotalWastedSession').set('html', friendlyUnit(serverState.total_wasted_session, false)); + $('AlltimeDL').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.alltime_dl, false)); + $('AlltimeUL').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.alltime_ul, false)); + $('TotalWastedSession').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.total_wasted_session, false)); $('GlobalRatio').set('html', serverState.global_ratio); $('TotalPeerConnections').set('html', serverState.total_peer_connections); $('ReadCacheHits').set('html', serverState.read_cache_hits + "%"); - $('TotalBuffersSize').set('html', friendlyUnit(serverState.total_buffers_size, false)); + $('TotalBuffersSize').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.total_buffers_size, false)); $('WriteCacheOverload').set('html', serverState.write_cache_overload + "%"); $('ReadCacheOverload').set('html', serverState.read_cache_overload + "%"); $('QueuedIOJobs').set('html', serverState.queued_io_jobs); $('AverageTimeInQueue').set('html', serverState.average_time_queue + " ms"); - $('TotalQueuedSize').set('html', friendlyUnit(serverState.total_queued_size, false)); + $('TotalQueuedSize').set('html', window.qBittorrent.Misc.friendlyUnit(serverState.total_queued_size, false)); } if (serverState.connection_status == "connected") @@ -790,7 +779,7 @@ window.addEvent('load', function() { const showSearchTab = function() { if (!searchTabInitialized) { - initSearchTab(); + window.qBittorrent.Search.init(); searchTabInitialized = true; } @@ -878,16 +867,26 @@ window.addEvent('load', function() { MochaUI.initializeTabs('propertiesTabs'); updatePropertiesPanel = function() { - if (!$('prop_general').hasClass('invisible')) - updateTorrentData(); - else if (!$('prop_trackers').hasClass('invisible')) - updateTrackersData(); - else if (!$('prop_peers').hasClass('invisible')) - updateTorrentPeersData(); - else if (!$('prop_webseeds').hasClass('invisible')) - updateWebSeedsData(); - else if (!$('prop_files').hasClass('invisible')) - updateTorrentFilesData(); + if (!$('prop_general').hasClass('invisible')) { + if (window.qBittorrent.PropGeneral !== undefined) + window.qBittorrent.PropGeneral.updateData(); + } + else if (!$('prop_trackers').hasClass('invisible')) { + if (window.qBittorrent.PropTrackers !== undefined) + window.qBittorrent.PropTrackers.updateData(); + } + else if (!$('prop_peers').hasClass('invisible')) { + if (window.qBittorrent.PropPeers !== undefined) + window.qBittorrent.PropPeers.updateData(); + } + else if (!$('prop_webseeds').hasClass('invisible')) { + if (window.qBittorrent.PropWebseeds !== undefined) + window.qBittorrent.PropWebseeds.updateData(); + } + else if (!$('prop_files').hasClass('invisible')) { + if (window.qBittorrent.PropFiles !== undefined) + window.qBittorrent.PropFiles.updateData(); + } }; $('PropGeneralLink').addEvent('click', function(e) { diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 310f520f8..0d16525f9 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -28,497 +28,515 @@ 'use strict'; -let lastShownContextMenu = null; -const ContextMenu = new Class({ - //implements - Implements: [Options, Events], +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} - //options - options: { - actions: {}, - menu: 'menu_id', - stopEvent: true, - targets: 'body', - offsets: { - x: 0, - y: 0 +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); + //initialization + initialize: function(options) { + //set options + this.setOptions(options); - //option diffs menu - this.menu = $(this.options.menu); - this.targets = $$(this.options.targets); + //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) - }); + //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(); + //hide and begin the listener + this.hide().startListener(); + + //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 + }); + } + }, + + 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) { + 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)); + }, + + 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 - this.menu.setStyles({ - 'position': 'absolute', - 'top': '-900000px', - 'display': 'block' - }); - }, + hide: function(trigger) { + if (this.shown) { + this.fx.start(0); + //this.menu.fade('out'); + this.fireEvent('hide'); + this.shown = false; + } + return this; + }, - adjustMenuPosition: function(e) { - this.updateMenuItems(); + setItemChecked: function(item, checked) { + this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity = + checked ? '1' : '0'; + return this; + }, - const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75; + getItemChecked: function(item) { + return '0' != this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity; + }, - if (this.menu.hasClass('scrollableMenu')) - this.menu.setStyle('max-height', scrollableMenuMaxHeight); + //hide an item + hideItem: function(item) { + this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible'); + return this; + }, - // draw the menu off-screen to know the menu dimensions - this.menu.setStyles({ - left: '-999em', - top: '-999em' - }); + //show an item + showItem: function(item) { + this.menu.getElement('a[href$=' + item + ']').parentNode.removeClass('invisible'); + return this; + }, - // 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' - }); + //disable the entire menu + disable: function() { + this.options.disabled = true; + return this; + }, - // 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 - }); + //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; } - }, + }); - setupEventListeners: function(elem) { - elem.addEvent('contextmenu', function(e) { - this.triggerMenu(e, elem); - }.bind(this)); - elem.addEvent('click', function(e) { - this.hide(); - }.bind(this)); + const TorrentsTableContextMenu = new Class({ + Extends: ContextMenu, - elem.addEvent('touchstart', function(e) { - e.preventDefault(); - clearTimeout(this.touchstartTimer); - this.hide(); + 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 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)); - }, + const h = torrentsTable.selectedRowsIds(); + h.each(function(item, index) { + const data = torrentsTable.rows.get(item).full_data; - 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) { - 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)); - }, - - 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; - - if (data['f_l_piece_prio'] !== true) - all_are_f_l_piece_prio = false; - else - there_are_f_l_piece_prio = true; - - 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; + if (data['seq_dl'] !== true) + all_are_seq_dl = false; else - tag.checked = tag.checked && tagExists; - } - }); + there_are_seq_dl = true; - let show_seq_dl = true; + if (data['f_l_piece_prio'] !== true) + all_are_f_l_piece_prio = false; + else + there_are_f_l_piece_prio = true; - if (!all_are_seq_dl && there_are_seq_dl) - show_seq_dl = false; + if (data['progress'] != 1.0) // not downloaded + all_are_downloaded = false; + else if (data['super_seeding'] !== true) + all_are_super_seeding = false; - let show_f_l_piece_prio = true; + if (data['state'] != 'pausedUP' && data['state'] != 'pausedDL') + all_are_paused = false; + else + there_are_paused = true; - if (!all_are_f_l_piece_prio && there_are_f_l_piece_prio) - show_f_l_piece_prio = false; + if (data['force_start'] !== true) + all_are_force_start = false; + else + there_are_force_start = true; - 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'); + if (data['auto_tmm'] === true) + there_are_auto_tmm = true; + else + all_are_auto_tmm = false; - if (show_seq_dl) - this.showItem('sequentialDownload'); - else + 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'); - - if (show_f_l_piece_prio) - this.showItem('firstLastPiecePrio'); - else this.hideItem('firstLastPiecePrio'); - - this.setItemChecked('sequentialDownload', all_are_seq_dl); - this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio); - - this.showItem('downloadLimit'); - this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator'); - this.hideItem('superSeeding'); - } - - 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); - }); - 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('superSeeding'); + this.setItemChecked('superSeeding', all_are_super_seeding); } - categoryList.appendChild(el); - }); - }, + 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'); - updateTagsSubMenu: function(tagList) { - const contextTagList = $('contextTagList'); - while (contextTagList.firstChild !== null) - contextTagList.removeChild(contextTagList.firstChild); + if (show_seq_dl) + this.showItem('sequentialDownload'); + else + this.hideItem('sequentialDownload'); - 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]' - + '' - })); + if (show_f_l_piece_prio) + this.showItem('firstLastPiecePrio'); + else + this.hideItem('firstLastPiecePrio'); - const sortedTags = []; - for (const key in tagList) - sortedTags.push(tagList[key].name); - sortedTags.sort(); + this.setItemChecked('sequentialDownload', all_are_seq_dl); + this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio); - 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('downloadLimit'); + this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator'); + this.hideItem('superSeeding'); + } + + 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, -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; + }; - 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('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; + if (pref.auto_tmm_enabled == 1) { + $('autoTMM').selectedIndex = 1; + $('savepath').disabled = true; + } + else { + $('autoTMM').selectedIndex = 0; + } } - 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; + } + else { + item.nextElementSibling.hidden = true; + const text = item.options[item.selectedIndex].innerHTML; + item.nextElementSibling.value = text; + + 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; } } - }).send(); -}; + }; -const changeCategorySelect = function(item) { - if (item.value == "\\other") { - item.nextElementSibling.hidden = false; - item.nextElementSibling.value = ""; - item.nextElementSibling.select(); + const changeTMM = function(item) { + if (item.selectedIndex == 1) { + $('savepath').disabled = true; - if ($('autoTMM').selectedIndex == 1) - $('savepath').value = defaultSavePath; - } - else { - item.nextElementSibling.hidden = true; - const text = item.options[item.selectedIndex].innerHTML; - item.nextElementSibling.value = text; - - 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']; } - } -}; + else { + $('savepath').disabled = false; + $('savepath').value = defaultSavePath; + } + }; -const changeTMM = function(item) { - if (item.selectedIndex == 1) { - $('savepath').disabled = true; + $(window).addEventListener("load", function() { + getPreferences(); + getCategories(); + }); - 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(); -}); + 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; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -const DynamicTable = new Class({ +window.qBittorrent.DynamicTable = (function() { + const exports = function() { + return { + TorrentsTable: TorrentsTable, + TorrentPeersTable: TorrentPeersTable, + SearchResultsTable: SearchResultsTable, + SearchPluginsTable: SearchPluginsTable, + TorrentTrackersTable: TorrentTrackersTable, + TorrentFilesTable: TorrentFilesTable + }; + }; - initialize: function() {}, + let DynamicTableHeaderContextMenuClass = null; + let ProgressColumnWidth = -1; - 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')); - }, + const DynamicTable = new Class({ - setupCommonEvents: function() { - const scrollFn = function() { - $(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px'; - }.bind(this); + initialize: function() {}, - $(this.dynamicTableDivId).addEvent('scroll', scrollFn); + 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')); + }, - // 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'; + setupCommonEvents: function() { + const scrollFn = function() { + $(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px'; + }.bind(this); - // Workaround due to inaccurate calculation of elements heights by browser + $(this.dynamicTableDivId).addEvent('scroll', scrollFn); - let n = 2; - - while (panel.clientWidth != panel.offsetWidth && n > 0) { // is panel vertical scrollbar visible ? - --n; - h -= 0.5; + // 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'; - } - this.lastPanelHeight = panel.getBoundingClientRect().height; - }.bind(this); + // Workaround due to inaccurate calculation of elements heights by browser - $(this.dynamicTableDivId).getParent('.panel').addEvent('resize', resizeFn); + let n = 2; - this.lastPanelHeight = 0; + while (panel.clientWidth != panel.offsetWidth && n > 0) { // is panel vertical scrollbar visible ? + --n; + h -= 0.5; + $(this.dynamicTableDivId).style.height = h + 'px'; + } - // 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); + }.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', ''); + // 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); } - if (side === 'right' || side !== 'left') { - el.setStyle('border-right-style', ''); - el.setStyle('border-right-color', ''); - el.setStyle('border-right-width', ''); - } - }; + }, - 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'; - - if (!previousVisibleSibling) { // right most column - borderChangeElement = e.target; - - 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); - - 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); - }); - } + setupHeaderEvents: function() { this.currentHeaderAction = ''; - }.bind(this); + this.canResize = false; - 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); - } + 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', ''); } - }); - } - }, - - 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' - }); - - const createLi = function(columnName, text) { - const html = '' + escapeHtml(text) + ''; - return new Element('li', { - html: html - }); - }; - - const actions = {}; - - 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) { - const text = this.columns[i].caption; - if (text === '') - continue; - ul.appendChild(createLi(this.columns[i].name, text)); - actions[this.columns[i].name] = onMenuItemClicked; - } - - ul.inject(document.body); - - this.headerContextMenu = new DynamicTableHeaderContextMenuClass({ - targets: '#' + this.dynamicTableFixedHeaderDivId + ' tr', - actions: actions, - menu: menuId, - offsets: { - x: -15, - y: 2 - } - }); - - 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; - }; - - 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'); - } - else { - el.removeClass('alt'); - } - }.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; - } - - 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'); - }); - }, - - 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() {}, - - 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 - 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]; - } - - 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; - }, - - 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; - } - - 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; + if (side === 'right' || side !== 'left') { + el.setStyle('border-right-style', ''); + el.setStyle('border-right-color', ''); + el.setStyle('border-right-width', ''); } - 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 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'; } - 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 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 { - // Simple selection - this._this.deselectAll(); - this._this.selectRow(this.rowId); + this.canResize = false; + e.target.getParent("tr").style.cursor = ''; } - 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); } + if (this.currentHeaderAction === 'drag') { + const previousVisibleSibling = e.target.getPrevious('[class=""]'); + let borderChangeElement = previousVisibleSibling; + let changeBorderSide = 'right'; - // Insert - if (rowPos >= trs.length) { - tr.inject(this.tableBody); - trs.push(tr); + if (mouseXRelative > brect.width / 2) { + borderChangeElement = e.target; + this.dropSide = 'right'; + } + else { + this.dropSide = 'left'; + } + + e.target.getParent("tr").style.cursor = 'move'; + + if (!previousVisibleSibling) { // right most column + borderChangeElement = e.target; + + 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); + + 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 { - tr.inject(trs[rowPos], 'before'); - trs.splice(rowPos, 0, tr); + this.currentHeaderAction = 'drag'; + el.setStyle('background-color', '#C1D5E7'); } + }.bind(this); - // 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); + 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); } - } - else { - td.adopt(new Element('img', { - 'src': img_path, - 'class': 'stateIcon', - 'title': state - })); - } - }; + }.bind(this); - // status - this.columns['status'].updateTd = function(td, row) { - const state = this.getRowValue(row); - if (!state) return; - - 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]"; - } - - 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 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 (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)); - } - }.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; - - // dlspeed - this.columns['dlspeed'].updateTd = function(td, row) { - const speed = friendlyUnit(this.getRowValue(row), true); - td.set('html', speed); - td.set('title', speed); - }; - - // upspeed - this.columns['upspeed'].updateTd = this.columns['dlspeed'].updateTd; - - // eta - this.columns['eta'].updateTd = function(td, row) { - const eta = friendlyDuration(this.getRowValue(row)); - td.set('html', eta); - td.set('title', eta); - }; - - // 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); - }; - - // 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 { - 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 = 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', 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 = 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': - if (~state.indexOf('paused')) - return false; - break; - 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 '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 - - 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 (this.currentHeaderAction === 'drag') { + resetElementBorderStyle(el); + el.getSiblings('[class=""]').each(function(el) { + resetElementBorderStyle(el); }); - if (!rowTags.contains(tagHashInt)) - return 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 + }); } - } + }, - if ((filterTerms !== undefined) && (filterTerms !== null) - && (filterTerms.length > 0) && !containsAllTerms(name, filterTerms)) - return false; + 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); + } + } + }); + } + }, - return true; - }, + showColumn: function(columnName, show) { + this.columns[columnName].visible = show ? '1' : '0'; + LocalPreferences.set('column_' + columnName + '_visible_' + this.dynamicTableDivId, show ? '1' : '0'); + this.updateColumn(columnName); + }, - getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash) { - let cnt = 0; - const rows = this.rows.getValues(); + setupHeaderMenu: function() { + this.setupDynamicTableHeaderContextMenuClass(); - for (let i = 0; i < rows.length; ++i) - if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) ++cnt; - return cnt; - }, + const menuId = this.dynamicTableDivId + '_headerMenu'; - getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash) { - const rowsHashes = []; - const rows = this.rows.getValues(); + const ul = new Element('ul', { + id: menuId, + class: 'contextMenu scrollableMenu' + }); - for (let i = 0; i < rows.length; ++i) - if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) - rowsHashes.push(rows[i]['rowId']); + const createLi = function(columnName, text) { + const html = '' + window.qBittorrent.Misc.escapeHtml(text) + ''; + return new Element('li', { + html: html + }); + }; - return rowsHashes; - }, + const actions = {}; - getFilteredAndSortedRows: function() { - const filteredRows = []; + const onMenuItemClicked = function(element, ref, action) { + this.showColumn(action, this.columns[action].visible === '0'); + }.bind(this); - const rows = this.rows.getValues(); - const filterText = $('torrentsFilterInput').value.trim().toLowerCase(); - const filterTerms = (filterText.length > 0) ? filterText.split(" ") : null; + 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 < rows.length; ++i) { - if (this.applyFilter(rows[i], selected_filter, selected_category, selectedTag, filterTerms)) { + ul.inject(document.body); + + this.headerContextMenu = new DynamicTableHeaderContextMenuClass({ + targets: '#' + this.dynamicTableFixedHeaderDivId + ' tr', + actions: actions, + menu: menuId, + offsets: { + x: -15, + y: 2 + } + }); + + 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; + }; + + 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'); + } + else { + el.removeClass('alt'); + } + }.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; + } + + 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'); + }); + }, + + 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() {}, + + 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 + 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]; } - } - 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; - }, + 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"); - }, + 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; + }, - getCurrentTorrentHash: function() { - return this.getSelectedRowId(); - }, + updateTable: function(fullUpdate) { + if (fullUpdate === undefined) + fullUpdate = false; - onSelectedRowChanged: function() { - updatePropertiesPanel(); - } -}); + const rows = this.getFilteredAndSortedRows(); -const TorrentPeersTable = new Class({ - Extends: DynamicTable, + for (let i = 0; i < this.selectedRows.length; ++i) + if (!(this.selectedRows[i] in rows)) { + this.selectedRows.splice(i, 1); + --i; + } - 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); + const trs = this.tableBody.getElements('tr'); - this.columns['country'].dataProperties.push('country_code'); - this.columns['flags'].dataProperties.push('flags_desc'); - this.initColumnsFunctions(); - }, + 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; + } + 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'); - initColumnsFunctions: function() { + tr['rowId'] = rows[rowPos]['rowId']; - // country + 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.columns['country'].updateTd = function(td, row) { - const country = this.getRowValue(row, 0); - const country_code = this.getRowValue(row, 1); + this.setupTr(tr); - if (!country_code) { - if (td.getChildren('img').length > 0) - td.getChildren('img')[0].dispose(); - 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); + } + + // Insert + if (rowPos >= trs.length) { + tr.inject(this.tableBody); + trs.push(tr); + } + else { + tr.inject(trs[rowPos], 'before'); + trs.splice(rowPos, 0, tr); + } + + // Update context menu + if (this.contextMenu) + this.contextMenu.addTarget(tr); + + this.updateRow(tr, true); + } } - const img_path = 'images/flags/' + country_code + '.svg'; + let rowPos = rows.length; - 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); + while ((rowPos < trs.length) && (trs.length > 0)) { + trs[trs.length - 1].dispose(); + trs.pop(); } - else - td.adopt(new Element('img', { - 'src': img_path, - 'class': 'flags', - 'alt': country, - 'title': country - })); - }; + }, - // ip + setupTr: function(tr) {}, - this.columns['ip'].compareRows = function(row1, row2) { - const ip1 = this.getRowValue(row1); - const ip2 = this.getRowValue(row2); + updateRow: function(tr, fullUpdate) { + const row = this.rows.get(tr.rowId); + const data = row[fullUpdate ? 'full_data' : 'data']; - const a = ip1.split("."); - const b = ip2.split("."); - - for (let i = 0; i < 4; ++i) { - if (a[i] != b[i]) - return a[i] - b[i]; + 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'] = {}; + }, - return 0; - }; - - // progress, relevance - - 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['relevance'].updateTd = this.columns['progress'].updateTd; - - // dl_speed, up_speed - - this.columns['dl_speed'].updateTd = function(td, row) { - const speed = this.getRowValue(row); - if (speed === 0) { - td.set('html', ''); - td.set('title', ''); + removeRow: function(rowId) { + this.selectedRows.erase(rowId); + const tr = this.getTrByRowId(rowId); + if (tr !== null) { + tr.dispose(); + this.rows.erase(rowId); + return true; } - else { - const formattedSpeed = friendlyUnit(speed, true); - td.set('html', formattedSpeed); - td.set('title', formattedSpeed); + 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(); } - }; + }, - this.columns['up_speed'].updateTd = this.columns['dl_speed'].updateTd; + selectedRowsIds: function() { + return this.selectedRows.slice(); + }, - // downloaded, uploaded + getRowIds: function() { + return this.rows.getKeys(); + }, + }); - this.columns['downloaded'].updateTd = function(td, row) { - const downloaded = friendlyUnit(this.getRowValue(row), false); - td.set('html', downloaded); - td.set('title', downloaded); - }; + const TorrentsTable = new Class({ + Extends: DynamicTable, - this.columns['uploaded'].updateTd = this.columns['downloaded'].updateTd; + 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); - // flags + this.columns['state_icon'].onclick = ''; + this.columns['state_icon'].dataProperties[0] = 'state'; - this.columns['flags'].updateTd = function(td, row) { - td.innerHTML = this.getRowValue(row, 0); - td.title = this.getRowValue(row, 1); - }; + this.columns['num_seeds'].dataProperties.push('num_complete'); + this.columns['num_leechs'].dataProperties.push('num_incomplete'); - // files + this.initColumnsFunctions(); + }, - this.columns['files'].updateTd = function(td, row) { - td.innerHTML = escapeHtml(this.getRowValue(row, 0).replace(/\n/g, ';')); - td.title = escapeHtml(this.getRowValue(row, 0)); - }; + 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 SearchResultsTable = new Class({ - Extends: DynamicTable, + const img_path = 'images/skin/' + state + '.svg'; - 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 = escapeHtml(this.getRowValue(row)); - td.set('html', value); - td.set('title', value); - } - 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 getSeedsFilters = function() { - let minSeeds = (searchSeedsFilter.min > 0) ? searchSeedsFilter.min : 0; - let maxSeeds = (searchSeedsFilter.max > 0) ? searchSeedsFilter.max : 0; - - if ((minSeeds > maxSeeds) && (maxSeeds > 0)) { - const tmp = minSeeds; - minSeeds = maxSeeds; - maxSeeds = tmp; - } - - return { - min: minSeeds, - max: maxSeeds - } - }; - - 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; - - filteredRows.push(row); - } - } - else { - filteredRows = rows; - } - - 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); - } - - 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"); - } - }; - }, - - 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); - - 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); + 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); + } } 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 span = new Element('span', { - text: 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); + td.adopt(new Element('img', { + 'src': img_path, + 'class': 'stateIcon', + 'title': state + })); + } + }; + + // status + this.columns['status'].updateTd = function(td, row) { + const state = this.getRowValue(row); + if (!state) return; + + 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]"; + } + + 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 { + if (ProgressColumnWidth < 0) + ProgressColumnWidth = td.offsetWidth; + td.adopt(new window.qBittorrent.ProgressBar.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)); + } + }.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; + + // 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); + }; + + // upspeed + this.columns['upspeed'].updateTd = this.columns['dlspeed'].updateTd; + + // 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); + }; + + // 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 { + 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); + }; + }, + + 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': + if (~state.indexOf('paused')) + return false; + break; + 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 '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 + + 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; + } + } + + if ((filterTerms !== undefined) && (filterTerms !== null) + && (filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(name, filterTerms)) + return false; + + return true; + }, + + getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash) { + let cnt = 0; + const rows = this.rows.getValues(); + + for (let i = 0; i < rows.length; ++i) + if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) ++cnt; + return cnt; + }, + + getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash) { + const rowsHashes = []; + const rows = this.rows.getValues(); + + for (let i = 0; i < rows.length; ++i) + if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, null)) + rowsHashes.push(rows[i]['rowId']); + + return rowsHashes; + }, + + getFilteredAndSortedRows: function() { + const filteredRows = []; + + const rows = this.rows.getValues(); + const filterText = $('torrentsFilterInput').value.trim().toLowerCase(); + const filterTerms = (filterText.length > 0) ? filterText.split(" ") : null; + + 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]; + } + } + + 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; + } + + const img_path = 'images/flags/' + country_code + '.svg'; + + 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 + })); + }; + + // ip + + this.columns['ip'].compareRows = function(row1, row2) { + const ip1 = this.getRowValue(row1); + const ip2 = this.getRowValue(row2); + + const a = ip1.split("."); + const b = ip2.split("."); + + for (let i = 0; i < 4; ++i) { + if (a[i] != b[i]) + return a[i] - b[i]; + } + + return 0; + }; + + // progress, relevance + + 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['relevance'].updateTd = this.columns['progress'].updateTd; + + // dl_speed, up_speed + + 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['up_speed'].updateTd = this.columns['dl_speed'].updateTd; + + // downloaded, uploaded + + 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); + }; + + this.columns['uploaded'].updateTd = this.columns['downloaded'].updateTd; + + // flags + + this.columns['flags'].updateTd = function(td, row) { + td.innerHTML = this.getRowValue(row, 0); + td.title = this.getRowValue(row, 1); + }; + + // files + + 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)); + }; + + } + }); + + 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); + } + 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['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; + } + + return { + min: minSize, + max: maxSize + } + }; + + 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; + + if ((minSeeds > maxSeeds) && (maxSeeds > 0)) { + const tmp = minSeeds; + minSeeds = maxSeeds; + maxSeeds = tmp; + } + + return { + min: minSeeds, + max: maxSeeds + } + }; + + 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; + } + + 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 = window.qBittorrent.Misc.escapeHtml(this.getRowValue(row)); + td.set('html', value); + td.set('title', value); + } + + 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); - const span = new Element('span', { - text: escapeHtml(value), - id: fileNameId, - styles: { - 'margin-left': ((node.depth + 1) * 20) + 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"); + } + }; + }, + + 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)); + }, + + _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: 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 { + 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 = 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); + }; + + 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); } - }); - td.set('html', span.outerHTML); - } - }; - - 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 + 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); } - }); - td.adopt(treeImg, createDownloadCheckbox(id, row.full_data.fileId, value)); - } - }; + } + else { + const value = this.getRowValue(row); + const span = new Element('span', { + text: window.qBittorrent.Misc.escapeHtml(value), + id: fileNameId, + styles: { + 'margin-left': ((node.depth + 1) * 20) + } + }); + td.set('html', span.outerHTML); + } + }; - this.columns['size'].updateTd = displaySize; + this.columns['checked'].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); + 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)); + } + }; - const progressBar = $('pbf_' + id); - if (progressBar === null) { - td.adopt(new ProgressBar(value.toFloat(), { - id: 'pbf_' + id, - width: 80 - })); - } - else { - progressBar.setValue(value.toFloat()); - } - }; + this.columns['size'].updateTd = displaySize; - this.columns['priority'].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); - if (isPriorityComboExists(id)) - updatePriorityCombo(id, value); - else - td.adopt(createPriorityCombo(id, row.full_data.fileId, value)); - }; + 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['remaining'].updateTd = displaySize; - this.columns['availability'].updateTd = displayPercentage; - }, + this.columns['priority'].updateTd = function(td, row) { + const id = row.rowId; + const value = this.getRowValue(row); - altRow: function() { - let addClass = false; - const trs = this.tableBody.getElements('tr'); - trs.each(function(tr) { - if (tr.hasClass("invisible")) - return; + 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)); + }; - if (addClass){ - tr.addClass("alt"); - tr.removeClass("nonAlt"); - } - else { - tr.removeClass("alt"); - tr.addClass("nonAlt"); - } - addClass = !addClass; - }.bind(this)); - }, + this.columns['remaining'].updateTd = displaySize; + this.columns['availability'].updateTd = displayPercentage; + }, - _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; + 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; + } + + 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)); + }, + + _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; + } } - 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)); - }, - - _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) { + 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); +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -const TriState = { - "Unchecked": 0, - "Checked": 1, - "Partial": 2 -}; -Object.freeze(TriState); +window.qBittorrent.FileTree = (function() { + const exports = function() { + return { + FilePriority: FilePriority, + TriState: TriState, + FileTree: FileTree, + FileNode: FileNode, + FolderNode: FolderNode, + }; + }; -const FileTree = new Class({ - root: null, - nodeMap: {}, + const FilePriority = { + "Ignored": 0, + "Normal": 1, + "High": 6, + "Maximum": 7, + "Mixed": -1 + }; + Object.freeze(FilePriority); - setRoot: function(root) { - this.root = root; - this.generateNodeMap(root); + const TriState = { + "Unchecked": 0, + "Checked": 1, + "Partial": 2 + }; + Object.freeze(TriState); - if (this.root.isFolder) - this.root.calculateSize(); - }, + const FileTree = new Class({ + root: null, + nodeMap: {}, - getRoot: function() { - return this.root; - }, + setRoot: function(root) { + this.root = root; + this.generateNodeMap(root); - generateNodeMap: function(node) { - // don't store root node in map - if (node.root !== null) { - this.nodeMap[node.rowId] = node; + 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)); } + }); - node.children.each(function(child) { - this.generateNodeMap(child); - }.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: [], + }); - getNode: function(rowId) { - return (this.nodeMap[rowId] === undefined) - ? null - : this.nodeMap[rowId]; - }, + const FolderNode = new Class({ + Extends: FileNode, - getRowId: function(node) { - return node.rowId; - }, + initialize: function() { + this.isFolder = true; + }, - /** - * Returns the nodes in dfs order - */ - toArray: function() { - const nodes = []; - this.root.children.each(function(node) { - this._getArrayOfNodes(node, nodes); - }.bind(this)); - return nodes; - }, + addChild(node) { + this.children.push(node); + }, - _getArrayOfNodes: function(node, array) { - array.push(node); - node.children.each(function(child) { - this._getArrayOfNodes(child, array); - }.bind(this)); - } -}); + /** + * 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; -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: [], -}); + let isFirstFile = true; -const FolderNode = new Class({ - Extends: FileNode, + this.children.each(function(node) { + if (node.isFolder) + node.calculateSize(); - initialize: function() { - this.isFolder = true; - }, + size += node.size; - addChild(node) { - this.children.push(node); - }, + 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; + } - /** - * 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; + const isIgnored = (node.priority === FilePriority.Ignored); + if (!isIgnored) { + remaining += node.remaining; + progress += (node.progress * node.size); + availability += (node.availability * node.size); + } + }.bind(this)); - let isFirstFile = true; + this.size = size; + this.remaining = remaining; + this.checked = checked; + this.progress = (progress / size); + this.priority = priority; + this.availability = (availability / size); + } + }); - 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; - } - - function friendlyUnitPrecision(sizeUnit) { - if (sizeUnit <= 2) return 1; // KiB, MiB - else if (sizeUnit === 3) return 2; // GiB - else return 3; // TiB, PiB, EiB - } - - 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]; - } - - if (isSpeed) - ret += "QBT_TR(/s)QBT_TR[CONTEXT=misc]"; - return ret; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; } -/* - * 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 "∞"; -} - -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) + "%"; -} - -function friendlyFloat(value, precision) { - return parseFloat(value).toFixed(precision); -} - -/* - * 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; - } - - 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'; +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]" + ]; -/* - * 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"); -} + if ((value === undefined) || (value === null) || (value < 0)) + return "QBT_TR(Unknown)QBT_TR[CONTEXT=misc]"; -function escapeHtml(str) { - const div = document.createElement('div'); - div.appendChild(document.createTextNode(str)); - return div.innerHTML; -} - -function safeTrim(value) { - try { - return value.trim(); - } - catch (e) { - if (e instanceof TypeError) - return ""; - throw e; - } -} - -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 - */ -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); + let i = 0; + while (value >= 1024.0 && i < 6) { + value /= 1024.0; + ++i; } - const textContainsTerm = (textToSearch.indexOf(term) !== -1); - return isTermExcluded ? !textContainsTerm : textContainsTerm; - })); -} + function friendlyUnitPrecision(sizeUnit) { + if (sizeUnit <= 2) return 1; // KiB, MiB + else if (sizeUnit === 3) return 2; // GiB + else return 3; // TiB, PiB, EiB + } + + 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]; + } + + if (isSpeed) + ret += "QBT_TR(/s)QBT_TR[CONTEXT=misc]"; + return ret; + } + + /* + * 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 "∞"; + } + + 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) + "%"; + } + + const friendlyFloat = function(value, precision) { + return parseFloat(value).toFixed(precision); + } + + /* + * 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; + } + + 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 + */ + const parseHtmlLinks = function(text) { + const exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + return text.replace(exp, "$1"); + } + + const escapeHtml = function(str) { + const div = document.createElement('div'); + div.appendChild(document.createTextNode(str)); + return div.innerHTML; + } + + const safeTrim = function(value) { + try { + return value.trim(); + } + 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); + } + + /** + * + * @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); + } + + 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); +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); + } } - 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; + }; + + 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_getValue() { + return this.vals.value; } -}); -function ProgressBar_getValue() { - return 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_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); + } + } -let ProgressBars = 0; + 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); + } + + 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 = ""; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -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; - } -}; - -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 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; + } + }; + + 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 + }; + }; + + const fileCheckboxClicked = function(e) { + e.stopPropagation(); + + 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 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); + + setFilePriority(rows.rowIds, rows.fileIds, priority); + updateGlobalCheckbox(); + }; + + const isDownloadCheckboxExists = function(id) { + return ($('cbPrio' + id) !== null); + }; + + 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; + }; + + const updateDownloadCheckbox = function(id, checked) { + const checkbox = $('cbPrio' + id); + updateCheckbox(checkbox, checked); + }; + + 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 rowIds = []; - const fileIds = []; + const isPriorityComboExists = function(id) { + return ($('comboPrio' + id) !== null); + }; - const getChildFiles = function(node) { - if (node.isFolder) { - node.children.each(function(child) { - getChildFiles(child); + 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 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 { - rowIds.push(node.data.rowId); - fileIds.push(node.data.fileId); - } - }; - - node.children.each(function(child) { - getChildFiles(child); - }); - - return { - rowIds: rowIds, - fileIds: fileIds - }; -}; - -const fileCheckboxClicked = function(e) { - e.stopPropagation(); - - 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 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); - - setFilePriority(rows.rowIds, rows.fileIds, priority); - updateGlobalCheckbox(); -}; - -const isDownloadCheckboxExists = function(id) { - return ($('cbPrio' + id) !== null); -}; - -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; -}; - -const updateDownloadCheckbox = function(id, checked) { - const checkbox = $('cbPrio' + id); - updateCheckbox(checkbox, checked); -}; - -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 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'); - } - - 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); - } - }); - } - - 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); + 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); + } + }); } - }).send(); - const ignore = (priority === FilePriority.Ignored); - ids.forEach(function(_id) { - torrentFilesTable.setIgnored(_id, ignore); + if (rowIds.length > 0) + setFilePriority(rowIds, fileIds, priority); + }; - const combobox = $('comboPrio' + _id); - if (combobox !== null) - selectComboboxPriority(combobox, priority); - }); + const updateGlobalCheckbox = function() { + const checkbox = $('tristate_cb'); + if (isAllCheckboxesChecked()) + setCheckboxChecked(checkbox); + else if (isAllCheckboxesUnchecked()) + setCheckboxUnchecked(checkbox); + else + setCheckboxPartial(checkbox); + }; - torrentFilesTable.updateTable(false); -}; + 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; -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() { + 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 ignore = (priority === FilePriority.Ignored); + ids.forEach(function(_id) { + torrentFilesTable.setIgnored(_id, ignore); + + const combobox = $('comboPrio' + _id); + if (combobox !== null) + selectComboboxPriority(combobox, priority); + }); + + torrentFilesTable.updateTable(false); + }; + + 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); - }, - onSuccess: function(files) { - clearTimeout(torrentFilesFilterInputTimer); - - if (files.length === 0) { - torrentFilesTable.clear(); - } - else { - handleNewTorrentFiles(files); - if (loadedNewTorrent) - collapseAllNodes(); - } + return; } - }).send(); -}; + 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); -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 - }; - - return row; - }); - - addRowsToTable(rows); - updateGlobalCheckbox(); -}; - -const addRowsToTable = function(rows) { - const selectedFiles = torrentFilesTable.selectedRowsIds(); - let rowId = 0; - - const rootNode = new FolderNode(); - - rows.forEach(function(row) { - let parent = rootNode; - const pathFolders = row.fileName.split(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 (files.length === 0) { + torrentFilesTable.clear(); + } + else { + handleNewTorrentFiles(files); + if (loadedNewTorrent) + collapseAllNodes(); } } - if (parentNode === null) { - parentNode = new FolderNode(); - parentNode.name = folderName; - parentNode.rowId = rowId; - parentNode.root = parent; - parent.addChild(parentNode); + }).send(); + }; - ++rowId; - } + const updateData = function() { + clearTimeout(loadTorrentFilesDataTimer); + loadTorrentFilesData(); + }; - parent = parentNode; + 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; }); - 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); + addRowsToTable(rows); + updateGlobalCheckbox(); + }; - ++rowId; - }.bind(this)); + const addRowsToTable = function(rows) { + const selectedFiles = torrentFilesTable.selectedRowsIds(); + let rowId = 0; - torrentFilesTable.populateTable(rootNode); - torrentFilesTable.updateTable(false); - torrentFilesTable.altRow(); + const rootNode = new window.qBittorrent.FileTree.FolderNode(); - if (selectedFiles.length > 0) - torrentFilesTable.reselectRows(selectedFiles); -}; + 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; -const collapseIconClicked = function(event) { - const id = event.get("data-id"); - const node = torrentFilesTable.getNode(id); - const isCollapsed = (event.parentElement.get("data-collapsed") === "true"); + 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 window.qBittorrent.FileTree.FolderNode(); + parentNode.name = folderName; + parentNode.rowId = rowId; + parentNode.root = parent; + parent.addChild(parentNode); - if (isCollapsed) - expandNode(node); - else - collapseNode(node); -}; + ++rowId; + } -const filesPriorityMenuClicked = function(priority) { - const selectedRows = torrentFilesTable.selectedRowsIds(); - if (selectedRows.length === 0) return; + parent = parentNode; + }); - 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); - 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; - }); - } + ++rowId; + }.bind(this)); - setFilePriority(Object.keys(uniqueRowIds), Object.keys(uniqueFileIds), priority); -}; + torrentFilesTable.populateTable(rootNode); + torrentFilesTable.updateTable(false); + torrentFilesTable.altRow(); -const torrentFilesContextMenu = new ContextMenu({ - targets: '#torrentFilesTableDiv tr', - menu: 'torrentFilesMenu', - actions: { + if (selectedFiles.length > 0) + torrentFilesTable.reselectRows(selectedFiles); + }; - 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); - } - }, - offsets: { - x: -15, - y: 2 - }, - onShow: function() { - if (is_seed) - this.hideItem('FilePrio'); + 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 - this.showItem('FilePrio'); - } -}); + collapseNode(node); + }; -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 filesPriorityMenuClicked = function(priority) { + const selectedRows = torrentFilesTable.selectedRowsIds(); + if (selectedRows.length === 0) return; - const checkboxTH = tableHeaders[0]; - checkbox.injectInside(checkboxTH); -} + const rowIds = []; + const fileIds = []; + selectedRows.forEach(function(rowId) { + const elem = $('comboPrio' + rowId); + rowIds.push(rowId); + fileIds.push(elem.get("data-file-id")); + }); -// default sort by name column -if (torrentFilesTable.getSortedColumn() === null) - torrentFilesTable.setSortedColumn('name'); + 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; + }); + } -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); + setFilePriority(Object.keys(uniqueRowIds), Object.keys(uniqueFileIds), priority); + }; - if (value.trim() === "") - collapseAllNodes(); + 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); + } + }, + offsets: { + x: -15, + y: 2 + }, + onShow: function() { + if (is_seed) + this.hideItem('FilePrio'); else - expandAllNodes(); - }, 400); + 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); } -}); -/** - * 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"); -} + // default sort by name column + if (torrentFilesTable.getSortedColumn() === null) + torrentFilesTable.setSortedColumn('name'); -/** - * 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; + 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); - // 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"); -} - -const _isCollapsed = function(node) { - const span = $('filesTablefileName' + node.rowId); - if (span === null) - return true; - - const td = span.parentElement; - return (td.get("data-collapsed") === "true"); -}; - -const expandNode = function(node) { - _collapseNode(node, false, false, false); - torrentFilesTable.altRow(); -}; - -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); - }); + if (value.trim() === "") + collapseAllNodes(); + else + expandAllNodes(); + }, 400); + } }); - torrentFilesTable.altRow(); -}; -const collapseAllNodes = function() { - const root = torrentFilesTable.getRoot(); - root.children.each(function(node) { - node.children.each(function(child) { - _collapseNode(child, true, true, false); + /** + * 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; + + // 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"); + } + + const _isCollapsed = function(node) { + const span = $('filesTablefileName' + node.rowId); + if (span === null) + return true; + + const td = span.parentElement; + return (td.get("data-collapsed") === "true"); + }; + + const expandNode = function(node) { + _collapseNode(node, false, false, false); + torrentFilesTable.altRow(); + }; + + 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(); + }; -/** - * 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 collapseAllNodes = function() { + const root = torrentFilesTable.getRoot(); + root.children.each(function(node) { + node.children.each(function(child) { + _collapseNode(child, true, true, false); + }); + }); + torrentFilesTable.altRow(); + } - 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); - - if (!child.isFolder) + /** + * 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; - // 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; + 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); - _collapseNode(child, shouldCollapse, applyToChildren, true); - }); -}; + node.children.each(function(child) { + _hideNode(child, shouldCollapse); + + 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; + + _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', ''); -}; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -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); +window.qBittorrent.PropGeneral = (function() { + const exports = function() { + return { + updateData: updateData + }; + }; - $('eta').set('html', friendlyDuration(data.eta)); + 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', ''); + }; - 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(); - } + 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); -updateTorrentData = function() { - clearTimeout(loadTorrentDataTimer); - loadTorrentData(); -}; + $('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(); + }; + + 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) + 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'] = window.qBittorrent.Misc.escapeHtml(response['peers'][key]['client']); + + 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'); + } + } + } + else { 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']); - - torrentPeersTable.updateRowData(response['peers'][key]); - } } - if (response['peers_removed']) { - response['peers_removed'].each(function(hash) { - torrentPeersTable.removeRow(hash); - }); - } - torrentPeersTable.updateTable(full_update); - torrentPeersTable.altRow(); + } + }).send(); + }; - 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'); - } + 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(); } } + }, + offsets: { + x: -15, + y: 2 + }, + onShow: function() { + const selectedPeers = torrentPeersTable.selectedRowsIds(); + + if (selectedPeers.length >= 1) { + this.showItem('copyPeer'); + this.showItem('banPeer'); + } else { - torrentPeersTable.clear(); + this.hideItem('copyPeer'); + this.hideItem('banPeer'); } } - }).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) { - 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(); - } + new ClipboardJS('#CopyPeerInfo', { + text: function(trigger) { + return torrentPeersTable.selectedRowsIds().join("\n"); } - }, - 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'); - } - } -}); + torrentPeersTable.setup('torrentPeersTableDiv', 'torrentPeersTableFixedHeaderDiv', torrentPeersContextMenu); -new ClipboardJS('#CopyPeerInfo', { - text: function(trigger) { - return torrentPeersTable.selectedRowsIds().join("\n"); - } -}); - -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 = ""; +if (window.qBittorrent === undefined) { + window.qBittorrent = {}; +} -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() { +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 = 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; - } + 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: escapeHtml(tracker.msg) - }; + 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.updateRowData(row); + }); - torrentTrackersTable.updateTable(false); - torrentTrackersTable.altRow(); + torrentTrackersTable.updateTable(false); + torrentTrackersTable.altRow(); - if (selectedTrackers.length > 0) - torrentTrackersTable.reselectRows(selectedTrackers); + if (selectedTrackers.length > 0) + torrentTrackersTable.reselectRows(selectedTrackers); + } + } + }).send(); + }; + + 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); + } + }, + offsets: { + x: -15, + y: 2 + }, + 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'); } } - }).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); - }, - RemoveTracker: function(element, ref) { - removeTrackerFN(element); - } - }, - 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(); + } }); + }; - 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 editTrackerFN = function(element) { + if (current_hash.length === 0) return; -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 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(); + } + }); + }; + + 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 editTrackerFN = function(element) { - if (current_hash.length === 0) return; + torrentTrackersTable.setup('torrentTrackersTableDiv', 'torrentTrackersTableFixedHeaderDiv', torrentTrackersContextMenu); - 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() { - updateTrackersData(); - } - }).send(); -}; - -new ClipboardJS('#CopyTrackerUrl', { - text: function(trigger) { - return torrentTrackersTable.selectedRowsIds().join("\n"); - } -}); - -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); + initialize: function() {}, + + setup: function(table) { + this.table = $(table); + this.rows = new Hash(); + }, + + removeRow: function(url) { + if (this.rows.has(url)) { + const tr = this.rows.get(url); + tr.dispose(); + this.rows.erase(url); + return true; + } + return false; + }, + + 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; - } - return false; - }, + }, - removeAllRows: function() { - this.rows.each(function(tr, url) { - this.removeRow(url); - }.bind(this)); - }, + 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); + }, + }); - 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; - }, + let current_hash = ""; - insertRow: function(row) { - const url = row[0]; - if (this.rows.has(url)) { - const tableRow = this.rows.get(url); - this.updateRow(tableRow, row); + let loadWebSeedsDataTimer; + const loadWebSeedsData = function() { + if ($('prop_webseeds').hasClass('invisible') + || $('propertiesPanel_collapseToggle').hasClass('panel-expand')) { + // Tab changed, don't do anything 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); - }, -}); - -this.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; - } - 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(); - } + const new_hash = torrentsTable.getCurrentTorrentHash(); + if (new_hash === "") { + wsTable.removeAllRows(); clearTimeout(loadWebSeedsDataTimer); loadWebSeedsDataTimer = loadWebSeedsData.delay(10000); + return; } - }).send(); -}; + 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); + }, + 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(); + }; -updateWebSeedsData = function() { - clearTimeout(loadWebSeedsDataTimer); - loadWebSeedsData(); -}; + const updateData = 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 - - +