qBittorrent/src/webui/www/private/scripts/dynamicTable.js

3254 lines
132 KiB
JavaScript

/*
* MIT License
* Copyright (c) 2008 Ishan Arora <ishan@qbittorrent.org> & Christophe Dumez <chris@qbittorrent.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**************************************************************
Script : Dynamic Table
Version : 0.5
Authors : Ishan Arora & Christophe Dumez
Desc : Programmable sortable table
Licence : Open Source MIT Licence
**************************************************************/
'use strict';
if (window.qBittorrent === undefined) {
window.qBittorrent = {};
}
window.qBittorrent.DynamicTable = (function() {
const exports = function() {
return {
TorrentsTable: TorrentsTable,
TorrentPeersTable: TorrentPeersTable,
SearchResultsTable: SearchResultsTable,
SearchPluginsTable: SearchPluginsTable,
TorrentTrackersTable: TorrentTrackersTable,
BulkRenameTorrentFilesTable: BulkRenameTorrentFilesTable,
TorrentFilesTable: TorrentFilesTable,
LogMessageTable: LogMessageTable,
LogPeerTable: LogPeerTable,
RssFeedTable: RssFeedTable,
RssArticleTable: RssArticleTable,
RssDownloaderRulesTable: RssDownloaderRulesTable,
RssDownloaderFeedSelectionTable: RssDownloaderFeedSelectionTable,
RssDownloaderArticlesTable: RssDownloaderArticlesTable
};
};
const compareNumbers = (val1, val2) => {
if (val1 < val2)
return -1;
if (val1 > val2)
return 1;
return 0;
};
let DynamicTableHeaderContextMenuClass = null;
let ProgressColumnWidth = -1;
const DynamicTable = new Class({
initialize: function() {},
setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu) {
this.dynamicTableDivId = dynamicTableDivId;
this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId;
this.fixedTableHeader = $(dynamicTableFixedHeaderDivId).getElements('tr')[0];
this.hiddenTableHeader = $(dynamicTableDivId).getElements('tr')[0];
this.tableBody = $(dynamicTableDivId).getElements('tbody')[0];
this.rows = new Hash();
this.selectedRows = [];
this.columns = [];
this.contextMenu = contextMenu;
this.sortedColumn = LocalPreferences.get('sorted_column_' + this.dynamicTableDivId, 0);
this.reverseSort = LocalPreferences.get('reverse_sort_' + this.dynamicTableDivId, '0');
this.initColumns();
this.loadColumnsOrder();
this.updateTableHeaders();
this.setupCommonEvents();
this.setupHeaderEvents();
this.setupHeaderMenu();
this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === '1'));
},
setupCommonEvents: function() {
const scrollFn = function() {
$(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px';
}.bind(this);
$(this.dynamicTableDivId).addEvent('scroll', scrollFn);
// if the table exists within a panel
if ($(this.dynamicTableDivId).getParent('.panel')) {
const resizeFn = function() {
const panel = $(this.dynamicTableDivId).getParent('.panel');
let h = panel.getBoundingClientRect().height - $(this.dynamicTableFixedHeaderDivId).getBoundingClientRect().height;
$(this.dynamicTableDivId).style.height = h + 'px';
// Workaround due to inaccurate calculation of elements heights by browser
let n = 2;
// is panel vertical scrollbar visible or does panel content not fit?
while (((panel.clientWidth != panel.offsetWidth) || (panel.clientHeight != panel.scrollHeight)) && (n > 0)) {
--n;
h -= 0.5;
$(this.dynamicTableDivId).style.height = h + 'px';
}
this.lastPanelHeight = panel.getBoundingClientRect().height;
}.bind(this);
$(this.dynamicTableDivId).getParent('.panel').addEvent('resize', resizeFn);
this.lastPanelHeight = 0;
// Workaround. Resize event is called not always (for example it isn't called when browser window changes it's size)
const checkResizeFn = function() {
const tableDiv = $(this.dynamicTableDivId);
// dynamicTableDivId is not visible on the UI
if (!tableDiv) {
return;
}
const panel = tableDiv.getParent('.panel');
if (this.lastPanelHeight != panel.getBoundingClientRect().height) {
this.lastPanelHeight = panel.getBoundingClientRect().height;
panel.fireEvent('resize');
}
}.bind(this);
setInterval(checkResizeFn, 500);
}
},
setupHeaderEvents: function() {
this.currentHeaderAction = '';
this.canResize = false;
const resetElementBorderStyle = function(el, side) {
if ((side === 'left') || (side !== 'right')) {
el.setStyle('border-left-style', '');
el.setStyle('border-left-color', '');
el.setStyle('border-left-width', '');
}
if ((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);
});
}
this.currentHeaderAction = '';
}.bind(this);
const onCancel = function(el) {
this.currentHeaderAction = '';
this.setSortedColumn(el.columnName);
}.bind(this);
const onTouch = function(e) {
const column = e.target.columnName;
this.currentHeaderAction = '';
this.setSortedColumn(column);
}.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.addEvent('touchend', onTouch);
th.makeResizable({
modifiers: {
x: '',
y: ''
},
onBeforeStart: onBeforeStart,
onStart: onStart,
onDrag: onDrag,
onComplete: onComplete,
onCancel: onCancel
});
}
},
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);
}
}
});
}
},
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';
// reuse menu if already exists
const ul = $(menuId) ?? new Element('ul', {
id: menuId,
class: 'contextMenu scrollableMenu'
});
const createLi = function(columnName, text) {
const html = '<a href="#' + columnName + '" ><img src="images/checked-completed.svg"/>' + window.qBittorrent.Misc.escapeHtml(text) + '</a>';
return new Element('li', {
html: html
});
};
const actions = {};
const onMenuItemClicked = function(element, ref, action) {
this.showColumn(action, this.columns[action].visible === '0');
}.bind(this);
// recreate child nodes when reusing (enables the context menu to work correctly)
if (ul.hasChildNodes()) {
while (ul.firstChild) {
ul.removeChild(ul.lastChild);
}
}
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) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === 'number') && (typeof(value2) === 'number'))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column['updateTd'] = function(td, row) {
const value = this.getRowValue(row);
td.set('text', value);
td.set('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.set('text', 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);
},
/**
* @param {string} column name to sort by
* @param {string|null} reverse defaults to implementation-specific behavior when not specified. Should only be passed when restoring previous state.
*/
setSortedColumn: function(column, reverse = null) {
if (column != this.sortedColumn) {
const oldColumn = this.sortedColumn;
this.sortedColumn = column;
this.reverseSort = reverse ?? '0';
this.setSortedColumnIcon(column, oldColumn, false);
}
else {
// Toggle sort order
this.reverseSort = reverse ?? (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) {
// ensure rowId is a string
const rowId = `${data['rowId']}`;
let row;
if (!this.rows.has(rowId)) {
row = {
'full_data': {},
'rowId': rowId
};
this.rows.set(rowId, row);
}
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 = 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 (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');
// set tabindex so element receives keydown events
// more info: https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
tr.setProperty("tabindex", "-1");
const rowId = rows[rowPos]['rowId'];
tr.setProperty("data-row-id", rowId);
tr['rowId'] = rowId;
tr._this = this;
tr.addEvent('contextmenu', function(e) {
if (!this._this.isRowSelected(this.rowId)) {
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
return true;
});
tr.addEvent('click', function(e) {
e.stop();
if (e.control || e.meta) {
// CTRL/CMD ⌘ key was pressed
if (this._this.isRowSelected(this.rowId))
this._this.deselectRow(this.rowId);
else
this._this.selectRow(this.rowId);
}
else if (e.shift && (this._this.selectedRows.length == 1)) {
// Shift key was pressed
this._this.selectRows(this._this.getSelectedRowId(), this.rowId);
}
else {
// Simple selection
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
return false;
});
tr.addEvent('touchstart', function(e) {
if (!this._this.isRowSelected(this.rowId)) {
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
});
tr.addEvent('keydown', function(event) {
switch (event.key) {
case "up":
this._this.selectPreviousRow();
return false;
case "down":
this._this.selectNextRow();
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);
}
// 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);
}
}
let rowPos = rows.length;
while ((rowPos < trs.length) && (trs.length > 0)) {
trs.pop().destroy();
}
},
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 (Object.hasOwn(data, 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.destroy();
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.pop().destroy();
}
},
selectedRowsIds: function() {
return this.selectedRows.slice();
},
getRowIds: function() {
return this.rows.getKeys();
},
selectNextRow: function() {
const visibleRows = $(this.dynamicTableDivId).getElements("tbody tr").filter(e => e.getStyle("display") !== "none");
const selectedRowId = this.getSelectedRowId();
let selectedIndex = -1;
for (let i = 0; i < visibleRows.length; ++i) {
const row = visibleRows[i];
if (row.getProperty("data-row-id") === selectedRowId) {
selectedIndex = i;
break;
}
}
const isLastRowSelected = (selectedIndex >= (visibleRows.length - 1));
if (!isLastRowSelected) {
this.deselectAll();
const newRow = visibleRows[selectedIndex + 1];
this.selectRow(newRow.getProperty("data-row-id"));
}
},
selectPreviousRow: function() {
const visibleRows = $(this.dynamicTableDivId).getElements("tbody tr").filter(e => e.getStyle("display") !== "none");
const selectedRowId = this.getSelectedRowId();
let selectedIndex = -1;
for (let i = 0; i < visibleRows.length; ++i) {
const row = visibleRows[i];
if (row.getProperty("data-row-id") === selectedRowId) {
selectedIndex = i;
break;
}
}
const isFirstRowSelected = selectedIndex <= 0;
if (!isFirstRowSelected) {
this.deselectAll();
const newRow = visibleRows[selectedIndex - 1];
this.selectRow(newRow.getProperty("data-row-id"));
}
},
});
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('popularity', '', 'QBT_TR(Popularity)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.newColumn('reannounce', '', 'QBT_TR(Reannounce In)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.columns['time_active'].dataProperties.push('seeding_time');
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
// state_icon
this.columns['state_icon'].updateTd = function(td, row) {
let state = this.getRowValue(row);
let img_path;
// normalize states
switch (state) {
case "forcedDL":
case "metaDL":
case "forcedMetaDL":
case "downloading":
state = "downloading";
img_path = "images/downloading.svg";
break;
case "forcedUP":
case "uploading":
state = "uploading";
img_path = "images/upload.svg";
break;
case "stalledUP":
state = "stalledUP";
img_path = "images/stalledUP.svg";
break;
case "stalledDL":
state = "stalledDL";
img_path = "images/stalledDL.svg";
break;
case "stoppedDL":
state = "torrent-stop";
img_path = "images/stopped.svg";
break;
case "stoppedUP":
state = "checked-completed";
img_path = "images/checked-completed.svg";
break;
case "queuedDL":
case "queuedUP":
state = "queued";
img_path = "images/queued.svg";
break;
case "checkingDL":
case "checkingUP":
case "queuedForChecking":
case "checkingResumeData":
state = "force-recheck";
img_path = "images/force-recheck.svg";
break;
case "moving":
state = "moving";
img_path = "images/set-location.svg";
break;
case "error":
case "unknown":
case "missingFiles":
state = "error";
img_path = "images/error.svg";
break;
default:
break; // do nothing
}
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 {
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 "forcedMetaDL":
status = "QBT_TR([F] Downloading metadata)QBT_TR[CONTEXT=TransferListDelegate]";
break;
case "forcedDL":
status = "QBT_TR([F] Downloading)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 "stoppedDL":
status = "QBT_TR(Stopped)QBT_TR[CONTEXT=TransferListDelegate]";
break;
case "stoppedUP":
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('text', 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('text', 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;
return compareNumbers(row1_val, row2_val);
};
// name, category, tags
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, total_size
this.columns['size'].updateTd = function(td, row) {
const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false);
td.set('text', size);
td.set('title', size);
};
this.columns['total_size'].updateTd = this.columns['size'].updateTd;
// progress
this.columns['progress'].updateTd = function(td, row) {
const progress = this.getRowValue(row);
let progressFormatted = (progress * 100).round(1);
if ((progressFormatted == 100.0) && (progress != 1.0))
progressFormatted = 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() != progressFormatted)
div.setValue(progressFormatted);
}
else {
if (ProgressColumnWidth < 0)
ProgressColumnWidth = td.offsetWidth;
td.adopt(new window.qBittorrent.ProgressBar.ProgressBar(progressFormatted.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 value = num_seeds;
if (num_complete != -1)
value += ' (' + num_complete + ')';
td.set('text', value);
td.set('title', value);
};
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);
const result = compareNumbers(num_complete1, num_complete2);
if (result !== 0)
return result;
return compareNumbers(num_seeds1, num_seeds2);
};
// 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('text', 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), window.qBittorrent.Misc.MAX_ETA);
td.set('text', 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('text', string);
td.set('title', string);
};
// popularity
this.columns['popularity'].updateTd = function(td, row) {
const value = this.getRowValue(row);
const popularity = (value === -1) ? '∞' : window.qBittorrent.Misc.toFixedPointString(value, 2);
td.set('text', popularity);
td.set('title', popularity);
};
// added on
this.columns['added_on'].updateTd = function(td, row) {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
td.set('text', 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('text', '');
td.set('title', '');
}
else {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
td.set('text', date);
td.set('title', date);
}
};
// dl_limit, up_limit
this.columns['dl_limit'].updateTd = function(td, row) {
const speed = this.getRowValue(row);
if (speed === 0) {
td.set('text', '∞');
td.set('title', '∞');
}
else {
const formattedSpeed = window.qBittorrent.Misc.friendlyUnit(speed, true);
td.set('text', formattedSpeed);
td.set('title', formattedSpeed);
}
};
this.columns['up_limit'].updateTd = this.columns['dl_limit'].updateTd;
// downloaded, uploaded, downloaded_session, uploaded_session, amount_left
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;
// time active
this.columns['time_active'].updateTd = function(td, row) {
const activeTime = this.getRowValue(row, 0);
const seedingTime = this.getRowValue(row, 1);
const time = (seedingTime > 0)
? ('QBT_TR(%1 (seeded for %2))QBT_TR[CONTEXT=TransferListDelegate]'
.replace('%1', window.qBittorrent.Misc.friendlyDuration(activeTime))
.replace('%2', window.qBittorrent.Misc.friendlyDuration(seedingTime)))
: window.qBittorrent.Misc.friendlyDuration(activeTime);
td.set('text', time);
td.set('title', time);
};
// completed
this.columns['completed'].updateTd = this.columns['size'].updateTd;
// max_ratio
this.columns['max_ratio'].updateTd = this.columns['ratio'].updateTd;
// seen_complete
this.columns['seen_complete'].updateTd = this.columns['completion_on'].updateTd;
// last_activity
this.columns['last_activity'].updateTd = function(td, row) {
const val = this.getRowValue(row);
if (val < 1) {
td.set('text', '∞');
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('text', formattedVal);
td.set('title', formattedVal);
}
};
// availability
this.columns['availability'].updateTd = function(td, row) {
const value = window.qBittorrent.Misc.toFixedPointString(this.getRowValue(row), 3);
td.set('text', value);
td.set('title', value);
};
// reannounce
this.columns['reannounce'].updateTd = function(td, row) {
const time = window.qBittorrent.Misc.friendlyDuration(this.getRowValue(row));
td.set('text', time);
td.set('title', time);
};
},
applyFilter: function(row, filterName, categoryHash, tagHash, trackerHash, 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') === -1))
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') === -1))
return false;
break;
case 'stopped':
if (state.indexOf('stopped') === -1)
return false;
break;
case 'running':
if (state.indexOf('stopped') > -1)
return false;
break;
case 'stalled':
if ((state != 'stalledUP') && (state != 'stalledDL'))
return false;
break;
case 'stalled_uploading':
if (state != 'stalledUP')
return false;
break;
case 'stalled_downloading':
if (state != 'stalledDL')
return false;
break;
case 'inactive':
inactive = true;
// fallthrough
case 'active':
if (state == 'stalledDL')
r = (row['full_data'].upspeed > 0);
else
r = (state == 'metaDL') || (state == 'forcedMetaDL') || (state == 'downloading') || (state == 'forcedDL') || (state == 'uploading') || (state == 'forcedUP');
if (r == inactive)
return false;
break;
case 'checking':
if ((state !== 'checkingUP') && (state !== 'checkingDL') && (state !== 'checkingResumeData'))
return false;
break;
case 'moving':
if (state !== 'moving')
return false;
break;
case 'errored':
if ((state != 'error') && (state != 'unknown') && (state != 'missingFiles'))
return false;
break;
}
switch (categoryHash) {
case CATEGORIES_ALL:
break; // do nothing
case CATEGORIES_UNCATEGORIZED:
if (row['full_data'].category.length !== 0)
return false;
break; // do nothing
default:
if (!useSubcategories) {
if (categoryHash !== window.qBittorrent.Client.genHash(row['full_data'].category))
return false;
}
else {
const selectedCategory = category_list.get(categoryHash);
if (selectedCategory !== undefined) {
const selectedCategoryName = selectedCategory.name + "/";
const torrentCategoryName = row['full_data'].category + "/";
if (!torrentCategoryName.startsWith(selectedCategoryName))
return false;
}
}
break;
}
switch (tagHash) {
case TAGS_ALL:
break; // do nothing
case TAGS_UNTAGGED:
if (row['full_data'].tags.length !== 0)
return false;
break; // do nothing
default: {
const tagHashes = row['full_data'].tags.split(', ').map(tag => window.qBittorrent.Client.genHash(tag));
if (!tagHashes.contains(tagHash))
return false;
break;
}
}
const trackerHashInt = Number.parseInt(trackerHash, 10);
switch (trackerHashInt) {
case TRACKERS_ALL:
break; // do nothing
case TRACKERS_TRACKERLESS:
if (row['full_data'].trackers_count !== 0)
return false;
break;
default: {
const tracker = trackerList.get(trackerHashInt);
if (tracker) {
let found = false;
for (const torrents of tracker.trackerTorrentMap.values()) {
if (torrents.includes(row['full_data'].rowId)) {
found = true;
break;
}
}
if (!found)
return false;
}
break;
}
}
if ((filterTerms !== undefined) && (filterTerms !== null)) {
if (filterTerms instanceof RegExp) {
if (!filterTerms.test(name))
return false;
}
else {
if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(name, filterTerms))
return false;
}
}
return true;
},
getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash, trackerHash) {
let cnt = 0;
const rows = this.rows.getValues();
for (let i = 0; i < rows.length; ++i) {
if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, trackerHash, null))
++cnt;
}
return cnt;
},
getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash, trackerHash) {
const rowsHashes = [];
const rows = this.rows.getValues();
for (let i = 0; i < rows.length; ++i) {
if (this.applyFilter(rows[i], filterName, categoryHash, tagHash, trackerHash, null))
rowsHashes.push(rows[i]['rowId']);
}
return rowsHashes;
},
getFilteredAndSortedRows: function() {
const filteredRows = [];
const rows = this.rows.getValues();
const useRegex = $('torrentsFilterRegexBox').checked;
const filterText = $('torrentsFilterInput').value.trim().toLowerCase();
const filterTerms = (filterText.length > 0)
? (useRegex ? new RegExp(filterText) : filterText.split(" "))
: null;
for (let i = 0; i < rows.length; ++i) {
if (this.applyFilter(rows[i], selected_filter, selected_category, selectedTag, selectedTracker, 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('stopped') > -1)
startFN();
else
stopFN();
return true;
});
tr.addClass("torrentsTableContextMenuTarget");
},
getCurrentTorrentID: function() {
return this.getSelectedRowId();
},
onSelectedRowChanged: function() {
updatePropertiesPanel();
}
});
const TorrentPeersTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn('country', '', 'QBT_TR(Country/Region)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('peer_id_client', '', 'QBT_TR(Peer ID Client)QBT_TR[CONTEXT=PeerListWidget]', 60, false);
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].destroy();
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;
};
// flags
this.columns['flags'].updateTd = function(td, row) {
td.set('text', this.getRowValue(row, 0));
td.set('title', this.getRowValue(row, 1));
};
// progress
this.columns['progress'].updateTd = function(td, row) {
const progress = this.getRowValue(row);
let progressFormatted = (progress * 100).round(1);
if ((progressFormatted == 100.0) && (progress != 1.0))
progressFormatted = 99.9;
progressFormatted += "%";
td.set('text', progressFormatted);
td.set('title', progressFormatted);
};
// dl_speed, up_speed
this.columns['dl_speed'].updateTd = function(td, row) {
const speed = this.getRowValue(row);
if (speed === 0) {
td.set('text', '');
td.set('title', '');
}
else {
const formattedSpeed = window.qBittorrent.Misc.friendlyUnit(speed, true);
td.set('text', 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('text', downloaded);
td.set('title', downloaded);
};
this.columns['uploaded'].updateTd = this.columns['downloaded'].updateTd;
// relevance
this.columns['relevance'].updateTd = this.columns['progress'].updateTd;
// files
this.columns['files'].updateTd = function(td, row) {
const value = this.getRowValue(row, 0);
td.set('text', value.replace(/\n/g, ';'));
td.set('title', value);
};
}
});
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 displaySize = function(td, row) {
const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false);
td.set('text', size);
td.set('title', size);
};
const displayNum = function(td, row) {
const value = this.getRowValue(row);
const formattedValue = (value === "-1") ? "Unknown" : value;
td.set('text', formattedValue);
td.set('title', formattedValue);
};
this.columns['fileSize'].updateTd = displaySize;
this.columns['nbSeeders'].updateTd = displayNum;
this.columns['nbLeechers'].updateTd = displayNum;
},
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() {
this.columns['enabled'].updateTd = function(td, row) {
const value = this.getRowValue(row);
if (value) {
td.set('text', 'QBT_TR(Yes)QBT_TR[CONTEXT=SearchPluginsTable]');
td.set('title', 'QBT_TR(Yes)QBT_TR[CONTEXT=SearchPluginsTable]');
td.getParent("tr").addClass("green");
td.getParent("tr").removeClass("red");
}
else {
td.set('text', 'QBT_TR(No)QBT_TR[CONTEXT=SearchPluginsTable]');
td.set('title', 'QBT_TR(No)QBT_TR[CONTEXT=SearchPluginsTable]');
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(Times Downloaded)QBT_TR[CONTEXT=TrackerListWidget]', 100, true);
this.newColumn('message', '', 'QBT_TR(Message)QBT_TR[CONTEXT=TrackerListWidget]', 250, true);
},
});
const BulkRenameTorrentFilesTable = 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,
fileId: -1,
checked: node.checked,
path: node.path,
original: node.original,
renamed: node.renamed
};
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);
},
getSelectedRows: function() {
const nodes = this.fileTree.toArray();
return nodes.filter(x => x.checked == 0);
},
initColumns: function() {
// Blocks saving header width (because window width isn't saved)
LocalPreferences.remove('column_' + "checked" + '_width_' + this.dynamicTableDivId);
LocalPreferences.remove('column_' + "original" + '_width_' + this.dynamicTableDivId);
LocalPreferences.remove('column_' + "renamed" + '_width_' + this.dynamicTableDivId);
this.newColumn('checked', '', '', 50, true);
this.newColumn('original', '', 'QBT_TR(Original)QBT_TR[CONTEXT=TrackerListWidget]', 270, true);
this.newColumn('renamed', '', 'QBT_TR(Renamed)QBT_TR[CONTEXT=TrackerListWidget]', 220, true);
this.initColumnsFunctions();
},
/**
* Toggles the global checkbox and all checkboxes underneath
*/
toggleGlobalCheckbox: function() {
const checkbox = $('rootMultiRename_cb');
const checkboxes = $$('input.RenamingCB');
for (let i = 0; i < checkboxes.length; ++i) {
const node = this.getNode(i);
if (checkbox.checked || checkbox.indeterminate) {
let cb = checkboxes[i];
cb.checked = true;
cb.indeterminate = false;
cb.state = "checked";
node.checked = 0;
node.full_data.checked = node.checked;
}
else {
let cb = checkboxes[i];
cb.checked = false;
cb.indeterminate = false;
cb.state = "unchecked";
node.checked = 1;
node.full_data.checked = node.checked;
}
}
this.updateGlobalCheckbox();
},
toggleNodeTreeCheckbox: function(rowId, checkState) {
const node = this.getNode(rowId);
node.checked = checkState;
node.full_data.checked = checkState;
const checkbox = $(`cbRename${rowId}`);
checkbox.checked = node.checked == 0;
checkbox.state = checkbox.checked ? "checked" : "unchecked";
for (let i = 0; i < node.children.length; ++i) {
this.toggleNodeTreeCheckbox(node.children[i].rowId, checkState);
}
},
updateGlobalCheckbox: function() {
const checkbox = $('rootMultiRename_cb');
const checkboxes = $$('input.RenamingCB');
const isAllChecked = function() {
for (let i = 0; i < checkboxes.length; ++i) {
if (!checkboxes[i].checked)
return false;
}
return true;
};
const isAllUnchecked = function() {
for (let i = 0; i < checkboxes.length; ++i) {
if (checkboxes[i].checked)
return false;
}
return true;
};
if (isAllChecked()) {
checkbox.state = "checked";
checkbox.indeterminate = false;
checkbox.checked = true;
}
else if (isAllUnchecked()) {
checkbox.state = "unchecked";
checkbox.indeterminate = false;
checkbox.checked = false;
}
else {
checkbox.state = "partial";
checkbox.indeterminate = true;
checkbox.checked = false;
}
},
initColumnsFunctions: function() {
const that = this;
// checked
this.columns['checked'].updateTd = function(td, row) {
const id = row.rowId;
const value = this.getRowValue(row);
const treeImg = new Element('img', {
src: 'images/L.gif',
styles: {
'margin-bottom': -2
}
});
const checkbox = new Element('input');
checkbox.set('type', 'checkbox');
checkbox.set('id', 'cbRename' + id);
checkbox.set('data-id', id);
checkbox.set('class', 'RenamingCB');
checkbox.addEvent('click', function(e) {
const node = that.getNode(id);
node.checked = e.target.checked ? 0 : 1;
node.full_data.checked = node.checked;
that.updateGlobalCheckbox();
that.onRowSelectionChange(node);
e.stopPropagation();
});
checkbox.checked = value == 0;
checkbox.state = checkbox.checked ? "checked" : "unchecked";
checkbox.indeterminate = false;
td.adopt(treeImg, checkbox);
};
// original
this.columns['original'].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 dirImgId = 'renameTableDirImg' + id;
if ($(dirImgId)) {
// just update file name
$(fileNameId).set('text', value);
}
else {
const span = new Element('span', {
text: value,
id: fileNameId
});
const dirImg = new Element('img', {
src: 'images/directory.svg',
styles: {
'width': 15,
'padding-right': 5,
'margin-bottom': -3,
'margin-left': (node.depth * 20)
},
id: dirImgId
});
const html = dirImg.outerHTML + span.outerHTML;
td.set('html', html);
}
}
else { // is file
const value = this.getRowValue(row);
const span = new Element('span', {
text: value,
id: fileNameId,
styles: {
'margin-left': ((node.depth + 1) * 20)
}
});
td.set('html', span.outerHTML);
}
};
// renamed
this.columns['renamed'].updateTd = function(td, row) {
const id = row.rowId;
const fileNameRenamedId = 'filesTablefileRenamed' + id;
const value = this.getRowValue(row);
const span = new Element('span', {
text: value,
id: fileNameRenamedId,
});
td.set('html', span.outerHTML);
};
},
onRowSelectionChange: function(row) {},
selectRow: function() {
return;
},
reselectRows: function(rowIds) {
const that = this;
this.deselectAll();
this.tableBody.getElements('tr').each(function(tr) {
if (rowIds.indexOf(tr.rowId) > -1) {
const node = that.getNode(tr.rowId);
node.checked = 0;
node.full_data.checked = 0;
const checkbox = tr.children[0].getElement('input');
checkbox.state = "checked";
checkbox.indeterminate = false;
checkbox.checked = true;
}
});
this.updateGlobalCheckbox();
},
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 === "original") {
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;
}
}
if (window.qBittorrent.Misc.containsAllTerms(node.original, filterTerms)) {
const row = this.getRow(node);
filteredRows.push(row);
return true;
}
return false;
},
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 [];
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 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;
}
// 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)));
},
setupTr: function(tr) {
tr.addEvent('keydown', function(event) {
switch (event.key) {
case "left":
qBittorrent.PropFiles.collapseFolder(this._this.getSelectedRowId());
return false;
case "right":
qBittorrent.PropFiles.expandFolder(this._this.getSelectedRowId());
return false;
}
});
}
});
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(Total 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('text', size);
td.set('title', size);
};
const displayPercentage = function(td, row) {
const value = window.qBittorrent.Misc.friendlyPercentage(this.getRowValue(row));
td.set('text', value);
td.set('title', value);
};
// checked
this.columns['checked'].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));
}
};
// name
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).set('text', value);
}
else {
const collapseIcon = new Element('img', {
src: 'images/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: value,
id: fileNameId
});
const dirImg = new Element('img', {
src: 'images/directory.svg',
styles: {
'width': 15,
'padding-right': 5,
'margin-bottom': -3
},
id: dirImgId
});
const html = collapseIcon.outerHTML + dirImg.outerHTML + span.outerHTML;
td.set('html', html);
}
}
else {
const value = this.getRowValue(row);
const span = new Element('span', {
text: value,
id: fileNameId,
styles: {
'margin-left': ((node.depth + 1) * 20)
}
});
td.set('html', span.outerHTML);
}
};
// size
this.columns['size'].updateTd = displaySize;
// progress
this.columns['progress'].updateTd = function(td, row) {
const id = row.rowId;
const value = this.getRowValue(row);
const progressBar = $('pbf_' + id);
if (progressBar === null) {
td.adopt(new window.qBittorrent.ProgressBar.ProgressBar(value.toFloat(), {
id: 'pbf_' + id,
width: 80
}));
}
else {
progressBar.setValue(value.toFloat());
}
};
// priority
this.columns['priority'].updateTd = function(td, row) {
const id = row.rowId;
const value = this.getRowValue(row);
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));
};
// remaining, availability
this.columns['remaining'].updateTd = displaySize;
this.columns['availability'].updateTd = displayPercentage;
},
altRow: function() {
let addClass = false;
const trs = this.tableBody.getElements('tr');
trs.each(function(tr) {
if (tr.hasClass("invisible"))
return;
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;
}
}
if (window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) {
const row = this.getRow(node);
filteredRows.push(row);
return true;
}
return false;
},
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 [];
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 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;
}
// 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)));
},
setupTr: function(tr) {
tr.addEvent('keydown', function(event) {
switch (event.key) {
case "left":
qBittorrent.PropFiles.collapseFolder(this._this.getSelectedRowId());
return false;
case "right":
qBittorrent.PropFiles.expandFolder(this._this.getSelectedRowId());
return false;
}
});
}
});
const RssFeedTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn('state_icon', '', '', 30, true);
this.newColumn('name', '', 'QBT_TR(RSS feeds)QBT_TR[CONTEXT=FeedListWidget]', -1, true);
this.columns['state_icon'].dataProperties[0] = '';
// map name row to "[name] ([unread])"
this.columns['name'].dataProperties.push('unread');
this.columns['name'].updateTd = function(td, row) {
const name = this.getRowValue(row, 0);
const unreadCount = this.getRowValue(row, 1);
let value = name + ' (' + unreadCount + ')';
td.set('text', value);
td.set('title', value);
};
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return this.rows.getValues();
},
selectRow: function(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
const rows = this.rows.getValues();
let path = '';
for (let i = 0; i < rows.length; ++i) {
if (rows[i].rowId === rowId) {
path = rows[i].full_data.dataPath;
break;
}
}
window.qBittorrent.Rss.showRssFeed(path);
},
setupTr: function(tr) {
tr.addEvent('dblclick', function(e) {
if (this.rowId !== 0) {
window.qBittorrent.Rss.moveItem(this._this.rows.get(this.rowId).full_data.dataPath);
return true;
}
});
},
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 (Object.hasOwn(data, this.columns[i].dataProperties[0]))
this.columns[i].updateTd(tds[i], row);
}
row['data'] = {};
tds[0].style.overflow = 'visible';
let indentation = row.full_data.indentation;
tds[0].style.paddingLeft = (indentation * 32 + 4) + 'px';
tds[1].style.paddingLeft = (indentation * 32 + 4) + 'px';
},
updateIcons: function() {
// state_icon
this.rows.each(row => {
let img_path;
switch (row.full_data.status) {
case 'default':
img_path = 'images/application-rss.svg';
break;
case 'hasError':
img_path = 'images/task-reject.svg';
break;
case 'isLoading':
img_path = 'images/spinner.gif';
break;
case 'unread':
img_path = 'images/mail-inbox.svg';
break;
case 'isFolder':
img_path = 'images/folder-documents.svg';
break;
}
let td;
for (let i = 0; i < this.tableBody.rows.length; ++i) {
if (this.tableBody.rows[i].rowId === row.rowId) {
td = this.tableBody.rows[i].children[0];
break;
}
}
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', status);
}
}
else {
td.adopt(new Element('img', {
'src': img_path,
'class': 'stateIcon',
'height': '22px',
'width': '22px'
}));
}
});
},
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column['name'] = name;
column['title'] = name;
column['visible'] = defaultVisible;
column['force_hide'] = false;
column['caption'] = caption;
column['style'] = style;
if (defaultWidth !== -1) {
column['width'] = 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) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === 'number') && (typeof(value2) === 'number'))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column['updateTd'] = function(td, row) {
const value = this.getRowValue(row);
td.set('text', value);
td.set('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'));
},
setupCommonEvents: function() {
const scrollFn = function() {
$(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px';
}.bind(this);
$(this.dynamicTableDivId).addEvent('scroll', scrollFn);
}
});
const RssArticleTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn('name', '', 'QBT_TR(Torrents: (double-click to download))QBT_TR[CONTEXT=RSSWidget]', -1, true);
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return this.rows.getValues();
},
selectRow: function(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
const rows = this.rows.getValues();
let articleId = '';
let feedUid = '';
for (let i = 0; i < rows.length; ++i) {
if (rows[i].rowId === rowId) {
articleId = rows[i].full_data.dataId;
feedUid = rows[i].full_data.feedUid;
this.tableBody.rows[rows[i].rowId].removeClass('unreadArticle');
break;
}
}
window.qBittorrent.Rss.showDetails(feedUid, articleId);
},
setupTr: function(tr) {
tr.addEvent('dblclick', function(e) {
showDownloadPage([this._this.rows.get(this.rowId).full_data.torrentURL]);
return true;
});
tr.addClass('torrentsTableContextMenuTarget');
},
updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? 'full_data' : 'data'];
if (!row.full_data.isRead)
tr.addClass('unreadArticle');
else
tr.removeClass('unreadArticle');
const tds = tr.getElements('td');
for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0]))
this.columns[i].updateTd(tds[i], row);
}
row['data'] = {};
},
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column['name'] = name;
column['title'] = name;
column['visible'] = defaultVisible;
column['force_hide'] = false;
column['caption'] = caption;
column['style'] = style;
if (defaultWidth !== -1) {
column['width'] = 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) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === 'number') && (typeof(value2) === 'number'))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column['updateTd'] = function(td, row) {
const value = this.getRowValue(row);
td.set('text', value);
td.set('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'));
},
setupCommonEvents: function() {
const scrollFn = function() {
$(this.dynamicTableFixedHeaderDivId).getElements('table')[0].style.left = -$(this.dynamicTableDivId).scrollLeft + 'px';
}.bind(this);
$(this.dynamicTableDivId).addEvent('scroll', scrollFn);
}
});
const RssDownloaderRulesTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn('checked', '', '', 30, true);
this.newColumn('name', '', '', -1, true);
this.columns['checked'].updateTd = function(td, row) {
if ($('cbRssDlRule' + row.rowId) === null) {
const checkbox = new Element('input');
checkbox.set('type', 'checkbox');
checkbox.set('id', 'cbRssDlRule' + row.rowId);
checkbox.checked = row.full_data.checked;
checkbox.addEvent('click', function(e) {
window.qBittorrent.RssDownloader.rssDownloaderRulesTable.updateRowData({
rowId: row.rowId,
checked: this.checked
});
window.qBittorrent.RssDownloader.modifyRuleState(row.full_data.name, 'enabled', this.checked);
e.stopPropagation();
});
td.append(checkbox);
}
else {
$('cbRssDlRule' + row.rowId).checked = row.full_data.checked;
}
};
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return this.rows.getValues();
},
setupTr: function(tr) {
tr.addEvent('dblclick', function(e) {
window.qBittorrent.RssDownloader.renameRule(this._this.rows.get(this.rowId).full_data.name);
return true;
});
},
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column['name'] = name;
column['title'] = name;
column['visible'] = defaultVisible;
column['force_hide'] = false;
column['caption'] = caption;
column['style'] = style;
if (defaultWidth !== -1) {
column['width'] = 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) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === 'number') && (typeof(value2) === 'number'))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column['updateTd'] = function(td, row) {
const value = this.getRowValue(row);
td.set('text', value);
td.set('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'));
},
selectRow: function(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
const rows = this.rows.getValues();
let name = '';
for (let i = 0; i < rows.length; ++i) {
if (rows[i].rowId === rowId) {
name = rows[i].full_data.name;
break;
}
}
window.qBittorrent.RssDownloader.showRule(name);
}
});
const RssDownloaderFeedSelectionTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn('checked', '', '', 30, true);
this.newColumn('name', '', '', -1, true);
this.columns['checked'].updateTd = function(td, row) {
if ($('cbRssDlFeed' + row.rowId) === null) {
const checkbox = new Element('input');
checkbox.set('type', 'checkbox');
checkbox.set('id', 'cbRssDlFeed' + row.rowId);
checkbox.checked = row.full_data.checked;
checkbox.addEvent('click', function(e) {
window.qBittorrent.RssDownloader.rssDownloaderFeedSelectionTable.updateRowData({
rowId: row.rowId,
checked: this.checked
});
e.stopPropagation();
});
td.append(checkbox);
}
else {
$('cbRssDlFeed' + row.rowId).checked = row.full_data.checked;
}
};
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return this.rows.getValues();
},
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column['name'] = name;
column['title'] = name;
column['visible'] = defaultVisible;
column['force_hide'] = false;
column['caption'] = caption;
column['style'] = style;
if (defaultWidth !== -1) {
column['width'] = 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) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === 'number') && (typeof(value2) === 'number'))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column['updateTd'] = function(td, row) {
const value = this.getRowValue(row);
td.set('text', value);
td.set('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'));
},
selectRow: function() {}
});
const RssDownloaderArticlesTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn('name', '', '', -1, true);
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return this.rows.getValues();
},
newColumn: function(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column['name'] = name;
column['title'] = name;
column['visible'] = defaultVisible;
column['force_hide'] = false;
column['caption'] = caption;
column['style'] = style;
if (defaultWidth !== -1) {
column['width'] = 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) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === 'number') && (typeof(value2) === 'number'))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column['updateTd'] = function(td, row) {
const value = this.getRowValue(row);
td.set('text', value);
td.set('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'));
},
selectRow: function() {},
updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? 'full_data' : 'data'];
if (row.full_data.isFeed) {
tr.addClass('articleTableFeed');
tr.removeClass('articleTableArticle');
}
else {
tr.removeClass('articleTableFeed');
tr.addClass('articleTableArticle');
}
const tds = tr.getElements('td');
for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0]))
this.columns[i].updateTd(tds[i], row);
}
row['data'] = {};
}
});
const LogMessageTable = new Class({
Extends: DynamicTable,
filterText: '',
filteredLength: function() {
return this.tableBody.getElements('tr').length;
},
initColumns: function() {
this.newColumn('rowId', '', 'QBT_TR(ID)QBT_TR[CONTEXT=ExecutionLogWidget]', 50, true);
this.newColumn('message', '', 'QBT_TR(Message)QBT_TR[CONTEXT=ExecutionLogWidget]', 350, true);
this.newColumn('timestamp', '', 'QBT_TR(Timestamp)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
this.newColumn('type', '', 'QBT_TR(Log Type)QBT_TR[CONTEXT=ExecutionLogWidget]', 100, true);
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
this.columns['timestamp'].updateTd = function(td, row) {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
td.set({ 'text': date, 'title': date });
};
this.columns['type'].updateTd = function(td, row) {
//Type of the message: Log::NORMAL: 1, Log::INFO: 2, Log::WARNING: 4, Log::CRITICAL: 8
let logLevel, addClass;
switch (this.getRowValue(row).toInt()) {
case 1:
logLevel = 'QBT_TR(Normal)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'logNormal';
break;
case 2:
logLevel = 'QBT_TR(Info)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'logInfo';
break;
case 4:
logLevel = 'QBT_TR(Warning)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'logWarning';
break;
case 8:
logLevel = 'QBT_TR(Critical)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'logCritical';
break;
default:
logLevel = 'QBT_TR(Unknown)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'logUnknown';
break;
}
td.set({ 'text': logLevel, 'title': logLevel });
td.getParent('tr').set('class', 'logTableRow ' + addClass);
};
},
getFilteredAndSortedRows: function() {
let filteredRows = [];
const rows = this.rows.getValues();
this.filterText = window.qBittorrent.Log.getFilterText();
const filterTerms = (this.filterText.length > 0) ? this.filterText.toLowerCase().split(' ') : [];
const logLevels = window.qBittorrent.Log.getSelectedLevels();
if ((filterTerms.length > 0) || (logLevels.length < 4)) {
for (let i = 0; i < rows.length; ++i) {
if (logLevels.indexOf(rows[i].full_data.type.toString()) == -1)
continue;
if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(rows[i].full_data.message, filterTerms))
continue;
filteredRows.push(rows[i]);
}
}
else {
filteredRows = rows;
}
filteredRows.sort(function(row1, row2) {
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
return (this.reverseSort == '0') ? res : -res;
}.bind(this));
return filteredRows;
},
setupCommonEvents: function() {},
setupTr: function(tr) {
tr.addClass('logTableRow');
}
});
const LogPeerTable = new Class({
Extends: LogMessageTable,
initColumns: function() {
this.newColumn('rowId', '', 'QBT_TR(ID)QBT_TR[CONTEXT=ExecutionLogWidget]', 50, true);
this.newColumn('ip', '', 'QBT_TR(IP)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
this.newColumn('timestamp', '', 'QBT_TR(Timestamp)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
this.newColumn('blocked', '', 'QBT_TR(Status)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
this.newColumn('reason', '', 'QBT_TR(Reason)QBT_TR[CONTEXT=ExecutionLogWidget]', 150, true);
this.columns['timestamp'].updateTd = function(td, row) {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
td.set({ 'text': date, 'title': date });
};
this.columns['blocked'].updateTd = function(td, row) {
let status, addClass;
if (this.getRowValue(row)) {
status = 'QBT_TR(Blocked)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'peerBlocked';
}
else {
status = 'QBT_TR(Banned)QBT_TR[CONTEXT=ExecutionLogWidget]';
addClass = 'peerBanned';
}
td.set({ 'text': status, 'title': status });
td.getParent('tr').set('class', 'logTableRow ' + addClass);
};
},
getFilteredAndSortedRows: function() {
let filteredRows = [];
const rows = this.rows.getValues();
this.filterText = window.qBittorrent.Log.getFilterText();
const filterTerms = (this.filterText.length > 0) ? this.filterText.toLowerCase().split(' ') : [];
if (filterTerms.length > 0) {
for (let i = 0; i < rows.length; ++i) {
if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(rows[i].full_data.ip, filterTerms))
continue;
filteredRows.push(rows[i]);
}
}
else {
filteredRows = rows;
}
filteredRows.sort(function(row1, row2) {
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
return (this.reverseSort == '0') ? res : -res;
}.bind(this));
return filteredRows;
}
});
return exports();
})();
Object.freeze(window.qBittorrent.DynamicTable);
/*************************************************************/