3265 lines
128 KiB
JavaScript
Raw Normal View History

2022-10-18 22:39:32 -04:00
/*
* 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";
2022-10-18 22:39:32 -04:00
window.qBittorrent ??= {};
window.qBittorrent.DynamicTable ??= (() => {
const exports = () => {
2022-10-18 22:39:32 -04:00
return {
TorrentsTable: TorrentsTable,
TorrentPeersTable: TorrentPeersTable,
SearchResultsTable: SearchResultsTable,
SearchPluginsTable: SearchPluginsTable,
TorrentTrackersTable: TorrentTrackersTable,
BulkRenameTorrentFilesTable: BulkRenameTorrentFilesTable,
2022-10-18 22:39:32 -04:00
TorrentFilesTable: TorrentFilesTable,
LogMessageTable: LogMessageTable,
LogPeerTable: LogPeerTable,
2022-10-18 22:39:32 -04:00
RssFeedTable: RssFeedTable,
RssArticleTable: RssArticleTable,
RssDownloaderRulesTable: RssDownloaderRulesTable,
RssDownloaderFeedSelectionTable: RssDownloaderFeedSelectionTable,
RssDownloaderArticlesTable: RssDownloaderArticlesTable,
TorrentWebseedsTable: TorrentWebseedsTable
2022-10-18 22:39:32 -04:00
};
};
const compareNumbers = (val1, val2) => {
if (val1 < val2)
return -1;
if (val1 > val2)
return 1;
return 0;
};
2022-10-18 22:39:32 -04:00
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 Map();
2022-10-18 22:39:32 -04:00
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");
2022-10-18 22:39:32 -04:00
this.initColumns();
this.loadColumnsOrder();
this.updateTableHeaders();
this.setupCommonEvents();
this.setupHeaderEvents();
this.setupHeaderMenu();
this.setSortedColumnIcon(this.sortedColumn, null, (this.reverseSort === "1"));
this.setupAltRow();
2022-10-18 22:39:32 -04:00
},
setupCommonEvents: function() {
const tableDiv = $(this.dynamicTableDivId);
const tableFixedHeaderDiv = $(this.dynamicTableFixedHeaderDivId);
2022-10-18 22:39:32 -04:00
const tableElement = tableFixedHeaderDiv.querySelector("table");
tableDiv.addEventListener("scroll", () => {
tableElement.style.left = `${-tableDiv.scrollLeft}px`;
});
2022-10-18 22:39:32 -04:00
// if the table exists within a panel
const parentPanel = tableDiv.getParent(".panel");
if (parentPanel) {
const resizeFn = (entries) => {
const panel = entries[0].target;
let h = panel.getBoundingClientRect().height - tableFixedHeaderDiv.getBoundingClientRect().height;
tableDiv.style.height = `${h}px`;
2022-10-18 22:39:32 -04:00
// 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)) {
2022-10-18 22:39:32 -04:00
--n;
h -= 0.5;
tableDiv.style.height = `${h}px`;
}
};
const resizeDebouncer = window.qBittorrent.Misc.createDebounceHandler(100, (entries) => {
resizeFn(entries);
});
2022-10-18 22:39:32 -04:00
const resizeObserver = new ResizeObserver(resizeDebouncer);
resizeObserver.observe(parentPanel, { box: "border-box" });
2022-10-18 22:39:32 -04:00
}
},
setupHeaderEvents: function() {
this.currentHeaderAction = "";
2022-10-18 22:39:32 -04:00
this.canResize = false;
const resetElementBorderStyle = function(el, side) {
if ((side === "left") || (side !== "right"))
el.style.borderLeft = "";
if ((side === "right") || (side !== "left"))
el.style.borderRight = "";
2022-10-18 22:39:32 -04:00
};
const mouseMoveFn = function(e) {
const brect = e.target.getBoundingClientRect();
const mouseXRelative = e.clientX - brect.left;
if (this.currentHeaderAction === "") {
if ((brect.width - mouseXRelative) < 5) {
2022-10-18 22:39:32 -04:00
this.resizeTh = e.target;
this.canResize = true;
e.target.getParent("tr").style.cursor = "col-resize";
2022-10-18 22:39:32 -04:00
}
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";
2022-10-18 22:39:32 -04:00
}
else {
this.canResize = false;
e.target.getParent("tr").style.cursor = "";
2022-10-18 22:39:32 -04:00
}
}
if (this.currentHeaderAction === "drag") {
2022-10-18 22:39:32 -04:00
const previousVisibleSibling = e.target.getPrevious('[class=""]');
let borderChangeElement = previousVisibleSibling;
let changeBorderSide = "right";
2022-10-18 22:39:32 -04:00
if (mouseXRelative > (brect.width / 2)) {
2022-10-18 22:39:32 -04:00
borderChangeElement = e.target;
this.dropSide = "right";
2022-10-18 22:39:32 -04:00
}
else {
this.dropSide = "left";
2022-10-18 22:39:32 -04:00
}
e.target.getParent("tr").style.cursor = "move";
2022-10-18 22:39:32 -04:00
if (!previousVisibleSibling) { // right most column
borderChangeElement = e.target;
if (mouseXRelative <= (brect.width / 2))
changeBorderSide = "left";
2022-10-18 22:39:32 -04:00
}
const borderStyle = "initial solid #e60";
if (changeBorderSide === "left")
borderChangeElement.style.borderLeft = borderStyle;
else
borderChangeElement.style.borderRight = borderStyle;
2022-10-18 22:39:32 -04:00
resetElementBorderStyle(borderChangeElement, ((changeBorderSide === "right") ? "left" : "right"));
2022-10-18 22:39:32 -04:00
borderChangeElement.getSiblings('[class=""]').each((el) => {
2022-10-18 22:39:32 -04:00
resetElementBorderStyle(el);
});
}
this.lastHoverTh = e.target;
this.lastClientX = e.clientX;
2022-10-18 22:39:32 -04:00
}.bind(this);
const mouseOutFn = function(e) {
resetElementBorderStyle(e.target);
}.bind(this);
const onBeforeStart = function(el) {
this.clickedTh = el;
this.currentHeaderAction = "start";
2022-10-18 22:39:32 -04:00
this.dragMovement = false;
this.dragStartX = this.lastClientX;
}.bind(this);
const onStart = function(el, event) {
if (this.canResize) {
this.currentHeaderAction = "resize";
this.startWidth = parseInt(this.resizeTh.style.width, 10);
2022-10-18 22:39:32 -04:00
}
else {
this.currentHeaderAction = "drag";
el.style.backgroundColor = "#C1D5E7";
2022-10-18 22:39:32 -04:00
}
}.bind(this);
const onDrag = function(el, event) {
if (this.currentHeaderAction === "resize") {
let width = this.startWidth + (event.event.pageX - this.dragStartX);
2022-10-18 22:39:32 -04:00
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.style.backgroundColor = "";
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)) {
2022-10-18 22:39:32 -04:00
this.saveColumnsOrder();
const val = LocalPreferences.get("columns_order_" + this.dynamicTableDivId).split(",");
2022-10-18 22:39:32 -04:00
val.erase(el.columnName);
let pos = val.indexOf(this.lastHoverTh.columnName);
if (this.dropSide === "right")
2022-10-18 22:39:32 -04:00
++pos;
val.splice(pos, 0, el.columnName);
LocalPreferences.set("columns_order_" + this.dynamicTableDivId, val.join(","));
2022-10-18 22:39:32 -04:00
this.loadColumnsOrder();
this.updateTableHeaders();
while (this.tableBody.firstChild)
this.tableBody.removeChild(this.tableBody.firstChild);
this.updateTable(true);
}
if (this.currentHeaderAction === "drag") {
2022-10-18 22:39:32 -04:00
resetElementBorderStyle(el);
el.getSiblings('[class=""]').each((el) => {
2022-10-18 22:39:32 -04:00
resetElementBorderStyle(el);
});
}
this.currentHeaderAction = "";
2022-10-18 22:39:32 -04:00
}.bind(this);
const onCancel = function(el) {
this.currentHeaderAction = "";
2022-10-18 22:39:32 -04:00
this.setSortedColumn(el.columnName);
}.bind(this);
const onTouch = function(e) {
const column = e.target.columnName;
this.currentHeaderAction = "";
2022-10-18 22:39:32 -04:00
this.setSortedColumn(column);
}.bind(this);
const ths = this.fixedTableHeader.getElements("th");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < ths.length; ++i) {
const th = ths[i];
th.addEventListener("mousemove", mouseMoveFn);
th.addEventListener("mouseout", mouseOutFn);
th.addEventListener("touchend", onTouch, { passive: true });
2022-10-18 22:39:32 -04:00
th.makeResizable({
modifiers: {
x: "",
y: ""
2022-10-18 22:39:32 -04:00
},
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 === "")
2022-10-18 22:39:32 -04:00
continue;
if (this.dynamicTable.columns[i].visible !== "0")
2022-10-18 22:39:32 -04:00
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");
2022-10-18 22:39:32 -04:00
this.updateColumn(columnName);
},
setupHeaderMenu: function() {
this.setupDynamicTableHeaderContextMenuClass();
const menuId = this.dynamicTableDivId + "_headerMenu";
2022-10-18 22:39:32 -04:00
// reuse menu if already exists
const ul = $(menuId) ?? new Element("ul", {
2022-10-18 22:39:32 -04:00
id: menuId,
class: "contextMenu scrollableMenu"
2022-10-18 22:39:32 -04:00
});
const createLi = function(columnName, text) {
const anchor = document.createElement("a");
anchor.href = `#${columnName}`;
anchor.textContent = text;
const img = document.createElement("img");
img.src = "images/checked-completed.svg";
anchor.prepend(img);
const listItem = document.createElement("li");
listItem.appendChild(anchor);
return listItem;
2022-10-18 22:39:32 -04:00
};
const actions = {};
const onMenuItemClicked = function(element, ref, action) {
this.showColumn(action, this.columns[action].visible === "0");
2022-10-18 22:39:32 -04:00
}.bind(this);
// recreate child nodes when reusing (enables the context menu to work correctly)
if (ul.hasChildNodes()) {
while (ul.firstChild)
ul.removeChild(ul.lastChild);
}
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
const text = this.columns[i].caption;
if (text === "")
2022-10-18 22:39:32 -04:00
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",
2022-10-18 22:39:32 -04:00
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) {
2022-10-18 22:39:32 -04:00
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
2022-10-18 22:39:32 -04:00
};
column["compareRows"] = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
column["updateTd"] = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
column["onResize"] = null;
2022-10-18 22:39:32 -04:00
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.appendChild(new Element("th"));
this.fixedTableHeader.appendChild(new Element("th"));
2022-10-18 22:39:32 -04:00
},
loadColumnsOrder: function() {
const columnsOrder = [];
const val = LocalPreferences.get("columns_order_" + this.dynamicTableDivId);
if ((val === null) || (val === undefined))
2022-10-18 22:39:32 -04:00
return;
val.split(",").forEach((v) => {
2022-10-18 22:39:32 -04:00
if ((v in this.columns) && (!columnsOrder.contains(v)))
columnsOrder.push(v);
});
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
2022-10-18 22:39:32 -04:00
if (!columnsOrder.contains(this.columns[i].name))
columnsOrder.push(this.columns[i].name);
}
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i)
this.columns[i] = this.columns[columnsOrder[i]];
},
saveColumnsOrder: function() {
let val = "";
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
if (i > 0)
val += ",";
2022-10-18 22:39:32 -04:00
val += this.columns[i].name;
}
LocalPreferences.set("columns_order_" + this.dynamicTableDivId, val);
2022-10-18 22:39:32 -04:00
},
updateTableHeaders: function() {
this.updateHeader(this.hiddenTableHeader);
this.updateHeader(this.fixedTableHeader);
},
updateHeader: function(header) {
const ths = header.getElements("th");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < ths.length; ++i) {
const th = ths[i];
th._this = this;
th.title = this.columns[i].caption;
th.textContent = this.columns[i].caption;
th.setAttribute("style", "width: " + this.columns[i].width + "px;" + this.columns[i].style);
2022-10-18 22:39:32 -04:00
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");
2022-10-18 22:39:32 -04:00
else
th.removeClass("invisible");
2022-10-18 22:39:32 -04:00
}
},
getColumnPos: function(columnName) {
for (let i = 0; i < this.columns.length; ++i) {
if (this.columns[i].name === columnName)
2022-10-18 22:39:32 -04:00
return i;
}
2022-10-18 22:39:32 -04:00
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;
2022-10-18 22:39:32 -04:00
ths[pos].setAttribute("style", style);
fths[pos].setAttribute("style", style);
2022-10-18 22:39:32 -04:00
if (visible) {
ths[pos].removeClass("invisible");
fths[pos].removeClass("invisible");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < trs.length; ++i)
trs[i].getElements("td")[pos].removeClass("invisible");
2022-10-18 22:39:32 -04:00
}
else {
ths[pos].addClass("invisible");
fths[pos].addClass("invisible");
2022-10-18 22:39:32 -04:00
for (let j = 0; j < trs.length; ++j)
trs[j].getElements("td")[pos].addClass("invisible");
2022-10-18 22:39:32 -04:00
}
if (this.columns[pos].onResize !== null)
2022-10-18 22:39:32 -04:00
this.columns[pos].onResize(columnName);
},
getSortedColumn: function() {
return LocalPreferences.get("sorted_column_" + this.dynamicTableDivId);
2022-10-18 22:39:32 -04:00
},
/**
* @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) {
2022-10-18 22:39:32 -04:00
const oldColumn = this.sortedColumn;
this.sortedColumn = column;
this.reverseSort = reverse ?? "0";
2022-10-18 22:39:32 -04:00
this.setSortedColumnIcon(column, oldColumn, false);
}
else {
// Toggle sort order
this.reverseSort = reverse ?? (this.reverseSort === "0" ? "1" : "0");
this.setSortedColumnIcon(column, null, (this.reverseSort === "1"));
2022-10-18 22:39:32 -04:00
}
LocalPreferences.set("sorted_column_" + this.dynamicTableDivId, column);
LocalPreferences.set("reverse_sort_" + this.dynamicTableDivId, this.reverseSort);
2022-10-18 22:39:32 -04:00
this.updateTable(false);
},
setSortedColumnIcon: function(newColumn, oldColumn, isReverse) {
const getCol = function(headerDivId, colName) {
const colElem = $$("#" + headerDivId + " .column_" + colName);
if (colElem.length === 1)
2022-10-18 22:39:32 -04:00
return colElem[0];
return null;
};
const colElem = getCol(this.dynamicTableFixedHeaderDivId, newColumn);
if (colElem !== null) {
colElem.addClass("sorted");
2022-10-18 22:39:32 -04:00
if (isReverse)
colElem.addClass("reverse");
2022-10-18 22:39:32 -04:00
else
colElem.removeClass("reverse");
2022-10-18 22:39:32 -04:00
}
const oldColElem = getCol(this.dynamicTableFixedHeaderDivId, oldColumn);
if (oldColElem !== null) {
oldColElem.removeClass("sorted");
oldColElem.removeClass("reverse");
2022-10-18 22:39:32 -04:00
}
},
getSelectedRowId: function() {
if (this.selectedRows.length > 0)
return this.selectedRows[0];
return "";
2022-10-18 22:39:32 -04:00
},
isRowSelected: function(rowId) {
return this.selectedRows.contains(rowId);
},
setupAltRow: function() {
const useAltRowColors = (LocalPreferences.get("use_alt_row_colors", "true") === "true");
if (useAltRowColors)
document.getElementById(this.dynamicTableDivId).classList.add("altRowColors");
2022-10-18 22:39:32 -04:00
},
selectAll: function() {
this.deselectAll();
const trs = this.tableBody.getElements("tr");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < trs.length; ++i) {
const tr = trs[i];
this.selectedRows.push(tr.rowId);
if (!tr.hasClass("selected"))
tr.addClass("selected");
2022-10-18 22:39:32 -04:00
}
},
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((tr) => {
if ((tr.rowId === rowId1) || (tr.rowId === rowId2)) {
2022-10-18 22:39:32 -04:00
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((tr) => {
if (rowIds.includes(tr.rowId))
tr.addClass("selected");
2022-10-18 22:39:32 -04:00
});
},
setRowClass: function() {
const that = this;
this.tableBody.getElements("tr").each((tr) => {
2022-10-18 22:39:32 -04:00
if (that.isRowSelected(tr.rowId))
tr.addClass("selected");
2022-10-18 22:39:32 -04:00
else
tr.removeClass("selected");
2022-10-18 22:39:32 -04:00
});
},
onSelectedRowChanged: function() {},
updateRowData: function(data) {
// ensure rowId is a string
const rowId = `${data["rowId"]}`;
2022-10-18 22:39:32 -04:00
let row;
if (!this.rows.has(rowId)) {
row = {
"full_data": {},
"rowId": rowId
2022-10-18 22:39:32 -04:00
};
this.rows.set(rowId, row);
}
else {
2022-10-18 22:39:32 -04:00
row = this.rows.get(rowId);
}
2022-10-18 22:39:32 -04:00
row["data"] = data;
for (const x in data) {
if (!Object.hasOwn(data, x))
continue;
row["full_data"][x] = data[x];
}
},
getRow: function(rowId) {
return this.rows.get(rowId);
2022-10-18 22:39:32 -04:00
},
getFilteredAndSortedRows: function() {
const filteredRows = [];
for (const row of this.getRowValues()) {
filteredRows.push(row);
filteredRows[row.rowId] = row;
2022-10-18 22:39:32 -04:00
}
filteredRows.sort((row1, row2) => {
2022-10-18 22:39:32 -04:00
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
if (this.reverseSort === "0")
2022-10-18 22:39:32 -04:00
return res;
else
return -res;
});
2022-10-18 22:39:32 -04:00
return filteredRows;
},
getTrByRowId: function(rowId) {
const trs = this.tableBody.getElements("tr");
for (let i = 0; i < trs.length; ++i) {
if (trs[i].rowId === rowId)
2022-10-18 22:39:32 -04:00
return trs[i];
}
2022-10-18 22:39:32 -04:00
return null;
},
updateTable: function(fullUpdate = false) {
2022-10-18 22:39:32 -04:00
const rows = this.getFilteredAndSortedRows();
for (let i = 0; i < this.selectedRows.length; ++i) {
2022-10-18 22:39:32 -04:00
if (!(this.selectedRows[i] in rows)) {
this.selectedRows.splice(i, 1);
--i;
}
}
2022-10-18 22:39:32 -04:00
const trs = this.tableBody.getElements("tr");
2022-10-18 22:39:32 -04:00
for (let rowPos = 0; rowPos < rows.length; ++rowPos) {
const rowId = rows[rowPos]["rowId"];
2022-10-18 22:39:32 -04:00
let tr_found = false;
for (let j = rowPos; j < trs.length; ++j) {
if (trs[j]["rowId"] === rowId) {
2022-10-18 22:39:32 -04:00
tr_found = true;
if (rowPos === j)
2022-10-18 22:39:32 -04:00
break;
trs[j].inject(trs[rowPos], "before");
2022-10-18 22:39:32 -04:00
const tmpTr = trs[j];
trs.splice(j, 1);
trs.splice(rowPos, 0, tmpTr);
break;
}
}
if (tr_found) { // row already exists in the table
2022-10-18 22:39:32 -04:00
this.updateRow(trs[rowPos], fullUpdate);
}
2022-10-18 22:39:32 -04:00
else { // else create a new row in the table
const tr = new Element("tr");
2022-10-18 22:39:32 -04:00
// set tabindex so element receives keydown events
// more info: https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
tr.tabindex = "-1";
2022-10-18 22:39:32 -04:00
const rowId = rows[rowPos]["rowId"];
tr.setAttribute("data-row-id", rowId);
tr["rowId"] = rowId;
2022-10-18 22:39:32 -04:00
tr._this = this;
tr.addEventListener("contextmenu", function(e) {
2022-10-18 22:39:32 -04:00
if (!this._this.isRowSelected(this.rowId)) {
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
return true;
});
tr.addEventListener("click", function(e) {
e.preventDefault();
e.stopPropagation();
if (e.ctrlKey || e.metaKey) {
2022-10-18 22:39:32 -04:00
// 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.shiftKey && (this._this.selectedRows.length === 1)) {
2022-10-18 22:39:32 -04:00
// 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.addEventListener("touchstart", function(e) {
2022-10-18 22:39:32 -04:00
if (!this._this.isRowSelected(this.rowId)) {
this._this.deselectAll();
this._this.selectRow(this.rowId);
}
}, { passive: true });
tr.addEventListener("keydown", function(event) {
2022-10-18 22:39:32 -04:00
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");
2022-10-18 22:39:32 -04:00
td.injectInside(tr);
}
// Insert
if (rowPos >= trs.length) {
tr.inject(this.tableBody);
trs.push(tr);
}
else {
tr.inject(trs[rowPos], "before");
2022-10-18 22:39:32 -04:00
trs.splice(rowPos, 0, tr);
}
// Update context menu
if (this.contextMenu)
this.contextMenu.addTarget(tr);
this.updateRow(tr, true);
}
}
const rowPos = rows.length;
2022-10-18 22:39:32 -04:00
while ((rowPos < trs.length) && (trs.length > 0))
2024-01-07 17:04:03 -05:00
trs.pop().destroy();
2022-10-18 22:39:32 -04:00
},
setupTr: function(tr) {},
updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? "full_data" : "data"];
2022-10-18 22:39:32 -04:00
const tds = tr.getElements("td");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0]))
2022-10-18 22:39:32 -04:00
this.columns[i].updateTd(tds[i], row);
}
row["data"] = {};
2022-10-18 22:39:32 -04:00
},
removeRow: function(rowId) {
this.selectedRows.erase(rowId);
this.rows.delete(rowId);
2022-10-18 22:39:32 -04:00
const tr = this.getTrByRowId(rowId);
tr?.destroy();
2022-10-18 22:39:32 -04:00
},
clear: function() {
this.deselectAll();
this.rows.clear();
const trs = this.tableBody.getElements("tr");
while (trs.length > 0)
2024-01-07 17:04:03 -05:00
trs.pop().destroy();
2022-10-18 22:39:32 -04:00
},
selectedRowsIds: function() {
return this.selectedRows.slice();
},
getRowIds: function() {
return this.rows.keys();
},
getRowValues: function() {
return this.rows.values();
},
getRowItems: function() {
return this.rows.entries();
},
getRowSize: function() {
return this.rows.size;
2022-10-18 22:39:32 -04:00
},
selectNextRow: function() {
const visibleRows = $(this.dynamicTableDivId).getElements("tbody tr").filter(e => e.style.display !== "none");
2022-10-18 22:39:32 -04:00
const selectedRowId = this.getSelectedRowId();
let selectedIndex = -1;
for (let i = 0; i < visibleRows.length; ++i) {
const row = visibleRows[i];
if (row.getAttribute("data-row-id") === selectedRowId) {
2022-10-18 22:39:32 -04:00
selectedIndex = i;
break;
}
}
const isLastRowSelected = (selectedIndex >= (visibleRows.length - 1));
if (!isLastRowSelected) {
this.deselectAll();
const newRow = visibleRows[selectedIndex + 1];
this.selectRow(newRow.getAttribute("data-row-id"));
2022-10-18 22:39:32 -04:00
}
},
selectPreviousRow: function() {
const visibleRows = $(this.dynamicTableDivId).getElements("tbody tr").filter(e => e.style.display !== "none");
2022-10-18 22:39:32 -04:00
const selectedRowId = this.getSelectedRowId();
let selectedIndex = -1;
for (let i = 0; i < visibleRows.length; ++i) {
const row = visibleRows[i];
if (row.getAttribute("data-row-id") === selectedRowId) {
2022-10-18 22:39:32 -04:00
selectedIndex = i;
break;
}
}
const isFirstRowSelected = selectedIndex <= 0;
if (!isFirstRowSelected) {
this.deselectAll();
const newRow = visibleRows[selectedIndex - 1];
this.selectRow(newRow.getAttribute("data-row-id"));
2022-10-18 22:39:32 -04:00
}
},
});
const TorrentsTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("priority", "", "#", 30, true);
this.newColumn("state_icon", "cursor: default", "", 22, true);
this.newColumn("name", "", "Name", 200, true);
this.newColumn("size", "", "Size", 100, true);
this.newColumn("total_size", "", "Total Size", 100, false);
this.newColumn("progress", "", "Progress", 85, true);
this.newColumn("status", "", "Status", 100, true);
this.newColumn("num_seeds", "", "Seeds", 100, true);
this.newColumn("num_leechs", "", "Peers", 100, true);
this.newColumn("dlspeed", "", "Down Speed", 100, true);
this.newColumn("upspeed", "", "Up Speed", 100, true);
this.newColumn("eta", "", "ETA", 100, true);
this.newColumn("ratio", "", "Ratio", 100, true);
this.newColumn("popularity", "", "Popularity", 100, true);
this.newColumn("category", "", "Category", 100, true);
this.newColumn("tags", "", "Tags", 100, true);
this.newColumn("added_on", "", "Added On", 100, true);
this.newColumn("completion_on", "", "Completed On", 100, false);
this.newColumn("tracker", "", "Tracker", 100, false);
this.newColumn("dl_limit", "", "Down Limit", 100, false);
this.newColumn("up_limit", "", "Up Limit", 100, false);
this.newColumn("downloaded", "", "Downloaded", 100, false);
this.newColumn("uploaded", "", "Uploaded", 100, false);
this.newColumn("downloaded_session", "", "Session Download", 100, false);
this.newColumn("uploaded_session", "", "Session Upload", 100, false);
this.newColumn("amount_left", "", "Remaining", 100, false);
this.newColumn("time_active", "", "Time Active", 100, false);
this.newColumn("save_path", "", "Save path", 100, false);
this.newColumn("completed", "", "Completed", 100, false);
this.newColumn("max_ratio", "", "Ratio Limit", 100, false);
this.newColumn("seen_complete", "", "Last Seen Complete", 100, false);
this.newColumn("last_activity", "", "Last Activity", 100, false);
this.newColumn("availability", "", "Availability", 100, false);
this.newColumn("download_path", "", "Incomplete Save Path", 100, false);
this.newColumn("infohash_v1", "", "Info Hash v1", 100, false);
this.newColumn("infohash_v2", "", "Info Hash v2", 100, false);
this.newColumn("reannounce", "", "Reannounce In", 100, false);
this.newColumn("private", "", "Private", 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");
2022-10-18 22:39:32 -04:00
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
// state_icon
this.columns["state_icon"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
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":
2022-10-18 22:39:32 -04:00
state = "torrent-stop";
img_path = "images/stopped.svg";
2022-10-18 22:39:32 -04:00
break;
case "stoppedUP":
2022-10-18 22:39:32 -04:00
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;
2024-01-07 17:04:03 -05:00
case "moving":
state = "moving";
img_path = "images/set-location.svg";
break;
case "error":
2022-10-18 22:39:32 -04:00
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.includes(img_path)) {
img.src = img_path;
img.title = state;
2022-10-18 22:39:32 -04:00
}
}
else {
td.adopt(new Element("img", {
"src": img_path,
"class": "stateIcon",
"title": state
2022-10-18 22:39:32 -04:00
}));
}
};
// status
this.columns["status"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const state = this.getRowValue(row);
if (!state)
return;
let status;
switch (state) {
case "downloading":
status = "Downloading";
2022-10-18 22:39:32 -04:00
break;
case "stalledDL":
status = "Stalled";
2022-10-18 22:39:32 -04:00
break;
case "metaDL":
status = "Downloading metadata";
2022-10-18 22:39:32 -04:00
break;
case "forcedMetaDL":
status = "[F] Downloading metadata";
2022-10-18 22:39:32 -04:00
break;
case "forcedDL":
status = "[F] Downloading";
2022-10-18 22:39:32 -04:00
break;
case "uploading":
case "stalledUP":
status = "Seeding";
2022-10-18 22:39:32 -04:00
break;
case "forcedUP":
status = "[F] Seeding";
2022-10-18 22:39:32 -04:00
break;
case "queuedDL":
case "queuedUP":
status = "Queued";
2022-10-18 22:39:32 -04:00
break;
case "checkingDL":
case "checkingUP":
status = "Checking";
2022-10-18 22:39:32 -04:00
break;
case "queuedForChecking":
status = "Queued for checking";
2022-10-18 22:39:32 -04:00
break;
case "checkingResumeData":
status = "Checking resume data";
2022-10-18 22:39:32 -04:00
break;
case "stoppedDL":
status = "Stopped";
2022-10-18 22:39:32 -04:00
break;
case "stoppedUP":
status = "Completed";
2022-10-18 22:39:32 -04:00
break;
case "moving":
status = "Moving";
2022-10-18 22:39:32 -04:00
break;
case "missingFiles":
status = "Missing Files";
2022-10-18 22:39:32 -04:00
break;
case "error":
status = "Errored";
2022-10-18 22:39:32 -04:00
break;
default:
status = "Unknown";
2022-10-18 22:39:32 -04:00
}
td.textContent = status;
td.title = status;
2022-10-18 22:39:32 -04:00
};
// priority
this.columns["priority"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const queuePos = this.getRowValue(row);
const formattedQueuePos = (queuePos < 1) ? "*" : queuePos;
td.textContent = formattedQueuePos;
td.title = formattedQueuePos;
2022-10-18 22:39:32 -04:00
};
this.columns["priority"].compareRows = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
// name, category, tags
this.columns["name"].compareRows = function(row1, row2) {
2022-10-18 22:39:32 -04:00
const row1Val = this.getRowValue(row1);
const row2Val = this.getRowValue(row2);
return row1Val.localeCompare(row2Val, undefined, { numeric: true, sensitivity: "base" });
2022-10-18 22:39:32 -04:00
};
this.columns["category"].compareRows = this.columns["name"].compareRows;
this.columns["tags"].compareRows = this.columns["name"].compareRows;
2022-10-18 22:39:32 -04:00
// size, total_size
this.columns["size"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false);
td.textContent = size;
td.title = size;
2022-10-18 22:39:32 -04:00
};
this.columns["total_size"].updateTd = this.columns["size"].updateTd;
2022-10-18 22:39:32 -04:00
// progress
this.columns["progress"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const progress = this.getRowValue(row);
let progressFormatted = (progress * 100).round(1);
if ((progressFormatted === 100.0) && (progress !== 1.0))
progressFormatted = 99.9;
2022-10-18 22:39:32 -04:00
if (td.getChildren("div").length > 0) {
const div = td.getChildren("div")[0];
2022-10-18 22:39:32 -04:00
if (td.resized) {
td.resized = false;
div.setWidth(ProgressColumnWidth - 5);
}
if (div.getValue() !== progressFormatted)
div.setValue(progressFormatted);
2022-10-18 22:39:32 -04:00
}
else {
if (ProgressColumnWidth < 0)
ProgressColumnWidth = td.offsetWidth;
td.adopt(new window.qBittorrent.ProgressBar.ProgressBar(progressFormatted.toFloat(), {
"width": ProgressColumnWidth - 5
2022-10-18 22:39:32 -04:00
}));
td.resized = false;
}
};
this.columns["progress"].onResize = function(columnName) {
2022-10-18 22:39:32 -04:00
const pos = this.getColumnPos(columnName);
const trs = this.tableBody.getElements("tr");
2022-10-18 22:39:32 -04:00
ProgressColumnWidth = -1;
for (let i = 0; i < trs.length; ++i) {
const td = trs[i].getElements("td")[pos];
2022-10-18 22:39:32 -04:00
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) {
2022-10-18 22:39:32 -04:00
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.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
this.columns["num_seeds"].compareRows = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
// num_leechs
this.columns["num_leechs"].updateTd = this.columns["num_seeds"].updateTd;
this.columns["num_leechs"].compareRows = this.columns["num_seeds"].compareRows;
2022-10-18 22:39:32 -04:00
// dlspeed
this.columns["dlspeed"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const speed = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), true);
td.textContent = speed;
td.title = speed;
2022-10-18 22:39:32 -04:00
};
// upspeed
this.columns["upspeed"].updateTd = this.columns["dlspeed"].updateTd;
2022-10-18 22:39:32 -04:00
// eta
this.columns["eta"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const eta = window.qBittorrent.Misc.friendlyDuration(this.getRowValue(row), window.qBittorrent.Misc.MAX_ETA);
td.textContent = eta;
td.title = eta;
2022-10-18 22:39:32 -04:00
};
// ratio
this.columns["ratio"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const ratio = this.getRowValue(row);
const string = (ratio === -1) ? "∞" : window.qBittorrent.Misc.toFixedPointString(ratio, 2);
td.textContent = string;
td.title = string;
// ratio colors by percentage
td.classList.add('ratio');
if (string === 0 || string === "0.00" || string <= "0.09") {
td.classList.add('highlight-darkred');
} else if (string >= "0.10" && string <= "0.19") {
td.classList.add('highlight-red');
} else if (string >= "0.20" && string <= "0.39") {
td.classList.add('highlight-orange');
} else if (string >= "0.40" && string <= "0.59") {
td.classList.add('highlight-yellow');
} else if (string >= "0.60" && string <= "0.89") {
td.classList.add('highlight-green');
} else if (string >= "0.90" && string <= "0.99") {
td.classList.add('highlight-brightgreen');
} else if (string >= "1.0" && string <= "1.5") {
td.classList.add('highlight-blue');
} else if (string >= "1.5") {
td.classList.add('highlight-purple');
}
};
// popularity
this.columns["popularity"].updateTd = function(td, row) {
const value = this.getRowValue(row);
const popularity = (value === -1) ? "∞" : window.qBittorrent.Misc.toFixedPointString(value, 2);
td.textContent = popularity;
td.title = popularity;
2022-10-18 22:39:32 -04:00
};
// added on
this.columns["added_on"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
td.textContent = date;
td.title = date;
2022-10-18 22:39:32 -04:00
};
// completion_on
this.columns["completion_on"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const val = this.getRowValue(row);
if ((val === 0xffffffff) || (val < 0)) {
td.textContent = "";
td.title = "";
2022-10-18 22:39:32 -04:00
}
else {
const date = new Date(this.getRowValue(row) * 1000).toLocaleString();
td.textContent = date;
td.title = date;
2022-10-18 22:39:32 -04:00
}
};
// tracker
this.columns["tracker"].updateTd = function(td, row) {
const value = this.getRowValue(row);
const tracker = displayFullURLTrackerColumn ? value : window.qBittorrent.Misc.getHost(value);
td.textContent = tracker;
td.title = value;
};
2022-10-18 22:39:32 -04:00
// dl_limit, up_limit
this.columns["dl_limit"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const speed = this.getRowValue(row);
if (speed === 0) {
td.textContent = "∞";
td.title = "∞";
2022-10-18 22:39:32 -04:00
}
else {
const formattedSpeed = window.qBittorrent.Misc.friendlyUnit(speed, true);
td.textContent = formattedSpeed;
td.title = formattedSpeed;
2022-10-18 22:39:32 -04:00
}
};
this.columns["up_limit"].updateTd = this.columns["dl_limit"].updateTd;
2022-10-18 22:39:32 -04:00
// 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;
2022-10-18 22:39:32 -04:00
// time active
this.columns["time_active"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const activeTime = this.getRowValue(row, 0);
const seedingTime = this.getRowValue(row, 1);
const time = (seedingTime > 0)
? ("%1 (seeded for %2)"
.replace("%1", window.qBittorrent.Misc.friendlyDuration(activeTime))
.replace("%2", window.qBittorrent.Misc.friendlyDuration(seedingTime)))
2022-10-18 22:39:32 -04:00
: window.qBittorrent.Misc.friendlyDuration(activeTime);
td.textContent = time;
td.title = time;
2022-10-18 22:39:32 -04:00
};
// completed
this.columns["completed"].updateTd = this.columns["size"].updateTd;
2022-10-18 22:39:32 -04:00
// max_ratio
this.columns["max_ratio"].updateTd = this.columns["ratio"].updateTd;
2022-10-18 22:39:32 -04:00
// seen_complete
this.columns["seen_complete"].updateTd = this.columns["completion_on"].updateTd;
2022-10-18 22:39:32 -04:00
// last_activity
this.columns["last_activity"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const val = this.getRowValue(row);
if (val < 1) {
td.textContent = "∞";
td.title = "∞";
2022-10-18 22:39:32 -04:00
}
else {
const formattedVal = "%1 ago".replace("%1", window.qBittorrent.Misc.friendlyDuration((new Date() / 1000) - val));
td.textContent = formattedVal;
td.title = formattedVal;
2022-10-18 22:39:32 -04:00
}
};
// availability
this.columns["availability"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = window.qBittorrent.Misc.toFixedPointString(this.getRowValue(row), 3);
td.textContent = value;
td.title = value;
};
// infohash_v1
this.columns["infohash_v1"].updateTd = function(td, row) {
const sourceInfohashV1 = this.getRowValue(row);
const infohashV1 = (sourceInfohashV1 !== "") ? sourceInfohashV1 : "N/A";
td.textContent = infohashV1;
td.title = infohashV1;
};
// infohash_v2
this.columns["infohash_v2"].updateTd = function(td, row) {
const sourceInfohashV2 = this.getRowValue(row);
const infohashV2 = (sourceInfohashV2 !== "") ? sourceInfohashV2 : "N/A";
td.textContent = infohashV2;
td.title = infohashV2;
2022-10-18 22:39:32 -04:00
};
2024-01-07 17:04:03 -05:00
// reannounce
this.columns["reannounce"].updateTd = function(td, row) {
2024-01-07 17:04:03 -05:00
const time = window.qBittorrent.Misc.friendlyDuration(this.getRowValue(row));
td.textContent = time;
td.title = time;
};
// private
this.columns["private"].updateTd = function(td, row) {
const hasMetadata = row["full_data"].has_metadata;
const isPrivate = this.getRowValue(row);
const string = hasMetadata
? (isPrivate
? "Yes"
: "No")
: "N/A";
td.textContent = string;
td.title = string;
2024-01-07 17:04:03 -05:00
};
2022-10-18 22:39:32 -04:00
},
applyFilter: function(row, filterName, categoryHash, tagHash, trackerHash, filterTerms) {
const state = row["full_data"].state;
2022-10-18 22:39:32 -04:00
let inactive = false;
switch (filterName) {
case "downloading":
if ((state !== "downloading") && !state.includes("DL"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "seeding":
if ((state !== "uploading") && (state !== "forcedUP") && (state !== "stalledUP") && (state !== "queuedUP") && (state !== "checkingUP"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "completed":
if ((state !== "uploading") && !state.includes("UP"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "stopped":
if (!state.includes("stopped"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "running":
if (state.includes("stopped"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "stalled":
if ((state !== "stalledUP") && (state !== "stalledDL"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "stalled_uploading":
if (state !== "stalledUP")
2022-10-18 22:39:32 -04:00
return false;
break;
case "stalled_downloading":
if (state !== "stalledDL")
2022-10-18 22:39:32 -04:00
return false;
break;
case "inactive":
2022-10-18 22:39:32 -04:00
inactive = true;
// fallthrough
case "active": {
let r;
if (state === "stalledDL")
r = (row["full_data"].upspeed > 0);
2022-10-18 22:39:32 -04:00
else
r = (state === "metaDL") || (state === "forcedMetaDL") || (state === "downloading") || (state === "forcedDL") || (state === "uploading") || (state === "forcedUP");
if (r === inactive)
2022-10-18 22:39:32 -04:00
return false;
break;
}
case "checking":
if ((state !== "checkingUP") && (state !== "checkingDL") && (state !== "checkingResumeData"))
2022-10-18 22:39:32 -04:00
return false;
break;
case "moving":
if (state !== "moving")
2024-01-07 17:04:03 -05:00
return false;
break;
case "errored":
if ((state !== "error") && (state !== "unknown") && (state !== "missingFiles"))
2022-10-18 22:39:32 -04:00
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.Misc.genHash(row["full_data"].category))
2022-10-18 22:39:32 -04:00
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;
2022-10-18 22:39:32 -04:00
}
switch (tagHash) {
case TAGS_ALL:
break; // do nothing
2022-10-18 22:39:32 -04:00
case TAGS_UNTAGGED:
if (row["full_data"].tags.length !== 0)
return false;
break; // do nothing
2022-10-18 22:39:32 -04:00
default: {
const tagHashes = row["full_data"].tags.split(", ").map(tag => window.qBittorrent.Misc.genHash(tag));
if (!tagHashes.contains(tagHash))
return false;
break;
2022-10-18 22:39:32 -04:00
}
}
switch (trackerHash) {
2022-10-18 22:39:32 -04:00
case TRACKERS_ALL:
break; // do nothing
case TRACKERS_TRACKERLESS:
if (row["full_data"].trackers_count !== 0)
2022-10-18 22:39:32 -04:00
return false;
break;
default: {
const tracker = trackerList.get(trackerHash);
if (tracker) {
let found = false;
for (const torrents of tracker.trackerTorrentMap.values()) {
if (torrents.has(row["full_data"].rowId)) {
found = true;
break;
}
}
if (!found)
return false;
}
2022-10-18 22:39:32 -04:00
break;
}
}
if ((filterTerms !== undefined) && (filterTerms !== null)) {
const filterBy = document.getElementById("torrentsFilterSelect").value;
const textToSearch = row["full_data"][filterBy].toLowerCase();
if (filterTerms instanceof RegExp) {
if (!filterTerms.test(textToSearch))
return false;
}
else {
if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(textToSearch, filterTerms))
return false;
}
}
2022-10-18 22:39:32 -04:00
return true;
},
getFilteredTorrentsNumber: function(filterName, categoryHash, tagHash, trackerHash) {
let cnt = 0;
for (const row of this.rows.values()) {
if (this.applyFilter(row, filterName, categoryHash, tagHash, trackerHash, null))
2022-10-18 22:39:32 -04:00
++cnt;
}
2022-10-18 22:39:32 -04:00
return cnt;
},
getFilteredTorrentsHashes: function(filterName, categoryHash, tagHash, trackerHash) {
const rowsHashes = [];
for (const row of this.rows.values()) {
if (this.applyFilter(row, filterName, categoryHash, tagHash, trackerHash, null))
rowsHashes.push(row["rowId"]);
}
2022-10-18 22:39:32 -04:00
return rowsHashes;
},
getFilteredAndSortedRows: function() {
const filteredRows = [];
const useRegex = $("torrentsFilterRegexBox").checked;
const filterText = $("torrentsFilterInput").value.trim().toLowerCase();
let filterTerms;
try {
filterTerms = (filterText.length > 0)
? (useRegex ? new RegExp(filterText) : filterText.split(" "))
: null;
}
catch (e) { // SyntaxError: Invalid regex pattern
return filteredRows;
}
2022-10-18 22:39:32 -04:00
for (const row of this.rows.values()) {
if (this.applyFilter(row, selectedStatus, selectedCategory, selectedTag, selectedTracker, filterTerms)) {
filteredRows.push(row);
filteredRows[row.rowId] = row;
2022-10-18 22:39:32 -04:00
}
}
filteredRows.sort((row1, row2) => {
2022-10-18 22:39:32 -04:00
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
if (this.reverseSort === "0")
2022-10-18 22:39:32 -04:00
return res;
else
return -res;
});
2022-10-18 22:39:32 -04:00
return filteredRows;
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
e.preventDefault();
e.stopPropagation();
2022-10-18 22:39:32 -04:00
this._this.deselectAll();
this._this.selectRow(this.rowId);
const row = this._this.rows.get(this.rowId);
const state = row["full_data"].state;
const prefKey =
(state !== "uploading")
&& (state !== "stoppedUP")
&& (state !== "forcedUP")
&& (state !== "stalledUP")
&& (state !== "queuedUP")
&& (state !== "checkingUP")
? "dblclick_download"
: "dblclick_complete";
if (LocalPreferences.get(prefKey, "1") !== "1")
return true;
if (state.includes("stopped"))
2022-10-18 22:39:32 -04:00
startFN();
else
stopFN();
2022-10-18 22:39:32 -04:00
return true;
});
tr.addClass("torrentsTableContextMenuTarget");
},
getCurrentTorrentID: function() {
return this.getSelectedRowId();
},
onSelectedRowChanged: function() {
updatePropertiesPanel();
}
});
const TorrentPeersTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("country", "", "Country/Region", 22, true);
this.newColumn("ip", "", "IP", 80, true);
this.newColumn("port", "", "Port", 35, true);
this.newColumn("connection", "", "Connection", 50, true);
this.newColumn("flags", "", "Flags", 50, true);
this.newColumn("client", "", "Client", 140, true);
this.newColumn("peer_id_client", "", "Peer ID Client", 60, false);
this.newColumn("progress", "", "Progress", 50, true);
this.newColumn("dl_speed", "", "Down Speed", 50, true);
this.newColumn("up_speed", "", "Up Speed", 50, true);
this.newColumn("downloaded", "", "Downloaded", 50, true);
this.newColumn("uploaded", "", "Uploaded", 50, true);
this.newColumn("relevance", "", "Relevance", 30, true);
this.newColumn("files", "", "Files", 100, true);
this.columns["country"].dataProperties.push("country_code");
this.columns["flags"].dataProperties.push("flags_desc");
2022-10-18 22:39:32 -04:00
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
// country
this.columns["country"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const country = this.getRowValue(row, 0);
const country_code = this.getRowValue(row, 1);
let span = td.firstElementChild;
if (span === null) {
span = document.createElement("span");
span.classList.add("flags");
td.append(span);
2022-10-18 22:39:32 -04:00
}
span.style.backgroundImage = `url('images/flags/${country_code ?? "xx"}.svg')`;
span.textContent = country;
td.title = country;
2022-10-18 22:39:32 -04:00
};
// ip
this.columns["ip"].compareRows = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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])
2022-10-18 22:39:32 -04:00
return a[i] - b[i];
}
return 0;
};
// flags
this.columns["flags"].updateTd = function(td, row) {
td.textContent = this.getRowValue(row, 0);
td.title = this.getRowValue(row, 1);
2022-10-18 22:39:32 -04:00
};
// progress
this.columns["progress"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const progress = this.getRowValue(row);
let progressFormatted = (progress * 100).round(1);
if ((progressFormatted === 100.0) && (progress !== 1.0))
progressFormatted = 99.9;
progressFormatted += "%";
td.textContent = progressFormatted;
td.title = progressFormatted;
2022-10-18 22:39:32 -04:00
};
// dl_speed, up_speed
this.columns["dl_speed"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const speed = this.getRowValue(row);
if (speed === 0) {
td.textContent = "";
td.title = "";
2022-10-18 22:39:32 -04:00
}
else {
const formattedSpeed = window.qBittorrent.Misc.friendlyUnit(speed, true);
td.textContent = formattedSpeed;
td.title = formattedSpeed;
2022-10-18 22:39:32 -04:00
}
};
this.columns["up_speed"].updateTd = this.columns["dl_speed"].updateTd;
2022-10-18 22:39:32 -04:00
// downloaded, uploaded
this.columns["downloaded"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const downloaded = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false);
td.textContent = downloaded;
td.title = downloaded;
2022-10-18 22:39:32 -04:00
};
this.columns["uploaded"].updateTd = this.columns["downloaded"].updateTd;
2022-10-18 22:39:32 -04:00
// relevance
this.columns["relevance"].updateTd = this.columns["progress"].updateTd;
2022-10-18 22:39:32 -04:00
// files
this.columns["files"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row, 0);
td.textContent = value.replace(/\n/g, ";");
td.title = value;
2022-10-18 22:39:32 -04:00
};
}
});
const SearchResultsTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("fileName", "", "Name", 500, true);
this.newColumn("fileSize", "", "Size", 100, true);
this.newColumn("nbSeeders", "", "Seeders", 100, true);
this.newColumn("nbLeechers", "", "Leechers", 100, true);
this.newColumn("engineName", "", "Engine", 100, true);
this.newColumn("siteUrl", "", "Engine URL", 250, true);
this.newColumn("pubDate", "", "Published On", 200, true);
2022-10-18 22:39:32 -04:00
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
const displaySize = function(td, row) {
const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false);
td.textContent = size;
td.title = size;
2022-10-18 22:39:32 -04:00
};
const displayNum = function(td, row) {
const value = this.getRowValue(row);
const formattedValue = (value === "-1") ? "Unknown" : value;
td.textContent = formattedValue;
td.title = formattedValue;
};
const displayDate = function(td, row) {
const value = this.getRowValue(row) * 1000;
const formattedValue = (isNaN(value) || (value <= 0)) ? "" : (new Date(value).toLocaleString());
td.textContent = formattedValue;
td.title = formattedValue;
2022-10-18 22:39:32 -04:00
};
this.columns["fileSize"].updateTd = displaySize;
this.columns["nbSeeders"].updateTd = displayNum;
this.columns["nbLeechers"].updateTd = displayNum;
this.columns["pubDate"].updateTd = displayDate;
2022-10-18 22:39:32 -04:00
},
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 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").value === "names";
2022-10-18 22:39:32 -04:00
if (searchInTorrentName || (filterTerms.length > 0) || (window.qBittorrent.Search.searchSizeFilter.min > 0.00) || (window.qBittorrent.Search.searchSizeFilter.max > 0.00)) {
for (const row of this.getRowValues()) {
2022-10-18 22:39:32 -04:00
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 = [...this.getRowValues()];
2022-10-18 22:39:32 -04:00
}
filteredRows.sort((row1, row2) => {
2022-10-18 22:39:32 -04:00
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
if (this.reverseSort === "0")
2022-10-18 22:39:32 -04:00
return res;
else
return -res;
});
2022-10-18 22:39:32 -04:00
return filteredRows;
},
setupTr: function(tr) {
tr.addClass("searchTableRow");
}
});
const SearchPluginsTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("fullName", "", "Name", 175, true);
this.newColumn("version", "", "Version", 100, true);
this.newColumn("url", "", "Url", 175, true);
this.newColumn("enabled", "", "Enabled", 100, true);
2022-10-18 22:39:32 -04:00
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
this.columns["enabled"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
if (value) {
td.textContent = "Yes";
td.title = "Yes";
2022-10-18 22:39:32 -04:00
td.getParent("tr").addClass("green");
td.getParent("tr").removeClass("red");
}
else {
td.textContent = "No";
td.title = "No";
2022-10-18 22:39:32 -04:00
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", "", "Tier", 35, true);
this.newColumn("url", "", "URL", 250, true);
this.newColumn("status", "", "Status", 125, true);
this.newColumn("peers", "", "Peers", 75, true);
this.newColumn("seeds", "", "Seeds", 75, true);
this.newColumn("leeches", "", "Leeches", 75, true);
this.newColumn("downloaded", "", "Times Downloaded", 100, true);
this.newColumn("message", "", "Message", 250, true);
2022-10-18 22:39:32 -04:00
},
});
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((node) => {
this._addNodeToTable(node, 0);
});
},
_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((child) => {
this._addNodeToTable(child, depth + 1);
});
},
getRoot: function() {
return this.fileTree.getRoot();
},
getNode: function(rowId) {
return this.fileTree.getNode(rowId);
},
getRow: function(node) {
const rowId = this.fileTree.getRowId(node).toString();
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", "", "Original", 270, true);
this.newColumn("renamed", "", "Renamed", 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) {
const cb = checkboxes[i];
cb.checked = true;
cb.indeterminate = false;
cb.state = "checked";
node.checked = 0;
node.full_data.checked = node.checked;
}
else {
const 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.type = "checkbox";
checkbox.id = "cbRename" + id;
checkbox.setAttribute("data-id", id);
checkbox.className = "RenamingCB";
checkbox.addEventListener("click", (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).textContent = 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
});
td.replaceChildren(dirImg, span);
}
}
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.replaceChildren(span);
}
};
// 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.replaceChildren(span);
};
},
onRowSelectionChange: function(row) {},
selectRow: function() {
return;
},
reselectRows: function(rowIds) {
const that = this;
this.deselectAll();
this.tableBody.getElements("tr").each((tr) => {
if (rowIds.includes(tr.rowId)) {
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();
},
_sortNodesByColumn: function(nodes, column) {
nodes.sort((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;
});
nodes.each((node) => {
if (node.children.length > 0)
this._sortNodesByColumn(node.children, column);
});
},
_filterNodes: function(node, filterTerms, filteredRows) {
if (node.isFolder) {
const childAdded = node.children.reduce((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);
}, 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 = () => {
const rowsData = [];
for (const { full_data } of this.getRowValues())
rowsData.push(full_data);
return JSON.stringify(rowsData);
};
const getFilteredRows = function() {
if (this.filterTerms.length === 0) {
const nodeArray = this.fileTree.toArray();
const filteredRows = nodeArray.map((node) => {
return this.getRow(node);
});
return filteredRows;
}
const filteredRows = [];
this.getRoot().children.each((child) => {
this._filterNodes(child, this.filterTerms, filteredRows);
});
filteredRows.reverse();
return filteredRows;
}.bind(this);
const hasRowsChanged = function(rowsString, prevRowsStringString) {
const rowsChanged = (rowsString !== prevRowsStringString);
const isFilterTermsChanged = this.filterTerms.reduce((acc, term, index) => {
return (acc || (term !== this.prevFilterTerms[index]));
}, 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();
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.addEventListener("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;
}
});
}
});
2022-10-18 22:39:32 -04:00
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((node) => {
2022-10-18 22:39:32 -04:00
this._addNodeToTable(node, 0);
});
2022-10-18 22:39:32 -04:00
},
_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((child) => {
2022-10-18 22:39:32 -04:00
this._addNodeToTable(child, depth + 1);
});
2022-10-18 22:39:32 -04:00
},
getRoot: function() {
return this.fileTree.getRoot();
},
getNode: function(rowId) {
return this.fileTree.getNode(rowId);
},
getRow: function(node) {
const rowId = this.fileTree.getRowId(node).toString();
2022-10-18 22:39:32 -04:00
return this.rows.get(rowId);
},
initColumns: function() {
this.newColumn("checked", "", "", 50, true);
this.newColumn("name", "", "Name", 300, true);
this.newColumn("size", "", "Total Size", 75, true);
this.newColumn("progress", "", "Progress", 100, true);
this.newColumn("priority", "", "Download Priority", 150, true);
this.newColumn("remaining", "", "Remaining", 75, true);
this.newColumn("availability", "", "Availability", 75, true);
2022-10-18 22:39:32 -04:00
this.initColumnsFunctions();
},
initColumnsFunctions: function() {
const that = this;
const displaySize = function(td, row) {
const size = window.qBittorrent.Misc.friendlyUnit(this.getRowValue(row), false);
td.textContent = size;
td.title = size;
2022-10-18 22:39:32 -04:00
};
const displayPercentage = function(td, row) {
const value = window.qBittorrent.Misc.friendlyPercentage(this.getRowValue(row));
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
// checked
this.columns["checked"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
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",
2022-10-18 22:39:32 -04:00
styles: {
"margin-bottom": -2
2022-10-18 22:39:32 -04:00
}
});
td.adopt(treeImg, window.qBittorrent.PropFiles.createDownloadCheckbox(id, row.full_data.fileId, value));
}
};
// name
this.columns["name"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const id = row.rowId;
const fileNameId = "filesTablefileName" + id;
2022-10-18 22:39:32 -04:00
const node = that.getNode(id);
if (node.isFolder) {
const value = this.getRowValue(row);
const collapseIconId = "filesTableCollapseIcon" + id;
const dirImgId = "filesTableDirImg" + id;
2022-10-18 22:39:32 -04:00
if ($(dirImgId)) {
// just update file name
$(fileNameId).textContent = value;
2022-10-18 22:39:32 -04:00
}
else {
const collapseIcon = new Element("img", {
src: "images/go-down.svg",
2022-10-18 22:39:32 -04:00
styles: {
"margin-left": (node.depth * 20)
2022-10-18 22:39:32 -04:00
},
class: "filesTableCollapseIcon",
id: collapseIconId,
"data-id": id,
onclick: "qBittorrent.PropFiles.collapseIconClicked(this)"
});
const span = new Element("span", {
2022-10-18 22:39:32 -04:00
text: value,
id: fileNameId
});
const dirImg = new Element("img", {
src: "images/directory.svg",
2022-10-18 22:39:32 -04:00
styles: {
"width": 15,
"padding-right": 5,
"margin-bottom": -3
2022-10-18 22:39:32 -04:00
},
id: dirImgId
});
td.replaceChildren(collapseIcon, dirImg, span);
2022-10-18 22:39:32 -04:00
}
}
else {
const value = this.getRowValue(row);
const span = new Element("span", {
2022-10-18 22:39:32 -04:00
text: value,
id: fileNameId,
styles: {
"margin-left": ((node.depth + 1) * 20)
2022-10-18 22:39:32 -04:00
}
});
td.replaceChildren(span);
2022-10-18 22:39:32 -04:00
}
};
// size
this.columns["size"].updateTd = displaySize;
2022-10-18 22:39:32 -04:00
// progress
this.columns["progress"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const id = row.rowId;
const value = this.getRowValue(row);
const progressBar = $("pbf_" + id);
2022-10-18 22:39:32 -04:00
if (progressBar === null) {
td.adopt(new window.qBittorrent.ProgressBar.ProgressBar(value.toFloat(), {
id: "pbf_" + id,
2022-10-18 22:39:32 -04:00
width: 80
}));
}
else {
progressBar.setValue(value.toFloat());
}
};
// priority
this.columns["priority"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
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;
2022-10-18 22:39:32 -04:00
},
_sortNodesByColumn: function(nodes, column) {
nodes.sort((row1, row2) => {
2022-10-18 22:39:32 -04:00
// 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;
});
2022-10-18 22:39:32 -04:00
nodes.each((node) => {
2022-10-18 22:39:32 -04:00
if (node.children.length > 0)
this._sortNodesByColumn(node.children, column);
});
2022-10-18 22:39:32 -04:00
},
_filterNodes: function(node, filterTerms, filteredRows) {
if (node.isFolder) {
const childAdded = node.children.reduce((acc, child) => {
2022-10-18 22:39:32 -04:00
// 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);
}, false);
2022-10-18 22:39:32 -04:00
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] === ""))
2022-10-18 22:39:32 -04:00
this.filterTerms = [];
else
this.filterTerms = filterTerms;
},
getFilteredAndSortedRows: function() {
if (this.getRoot() === null)
return [];
const generateRowsSignature = () => {
const rowsData = [];
for (const { full_data } of this.getRowValues())
rowsData.push(full_data);
2022-10-18 22:39:32 -04:00
return JSON.stringify(rowsData);
};
const getFilteredRows = function() {
if (this.filterTerms.length === 0) {
const nodeArray = this.fileTree.toArray();
const filteredRows = nodeArray.map((node) => {
2022-10-18 22:39:32 -04:00
return this.getRow(node);
});
2022-10-18 22:39:32 -04:00
return filteredRows;
}
const filteredRows = [];
this.getRoot().children.each((child) => {
2022-10-18 22:39:32 -04:00
this._filterNodes(child, this.filterTerms, filteredRows);
});
2022-10-18 22:39:32 -04:00
filteredRows.reverse();
return filteredRows;
}.bind(this);
const hasRowsChanged = function(rowsString, prevRowsStringString) {
const rowsChanged = (rowsString !== prevRowsStringString);
const isFilterTermsChanged = this.filterTerms.reduce((acc, term, index) => {
2022-10-18 22:39:32 -04:00
return (acc || (term !== this.prevFilterTerms[index]));
}, false);
2022-10-18 22:39:32 -04:00
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();
if (!hasRowsChanged(rowsString, this.prevRowsString))
2022-10-18 22:39:32 -04:00
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.toString());
2022-10-18 22:39:32 -04:00
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.addEventListener("keydown", function(event) {
2022-10-18 22:39:32 -04:00
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", "", "RSS feeds", -1, true);
2022-10-18 22:39:32 -04:00
this.columns["state_icon"].dataProperties[0] = "";
2022-10-18 22:39:32 -04:00
// map name row to "[name] ([unread])"
this.columns["name"].dataProperties.push("unread");
this.columns["name"].updateTd = function(td, row) {
2022-10-18 22:39:32 -04:00
const name = this.getRowValue(row, 0);
const unreadCount = this.getRowValue(row, 1);
const value = name + " (" + unreadCount + ")";
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return [...this.getRowValues()];
2022-10-18 22:39:32 -04:00
},
selectRow: function(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
let path = "";
for (const row of this.getRowValues()) {
if (row.rowId === rowId) {
path = row.full_data.dataPath;
2022-10-18 22:39:32 -04:00
break;
}
}
window.qBittorrent.Rss.showRssFeed(path);
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
2022-10-18 22:39:32 -04:00
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"];
2022-10-18 22:39:32 -04:00
const tds = tr.getElements("td");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0]))
2022-10-18 22:39:32 -04:00
this.columns[i].updateTd(tds[i], row);
}
row["data"] = {};
tds[0].style.overflow = "visible";
const indentation = row.full_data.indentation;
tds[0].style.paddingLeft = (indentation * 32 + 4) + "px";
tds[1].style.paddingLeft = (indentation * 32 + 4) + "px";
2022-10-18 22:39:32 -04:00
},
updateIcons: function() {
// state_icon
for (const row of this.getRowValues()) {
2022-10-18 22:39:32 -04:00
let img_path;
switch (row.full_data.status) {
case "default":
img_path = "images/application-rss.svg";
2022-10-18 22:39:32 -04:00
break;
case "hasError":
img_path = "images/task-reject.svg";
2022-10-18 22:39:32 -04:00
break;
case "isLoading":
img_path = "images/spinner.gif";
2022-10-18 22:39:32 -04:00
break;
case "unread":
img_path = "images/mail-inbox.svg";
2022-10-18 22:39:32 -04:00
break;
case "isFolder":
img_path = "images/folder-documents.svg";
2022-10-18 22:39:32 -04:00
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.includes(img_path)) {
img.src = img_path;
img.title = status;
2022-10-18 22:39:32 -04:00
}
}
else {
td.adopt(new Element("img", {
"src": img_path,
"class": "stateIcon",
"height": "22px",
"width": "22px"
2022-10-18 22:39:32 -04:00
}));
}
};
2022-10-18 22:39:32 -04:00
},
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) {
2022-10-18 22:39:32 -04:00
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
2022-10-18 22:39:32 -04:00
};
column["compareRows"] = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
column["updateTd"] = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
column["onResize"] = null;
2022-10-18 22:39:32 -04:00
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.appendChild(new Element("th"));
this.fixedTableHeader.appendChild(new Element("th"));
2022-10-18 22:39:32 -04:00
}
});
const RssArticleTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("name", "", "Torrents: (double-click to download)", -1, true);
2022-10-18 22:39:32 -04:00
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return [...this.getRowValues()];
2022-10-18 22:39:32 -04:00
},
selectRow: function(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
let articleId = "";
let feedUid = "";
for (const row of this.getRowValues()) {
if (row.rowId === rowId) {
articleId = row.full_data.dataId;
feedUid = row.full_data.feedUid;
this.tableBody.rows[row.rowId].removeClass("unreadArticle");
2022-10-18 22:39:32 -04:00
break;
}
}
window.qBittorrent.Rss.showDetails(feedUid, articleId);
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
2022-10-18 22:39:32 -04:00
showDownloadPage([this._this.rows.get(this.rowId).full_data.torrentURL]);
return true;
});
tr.addClass("torrentsTableContextMenuTarget");
2022-10-18 22:39:32 -04:00
},
updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? "full_data" : "data"];
2022-10-18 22:39:32 -04:00
if (!row.full_data.isRead)
tr.addClass("unreadArticle");
2022-10-18 22:39:32 -04:00
else
tr.removeClass("unreadArticle");
2022-10-18 22:39:32 -04:00
const tds = tr.getElements("td");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0]))
2022-10-18 22:39:32 -04:00
this.columns[i].updateTd(tds[i], row);
}
row["data"] = {};
2022-10-18 22:39:32 -04:00
},
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) {
2022-10-18 22:39:32 -04:00
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
2022-10-18 22:39:32 -04:00
};
column["compareRows"] = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
column["updateTd"] = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
column["onResize"] = null;
2022-10-18 22:39:32 -04:00
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.appendChild(new Element("th"));
this.fixedTableHeader.appendChild(new Element("th"));
2022-10-18 22:39:32 -04:00
}
});
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.type = "checkbox";
checkbox.id = "cbRssDlRule" + row.rowId;
2022-10-18 22:39:32 -04:00
checkbox.checked = row.full_data.checked;
checkbox.addEventListener("click", function(e) {
2022-10-18 22:39:32 -04:00
window.qBittorrent.RssDownloader.rssDownloaderRulesTable.updateRowData({
rowId: row.rowId,
checked: this.checked
});
window.qBittorrent.RssDownloader.modifyRuleState(row.full_data.name, "enabled", this.checked);
2022-10-18 22:39:32 -04:00
e.stopPropagation();
});
td.append(checkbox);
}
else {
$("cbRssDlRule" + row.rowId).checked = row.full_data.checked;
2022-10-18 22:39:32 -04:00
}
};
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return [...this.getRowValues()];
2022-10-18 22:39:32 -04:00
},
setupTr: function(tr) {
tr.addEventListener("dblclick", function(e) {
2022-10-18 22:39:32 -04:00
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) {
2022-10-18 22:39:32 -04:00
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
2022-10-18 22:39:32 -04:00
};
column["compareRows"] = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
column["updateTd"] = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
column["onResize"] = null;
2022-10-18 22:39:32 -04:00
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.appendChild(new Element("th"));
this.fixedTableHeader.appendChild(new Element("th"));
2022-10-18 22:39:32 -04:00
},
selectRow: function(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
let name = "";
for (const row of this.getRowValues()) {
if (row.rowId === rowId) {
name = row.full_data.name;
2022-10-18 22:39:32 -04:00
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.type = "checkbox";
checkbox.id = "cbRssDlFeed" + row.rowId;
2022-10-18 22:39:32 -04:00
checkbox.checked = row.full_data.checked;
checkbox.addEventListener("click", function(e) {
2022-10-18 22:39:32 -04:00
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;
2022-10-18 22:39:32 -04:00
}
};
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return [...this.getRowValues()];
2022-10-18 22:39:32 -04:00
},
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) {
2022-10-18 22:39:32 -04:00
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
2022-10-18 22:39:32 -04:00
};
column["compareRows"] = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
column["updateTd"] = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
column["onResize"] = null;
2022-10-18 22:39:32 -04:00
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.appendChild(new Element("th"));
this.fixedTableHeader.appendChild(new Element("th"));
2022-10-18 22:39:32 -04:00
},
selectRow: function() {}
});
const RssDownloaderArticlesTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("name", "", "", -1, true);
2022-10-18 22:39:32 -04:00
},
setupHeaderMenu: function() {},
setupHeaderEvents: function() {},
getFilteredAndSortedRows: function() {
return [...this.getRowValues()];
2022-10-18 22:39:32 -04:00
},
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) {
2022-10-18 22:39:32 -04:00
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
2022-10-18 22:39:32 -04:00
};
column["compareRows"] = function(row1, row2) {
2022-10-18 22:39:32 -04:00
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);
2022-10-18 22:39:32 -04:00
};
column["updateTd"] = function(td, row) {
2022-10-18 22:39:32 -04:00
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
2022-10-18 22:39:32 -04:00
};
column["onResize"] = null;
2022-10-18 22:39:32 -04:00
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.appendChild(new Element("th"));
this.fixedTableHeader.appendChild(new Element("th"));
2022-10-18 22:39:32 -04:00
},
selectRow: function() {},
updateRow: function(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);
const data = row[fullUpdate ? "full_data" : "data"];
2022-10-18 22:39:32 -04:00
if (row.full_data.isFeed) {
tr.addClass("articleTableFeed");
tr.removeClass("articleTableArticle");
2022-10-18 22:39:32 -04:00
}
else {
tr.removeClass("articleTableFeed");
tr.addClass("articleTableArticle");
2022-10-18 22:39:32 -04:00
}
const tds = tr.getElements("td");
2022-10-18 22:39:32 -04:00
for (let i = 0; i < this.columns.length; ++i) {
if (Object.hasOwn(data, this.columns[i].dataProperties[0]))
2022-10-18 22:39:32 -04:00
this.columns[i].updateTd(tds[i], row);
}
row["data"] = {};
2022-10-18 22:39:32 -04:00
}
});
const LogMessageTable = new Class({
Extends: DynamicTable,
filterText: "",
filteredLength: function() {
return this.tableBody.getElements("tr").length;
},
initColumns: function() {
this.newColumn("rowId", "", "ID", 50, true);
this.newColumn("message", "", "Message", 350, true);
this.newColumn("timestamp", "", "Timestamp", 150, true);
this.newColumn("type", "", "Log Type", 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 = "Normal";
addClass = "logNormal";
break;
case 2:
logLevel = "Info";
addClass = "logInfo";
break;
case 4:
logLevel = "Warning";
addClass = "logWarning";
break;
case 8:
logLevel = "Critical";
addClass = "logCritical";
break;
default:
logLevel = "Unknown";
addClass = "logUnknown";
break;
}
td.set({ "text": logLevel, "title": logLevel });
td.getParent("tr").className = `logTableRow${addClass}`;
};
},
getFilteredAndSortedRows: function() {
let filteredRows = [];
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 (const row of this.getRowValues()) {
if (!logLevels.includes(row.full_data.type.toString()))
continue;
if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(row.full_data.message, filterTerms))
continue;
filteredRows.push(row);
}
}
else {
filteredRows = [...this.getRowValues()];
}
filteredRows.sort((row1, row2) => {
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
return (this.reverseSort === "0") ? res : -res;
});
return filteredRows;
},
setupCommonEvents: function() {},
setupTr: function(tr) {
tr.addClass("logTableRow");
}
});
const LogPeerTable = new Class({
Extends: LogMessageTable,
initColumns: function() {
this.newColumn("rowId", "", "ID", 50, true);
this.newColumn("ip", "", "IP", 150, true);
this.newColumn("timestamp", "", "Timestamp", 150, true);
this.newColumn("blocked", "", "Status", 150, true);
this.newColumn("reason", "", "Reason", 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 = "Blocked";
addClass = "peerBlocked";
}
else {
status = "Banned";
addClass = "peerBanned";
}
td.set({ "text": status, "title": status });
td.getParent("tr").className = `logTableRow${addClass}`;
};
},
getFilteredAndSortedRows: function() {
let filteredRows = [];
this.filterText = window.qBittorrent.Log.getFilterText();
const filterTerms = (this.filterText.length > 0) ? this.filterText.toLowerCase().split(" ") : [];
if (filterTerms.length > 0) {
for (const row of this.getRowValues()) {
if ((filterTerms.length > 0) && !window.qBittorrent.Misc.containsAllTerms(row.full_data.ip, filterTerms))
continue;
filteredRows.push(row);
}
}
else {
filteredRows = [...this.getRowValues()];
}
filteredRows.sort((row1, row2) => {
const column = this.columns[this.sortedColumn];
const res = column.compareRows(row1, row2);
return (this.reverseSort === "0") ? res : -res;
});
return filteredRows;
}
});
const TorrentWebseedsTable = new Class({
Extends: DynamicTable,
initColumns: function() {
this.newColumn("url", "", "URL", 500, true);
},
});
2022-10-18 22:39:32 -04:00
return exports();
})();
Object.freeze(window.qBittorrent.DynamicTable);
/*************************************************************/