mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-20 13:23:34 -07:00
WebUI: Optimize table performance with virtual list
Adding virtual list support to dynamic tables to improve performance on large lists, I observed a 100x performance improvement on rendering on a torrent table with 5000 torrents. This optimization is disabled by default and can be enabled in options. PR #22502.
This commit is contained in:
parent
250fef4ee7
commit
b4a16f6464
9 changed files with 474 additions and 359 deletions
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
// Setup the dynamic table for bulk renaming
|
// Setup the dynamic table for bulk renaming
|
||||||
const bulkRenameFilesTable = new window.qBittorrent.DynamicTable.BulkRenameTorrentFilesTable();
|
const bulkRenameFilesTable = new window.qBittorrent.DynamicTable.BulkRenameTorrentFilesTable();
|
||||||
bulkRenameFilesTable.setup("bulkRenameFilesTableDiv", "bulkRenameFilesTableFixedHeaderDiv", bulkRenameFilesContextMenu);
|
bulkRenameFilesTable.setup("bulkRenameFilesTableDiv", "bulkRenameFilesTableFixedHeaderDiv", bulkRenameFilesContextMenu, true);
|
||||||
|
|
||||||
// Inject checkbox into the first column of the table header
|
// Inject checkbox into the first column of the table header
|
||||||
const tableHeaders = document.querySelectorAll("#bulkRenameFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
const tableHeaders = document.querySelectorAll("#bulkRenameFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
||||||
|
@ -172,7 +172,10 @@
|
||||||
// Update renamed column for matched rows
|
// Update renamed column for matched rows
|
||||||
for (let i = 0; i < matchedRows.length; ++i) {
|
for (let i = 0; i < matchedRows.length; ++i) {
|
||||||
const row = matchedRows[i];
|
const row = matchedRows[i];
|
||||||
$(`filesTablefileRenamed${row.rowId}`).textContent = row.renamed;
|
const element = document.getElementById(`filesTablefileRenamed${row.rowId}`);
|
||||||
|
if (element === null)
|
||||||
|
continue;
|
||||||
|
element.textContent = row.renamed;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fileRenamer.onInvalidRegex = (err) => {
|
fileRenamer.onInvalidRegex = (err) => {
|
||||||
|
@ -287,11 +290,6 @@
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.qBittorrent.Client.closeWindow(windowEl);
|
window.qBittorrent.Client.closeWindow(windowEl);
|
||||||
});
|
});
|
||||||
// synchronize header scrolling to table body
|
|
||||||
$("bulkRenameFilesTableDiv").onscroll = function() {
|
|
||||||
const length = $(this).scrollLeft;
|
|
||||||
$("bulkRenameFilesTableFixedHeaderDiv").scrollLeft = length;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTorrentFiles = (files, selectedRows) => {
|
const handleTorrentFiles = (files, selectedRows) => {
|
||||||
const rows = files.map((file, index) => {
|
const rows = files.map((file, index) => {
|
||||||
|
|
|
@ -71,14 +71,18 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
|
|
||||||
initialize: () => {},
|
initialize: () => {},
|
||||||
|
|
||||||
setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu) {
|
setup: function(dynamicTableDivId, dynamicTableFixedHeaderDivId, contextMenu, useVirtualList = false) {
|
||||||
this.dynamicTableDivId = dynamicTableDivId;
|
this.dynamicTableDivId = dynamicTableDivId;
|
||||||
this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId;
|
this.dynamicTableFixedHeaderDivId = dynamicTableFixedHeaderDivId;
|
||||||
this.dynamicTableDiv = document.getElementById(dynamicTableDivId);
|
this.dynamicTableDiv = document.getElementById(dynamicTableDivId);
|
||||||
|
this.useVirtualList = useVirtualList && (LocalPreferences.get("use_virtual_list", "false") === "true");
|
||||||
this.fixedTableHeader = document.querySelector(`#${dynamicTableFixedHeaderDivId} thead tr`);
|
this.fixedTableHeader = document.querySelector(`#${dynamicTableFixedHeaderDivId} thead tr`);
|
||||||
this.hiddenTableHeader = this.dynamicTableDiv.querySelector(`thead tr`);
|
this.hiddenTableHeader = this.dynamicTableDiv.querySelector(`thead tr`);
|
||||||
|
this.table = this.dynamicTableDiv.querySelector(`table`);
|
||||||
this.tableBody = this.dynamicTableDiv.querySelector(`tbody`);
|
this.tableBody = this.dynamicTableDiv.querySelector(`tbody`);
|
||||||
|
this.rowHeight = 26;
|
||||||
this.rows = new Map();
|
this.rows = new Map();
|
||||||
|
this.cachedElements = [];
|
||||||
this.selectedRows = [];
|
this.selectedRows = [];
|
||||||
this.columns = [];
|
this.columns = [];
|
||||||
this.contextMenu = contextMenu;
|
this.contextMenu = contextMenu;
|
||||||
|
@ -91,14 +95,34 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
this.setupHeaderEvents();
|
this.setupHeaderEvents();
|
||||||
this.setupHeaderMenu();
|
this.setupHeaderMenu();
|
||||||
this.setupAltRow();
|
this.setupAltRow();
|
||||||
|
this.setupVirtualList();
|
||||||
|
},
|
||||||
|
|
||||||
|
setupVirtualList: function() {
|
||||||
|
if (!this.useVirtualList)
|
||||||
|
return;
|
||||||
|
this.table.style.position = "relative";
|
||||||
|
|
||||||
|
this.renderedHeight = this.dynamicTableDiv.offsetHeight;
|
||||||
|
const resizeCallback = window.qBittorrent.Misc.createDebounceHandler(100, () => {
|
||||||
|
const height = this.dynamicTableDiv.offsetHeight;
|
||||||
|
const needRerender = this.renderedHeight < height;
|
||||||
|
this.renderedHeight = height;
|
||||||
|
if (needRerender)
|
||||||
|
this.rerender();
|
||||||
|
});
|
||||||
|
new ResizeObserver(resizeCallback).observe(this.dynamicTableDiv);
|
||||||
},
|
},
|
||||||
|
|
||||||
setupCommonEvents: function() {
|
setupCommonEvents: function() {
|
||||||
const tableFixedHeaderDiv = $(this.dynamicTableFixedHeaderDivId);
|
const tableFixedHeaderDiv = document.getElementById(this.dynamicTableFixedHeaderDivId);
|
||||||
|
|
||||||
const tableElement = tableFixedHeaderDiv.querySelector("table");
|
const tableElement = tableFixedHeaderDiv.querySelector("table");
|
||||||
this.dynamicTableDiv.addEventListener("scroll", function() {
|
this.dynamicTableDiv.addEventListener("scroll", (e) => {
|
||||||
tableElement.style.left = `${-this.scrollLeft}px`;
|
tableElement.style.left = `${-this.dynamicTableDiv.scrollLeft}px`;
|
||||||
|
// rerender on scroll
|
||||||
|
if (this.useVirtualList)
|
||||||
|
this.rerender();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dynamicTableDiv.addEventListener("click", (e) => {
|
this.dynamicTableDiv.addEventListener("click", (e) => {
|
||||||
|
@ -404,6 +428,9 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const style = `width: ${column.width}px; ${column.style}`;
|
const style = `width: ${column.width}px; ${column.style}`;
|
||||||
this.getRowCells(this.hiddenTableHeader)[pos].style.cssText = style;
|
this.getRowCells(this.hiddenTableHeader)[pos].style.cssText = style;
|
||||||
this.getRowCells(this.fixedTableHeader)[pos].style.cssText = style;
|
this.getRowCells(this.fixedTableHeader)[pos].style.cssText = style;
|
||||||
|
// rerender on column resize
|
||||||
|
if (this.useVirtualList)
|
||||||
|
this.rerender();
|
||||||
|
|
||||||
column.onResize?.(column.name);
|
column.onResize?.(column.name);
|
||||||
},
|
},
|
||||||
|
@ -707,10 +734,9 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
|
|
||||||
selectAll: function() {
|
selectAll: function() {
|
||||||
this.deselectAll();
|
this.deselectAll();
|
||||||
for (const tr of this.getTrs()) {
|
for (const row of this.getFilteredAndSortedRows())
|
||||||
this.selectedRows.push(tr.rowId);
|
this.selectedRows.push(row.rowId);
|
||||||
tr.classList.add("selected");
|
this.setRowClass();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
deselectAll: function() {
|
deselectAll: function() {
|
||||||
|
@ -832,64 +858,138 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const trs = [...this.getTrs()];
|
if (this.useVirtualList) {
|
||||||
|
// rerender on table update
|
||||||
|
this.rerender(rows);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const trs = [...this.getTrs()];
|
||||||
|
|
||||||
for (let rowPos = 0; rowPos < rows.length; ++rowPos) {
|
for (let rowPos = 0; rowPos < rows.length; ++rowPos) {
|
||||||
const rowId = rows[rowPos]["rowId"];
|
const rowId = rows[rowPos].rowId;
|
||||||
let tr_found = false;
|
let tr_found = false;
|
||||||
for (let j = rowPos; j < trs.length; ++j) {
|
for (let j = rowPos; j < trs.length; ++j) {
|
||||||
if (trs[j]["rowId"] === rowId) {
|
if (trs[j].rowId === rowId) {
|
||||||
tr_found = true;
|
tr_found = true;
|
||||||
if (rowPos === j)
|
if (rowPos === j)
|
||||||
|
break;
|
||||||
|
trs[j].inject(trs[rowPos], "before");
|
||||||
|
const tmpTr = trs[j];
|
||||||
|
trs.splice(j, 1);
|
||||||
|
trs.splice(rowPos, 0, tmpTr);
|
||||||
break;
|
break;
|
||||||
trs[j].inject(trs[rowPos], "before");
|
}
|
||||||
const tmpTr = trs[j];
|
}
|
||||||
trs.splice(j, 1);
|
if (tr_found) { // row already exists in the table
|
||||||
trs.splice(rowPos, 0, tmpTr);
|
this.updateRow(trs[rowPos], fullUpdate);
|
||||||
break;
|
}
|
||||||
|
else { // else create a new row in the table
|
||||||
|
const tr = this.createRowElement(rows[rowPos]);
|
||||||
|
|
||||||
|
// Insert
|
||||||
|
if (rowPos >= trs.length) {
|
||||||
|
tr.inject(this.tableBody);
|
||||||
|
trs.push(tr);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tr.inject(trs[rowPos], "before");
|
||||||
|
trs.splice(rowPos, 0, tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateRow(tr, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tr_found) { // row already exists in the table
|
|
||||||
this.updateRow(trs[rowPos], fullUpdate);
|
const rowPos = rows.length;
|
||||||
|
|
||||||
|
while ((rowPos < trs.length) && (trs.length > 0))
|
||||||
|
trs.pop().destroy();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rerender: function(rows = this.getFilteredAndSortedRows()) {
|
||||||
|
// set the scrollable height
|
||||||
|
this.table.style.height = `${rows.length * this.rowHeight}px`;
|
||||||
|
|
||||||
|
// show extra 6 rows at top/bottom to reduce flickering
|
||||||
|
const extraRowCount = 6;
|
||||||
|
// how many rows can be shown in the visible area
|
||||||
|
const visibleRowCount = Math.ceil(this.renderedHeight / this.rowHeight) + (extraRowCount * 2);
|
||||||
|
// start position of visible rows, offsetted by scrollTop
|
||||||
|
let startRow = Math.max((Math.trunc(this.dynamicTableDiv.scrollTop / this.rowHeight) - extraRowCount), 0);
|
||||||
|
// ensure startRow is even
|
||||||
|
if ((startRow % 2) === 1)
|
||||||
|
startRow = Math.max(0, startRow - 1);
|
||||||
|
const endRow = Math.min((startRow + visibleRowCount), rows.length);
|
||||||
|
|
||||||
|
const elements = [];
|
||||||
|
for (let i = startRow; i < endRow; ++i) {
|
||||||
|
const row = rows[i];
|
||||||
|
if (row === undefined)
|
||||||
|
continue;
|
||||||
|
const offset = i * this.rowHeight;
|
||||||
|
const position = i - startRow;
|
||||||
|
// reuse existing elements
|
||||||
|
let element = this.cachedElements[position];
|
||||||
|
if (element !== undefined)
|
||||||
|
this.updateRowElement(element, row.rowId, offset);
|
||||||
|
else
|
||||||
|
element = this.cachedElements[position] = this.createRowElement(row, offset);
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
this.tableBody.replaceChildren(...elements);
|
||||||
|
|
||||||
|
// update row classes
|
||||||
|
this.setRowClass();
|
||||||
|
|
||||||
|
// update visible rows
|
||||||
|
for (const row of this.tableBody.children)
|
||||||
|
this.updateRow(row, true);
|
||||||
|
|
||||||
|
// refresh row height based on first row
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.tableBody.firstChild === null)
|
||||||
|
return;
|
||||||
|
const tr = this.tableBody.firstChild;
|
||||||
|
if (this.rowHeight !== tr.offsetHeight) {
|
||||||
|
this.rowHeight = tr.offsetHeight;
|
||||||
|
// rerender on row height change
|
||||||
|
this.rerender();
|
||||||
}
|
}
|
||||||
else { // else create a new row in the table
|
|
||||||
const tr = document.createElement("tr");
|
|
||||||
// set tabindex so element receives keydown events
|
|
||||||
// more info: https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
|
|
||||||
tr.tabIndex = -1;
|
|
||||||
|
|
||||||
const rowId = rows[rowPos]["rowId"];
|
});
|
||||||
tr.setAttribute("data-row-id", rowId);
|
},
|
||||||
tr["rowId"] = rowId;
|
|
||||||
|
|
||||||
for (let k = 0; k < this.columns.length; ++k) {
|
createRowElement: function(row, top = -1) {
|
||||||
const td = document.createElement("td");
|
const tr = document.createElement("tr");
|
||||||
if ((this.columns[k].visible === "0") || this.columns[k].force_hide)
|
// set tabindex so element receives keydown events
|
||||||
td.classList.add("invisible");
|
// more info: https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
|
||||||
tr.append(td);
|
tr.tabIndex = -1;
|
||||||
}
|
|
||||||
|
|
||||||
// Insert
|
for (let k = 0; k < this.columns.length; ++k) {
|
||||||
if (rowPos >= trs.length) {
|
const td = document.createElement("td");
|
||||||
tr.inject(this.tableBody);
|
if ((this.columns[k].visible === "0") || this.columns[k].force_hide)
|
||||||
trs.push(tr);
|
td.classList.add("invisible");
|
||||||
}
|
tr.append(td);
|
||||||
else {
|
|
||||||
tr.inject(trs[rowPos], "before");
|
|
||||||
trs.splice(rowPos, 0, tr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update context menu
|
|
||||||
this.contextMenu?.addTarget(tr);
|
|
||||||
|
|
||||||
this.updateRow(tr, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowPos = rows.length;
|
this.updateRowElement(tr, row.rowId, top);
|
||||||
|
|
||||||
while ((rowPos < trs.length) && (trs.length > 0))
|
// update context menu
|
||||||
trs.pop().destroy();
|
this.contextMenu?.addTarget(tr);
|
||||||
|
return tr;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateRowElement: function(tr, rowId, top) {
|
||||||
|
tr.dataset.rowId = rowId;
|
||||||
|
tr.rowId = rowId;
|
||||||
|
|
||||||
|
tr.className = "";
|
||||||
|
|
||||||
|
if (this.useVirtualList) {
|
||||||
|
tr.style.position = "absolute";
|
||||||
|
tr.style.top = `${top}px`;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateRow: function(tr, fullUpdate) {
|
updateRow: function(tr, fullUpdate) {
|
||||||
|
@ -898,6 +998,11 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
|
|
||||||
const tds = this.getRowCells(tr);
|
const tds = this.getRowCells(tr);
|
||||||
for (let i = 0; i < this.columns.length; ++i) {
|
for (let i = 0; i < this.columns.length; ++i) {
|
||||||
|
// required due to position: absolute breaks table layout
|
||||||
|
if (this.useVirtualList) {
|
||||||
|
tds[i].style.width = `${this.columns[i].width}px`;
|
||||||
|
tds[i].style.maxWidth = `${this.columns[i].width}px`;
|
||||||
|
}
|
||||||
if (this.columns[i].dataProperties.some(prop => Object.hasOwn(data, prop)))
|
if (this.columns[i].dataProperties.some(prop => Object.hasOwn(data, prop)))
|
||||||
this.columns[i].updateTd(tds[i], row);
|
this.columns[i].updateTd(tds[i], row);
|
||||||
}
|
}
|
||||||
|
@ -907,15 +1012,25 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
removeRow: function(rowId) {
|
removeRow: function(rowId) {
|
||||||
this.selectedRows.erase(rowId);
|
this.selectedRows.erase(rowId);
|
||||||
this.rows.delete(rowId);
|
this.rows.delete(rowId);
|
||||||
const tr = this.getTrByRowId(rowId);
|
if (this.useVirtualList) {
|
||||||
tr?.destroy();
|
this.rerender();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const tr = this.getTrByRowId(rowId);
|
||||||
|
tr?.destroy();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
clear: function() {
|
clear: function() {
|
||||||
this.deselectAll();
|
this.deselectAll();
|
||||||
this.rows.clear();
|
this.rows.clear();
|
||||||
for (const tr of this.getTrs())
|
if (this.useVirtualList) {
|
||||||
tr.destroy();
|
this.rerender();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (const tr of this.getTrs())
|
||||||
|
tr.destroy();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
selectedRowsIds: function() {
|
selectedRowsIds: function() {
|
||||||
|
@ -986,6 +1101,12 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const TorrentsTable = new Class({
|
const TorrentsTable = new Class({
|
||||||
Extends: DynamicTable,
|
Extends: DynamicTable,
|
||||||
|
|
||||||
|
setupVirtualList: function() {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this.rowHeight = 22;
|
||||||
|
},
|
||||||
|
|
||||||
initColumns: function() {
|
initColumns: function() {
|
||||||
this.newColumn("priority", "", "#", 30, true);
|
this.newColumn("priority", "", "#", 30, true);
|
||||||
this.newColumn("state_icon", "", "QBT_TR(Status Icon)QBT_TR[CONTEXT=TransferListModel]", 30, false);
|
this.newColumn("state_icon", "", "QBT_TR(Status Icon)QBT_TR[CONTEXT=TransferListModel]", 30, false);
|
||||||
|
@ -2075,6 +2196,12 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
prevReverseSort: null,
|
prevReverseSort: null,
|
||||||
fileTree: new window.qBittorrent.FileTree.FileTree(),
|
fileTree: new window.qBittorrent.FileTree.FileTree(),
|
||||||
|
|
||||||
|
setupVirtualList: function() {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this.rowHeight = 29;
|
||||||
|
},
|
||||||
|
|
||||||
populateTable: function(root) {
|
populateTable: function(root) {
|
||||||
this.fileTree.setRoot(root);
|
this.fileTree.setRoot(root);
|
||||||
root.children.each((node) => {
|
root.children.each((node) => {
|
||||||
|
@ -2145,30 +2272,30 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
* Toggles the global checkbox and all checkboxes underneath
|
* Toggles the global checkbox and all checkboxes underneath
|
||||||
*/
|
*/
|
||||||
toggleGlobalCheckbox: function() {
|
toggleGlobalCheckbox: function() {
|
||||||
const checkbox = $("rootMultiRename_cb");
|
const checkbox = document.getElementById("rootMultiRename_cb");
|
||||||
const checkboxes = document.querySelectorAll("input.RenamingCB");
|
const checkboxes = document.querySelectorAll("input.RenamingCB");
|
||||||
|
|
||||||
for (let i = 0; i < checkboxes.length; ++i) {
|
for (let i = 0; i < checkboxes.length; ++i) {
|
||||||
const node = this.getNode(i);
|
|
||||||
|
|
||||||
if (checkbox.checked || checkbox.indeterminate) {
|
if (checkbox.checked || checkbox.indeterminate) {
|
||||||
const cb = checkboxes[i];
|
const cb = checkboxes[i];
|
||||||
cb.checked = true;
|
cb.checked = true;
|
||||||
cb.indeterminate = false;
|
cb.indeterminate = false;
|
||||||
cb.state = "checked";
|
cb.state = "checked";
|
||||||
node.checked = 0;
|
|
||||||
node.full_data.checked = node.checked;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const cb = checkboxes[i];
|
const cb = checkboxes[i];
|
||||||
cb.checked = false;
|
cb.checked = false;
|
||||||
cb.indeterminate = false;
|
cb.indeterminate = false;
|
||||||
cb.state = "unchecked";
|
cb.state = "unchecked";
|
||||||
node.checked = 1;
|
|
||||||
node.full_data.checked = node.checked;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nodes = this.fileTree.toArray();
|
||||||
|
for (const node of nodes) {
|
||||||
|
node.checked = (checkbox.checked || checkbox.indeterminate) ? 0 : 1;
|
||||||
|
node.full_data.checked = node.checked;
|
||||||
|
}
|
||||||
|
|
||||||
this.updateGlobalCheckbox();
|
this.updateGlobalCheckbox();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2176,7 +2303,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const node = this.getNode(rowId);
|
const node = this.getNode(rowId);
|
||||||
node.checked = checkState;
|
node.checked = checkState;
|
||||||
node.full_data.checked = checkState;
|
node.full_data.checked = checkState;
|
||||||
const checkbox = $(`cbRename${rowId}`);
|
const checkbox = document.getElementById(`cbRename${rowId}`);
|
||||||
checkbox.checked = node.checked === 0;
|
checkbox.checked = node.checked === 0;
|
||||||
checkbox.state = checkbox.checked ? "checked" : "unchecked";
|
checkbox.state = checkbox.checked ? "checked" : "unchecked";
|
||||||
|
|
||||||
|
@ -2184,11 +2311,11 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
this.toggleNodeTreeCheckbox(node.children[i].rowId, checkState);
|
this.toggleNodeTreeCheckbox(node.children[i].rowId, checkState);
|
||||||
},
|
},
|
||||||
|
|
||||||
updateGlobalCheckbox: () => {
|
updateGlobalCheckbox: function() {
|
||||||
const checkbox = $("rootMultiRename_cb");
|
const checkbox = document.getElementById("rootMultiRename_cb");
|
||||||
const checkboxes = document.querySelectorAll("input.RenamingCB");
|
const nodes = this.fileTree.toArray();
|
||||||
const isAllChecked = Array.prototype.every.call(checkboxes, (checkbox => checkbox.checked));
|
const isAllChecked = nodes.every((node) => node.checked === 0);
|
||||||
const isAllUnchecked = (() => Array.prototype.every.call(checkboxes, (checkbox => !checkbox.checked)));
|
const isAllUnchecked = (() => nodes.every((node) => node.checked !== 0));
|
||||||
if (isAllChecked) {
|
if (isAllChecked) {
|
||||||
checkbox.state = "checked";
|
checkbox.state = "checked";
|
||||||
checkbox.indeterminate = false;
|
checkbox.indeterminate = false;
|
||||||
|
@ -2210,82 +2337,86 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const that = this;
|
const that = this;
|
||||||
|
|
||||||
// checked
|
// checked
|
||||||
this.columns["checked"].updateTd = function(td, row) {
|
this.columns["checked"].updateTd = (td, row) => {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const value = this.getRowValue(row);
|
const node = that.getNode(id);
|
||||||
|
|
||||||
const treeImg = document.createElement("img");
|
if (td.firstElementChild === null) {
|
||||||
treeImg.src = "images/L.gif";
|
const treeImg = document.createElement("img");
|
||||||
treeImg.style.marginBottom = "-2px";
|
treeImg.src = "images/L.gif";
|
||||||
|
treeImg.style.marginBottom = "-2px";
|
||||||
|
td.append(treeImg);
|
||||||
|
}
|
||||||
|
|
||||||
const checkbox = document.createElement("input");
|
let checkbox = td.children[1];
|
||||||
checkbox.type = "checkbox";
|
if (checkbox === undefined) {
|
||||||
|
checkbox = document.createElement("input");
|
||||||
|
checkbox.type = "checkbox";
|
||||||
|
checkbox.className = "RenamingCB";
|
||||||
|
checkbox.addEventListener("click", (e) => {
|
||||||
|
const id = e.target.dataset.id;
|
||||||
|
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.indeterminate = false;
|
||||||
|
td.append(checkbox);
|
||||||
|
}
|
||||||
checkbox.id = `cbRename${id}`;
|
checkbox.id = `cbRename${id}`;
|
||||||
checkbox.setAttribute("data-id", id);
|
checkbox.dataset.id = id;
|
||||||
checkbox.className = "RenamingCB";
|
checkbox.checked = (node.checked === 0);
|
||||||
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.state = checkbox.checked ? "checked" : "unchecked";
|
||||||
checkbox.indeterminate = false;
|
|
||||||
td.replaceChildren(treeImg, checkbox);
|
|
||||||
};
|
};
|
||||||
this.columns["checked"].staticWidth = 50;
|
this.columns["checked"].staticWidth = 50;
|
||||||
|
|
||||||
// original
|
// original
|
||||||
this.columns["original"].updateTd = function(td, row) {
|
this.columns["original"].updateTd = function(td, row) {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const fileNameId = `filesTablefileName${id}`;
|
|
||||||
const node = that.getNode(id);
|
const node = that.getNode(id);
|
||||||
|
const value = this.getRowValue(row);
|
||||||
|
|
||||||
|
let dirImg = td.children[0];
|
||||||
|
if (dirImg === undefined) {
|
||||||
|
dirImg = document.createElement("img");
|
||||||
|
dirImg.src = "images/directory.svg";
|
||||||
|
dirImg.style.width = "20px";
|
||||||
|
dirImg.style.paddingRight = "5px";
|
||||||
|
dirImg.style.marginBottom = "-3px";
|
||||||
|
td.append(dirImg);
|
||||||
|
}
|
||||||
if (node.isFolder) {
|
if (node.isFolder) {
|
||||||
const value = this.getRowValue(row);
|
dirImg.style.display = "inline";
|
||||||
const dirImgId = `renameTableDirImg${id}`;
|
dirImg.style.marginLeft = `${node.depth * 20}px`;
|
||||||
if ($(dirImgId)) {
|
}
|
||||||
// just update file name
|
else {
|
||||||
$(fileNameId).textContent = value;
|
dirImg.style.display = "none";
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.textContent = value;
|
|
||||||
span.id = fileNameId;
|
|
||||||
|
|
||||||
const dirImg = document.createElement("img");
|
let span = td.children[1];
|
||||||
dirImg.src = "images/directory.svg";
|
if (span === undefined) {
|
||||||
dirImg.style.width = "20px";
|
span = document.createElement("span");
|
||||||
dirImg.style.paddingRight = "5px";
|
td.append(span);
|
||||||
dirImg.style.marginBottom = "-3px";
|
|
||||||
dirImg.style.marginLeft = `${node.depth * 20}px`;
|
|
||||||
dirImg.id = dirImgId;
|
|
||||||
td.replaceChildren(dirImg, span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else { // is file
|
|
||||||
const value = this.getRowValue(row);
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.textContent = value;
|
|
||||||
span.id = fileNameId;
|
|
||||||
span.style.marginLeft = `${(node.depth + 1) * 20}px`;
|
|
||||||
td.replaceChildren(span);
|
|
||||||
}
|
}
|
||||||
|
span.textContent = value;
|
||||||
|
span.style.marginLeft = node.isFolder ? "0" : `${(node.depth + 1) * 20}px`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// renamed
|
// renamed
|
||||||
this.columns["renamed"].updateTd = function(td, row) {
|
this.columns["renamed"].updateTd = (td, row) => {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const fileNameRenamedId = `filesTablefileRenamed${id}`;
|
const fileNameRenamedId = `filesTablefileRenamed${id}`;
|
||||||
const value = this.getRowValue(row);
|
const node = that.getNode(id);
|
||||||
|
|
||||||
const span = document.createElement("span");
|
let span = td.firstElementChild;
|
||||||
span.textContent = value;
|
if (span === null) {
|
||||||
|
span = document.createElement("span");
|
||||||
|
td.append(span);
|
||||||
|
}
|
||||||
span.id = fileNameRenamedId;
|
span.id = fileNameRenamedId;
|
||||||
td.replaceChildren(span);
|
span.textContent = node.renamed;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2431,7 +2562,15 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100)));
|
row.full_data.remaining = (row.full_data.size * (1.0 - (row.full_data.progress / 100)));
|
||||||
},
|
},
|
||||||
|
|
||||||
setupCommonEvents: () => {}
|
setupCommonEvents: function() {
|
||||||
|
const headerDiv = document.getElementById("bulkRenameFilesTableFixedHeaderDiv");
|
||||||
|
this.dynamicTableDiv.addEventListener("scroll", (e) => {
|
||||||
|
headerDiv.scrollLeft = this.dynamicTableDiv.scrollLeft;
|
||||||
|
// rerender on scroll
|
||||||
|
if (this.useVirtualList)
|
||||||
|
this.rerender();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const TorrentFilesTable = new Class({
|
const TorrentFilesTable = new Class({
|
||||||
|
@ -2445,6 +2584,108 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
prevReverseSort: null,
|
prevReverseSort: null,
|
||||||
fileTree: new window.qBittorrent.FileTree.FileTree(),
|
fileTree: new window.qBittorrent.FileTree.FileTree(),
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
this.collapseState = new Map();
|
||||||
|
},
|
||||||
|
|
||||||
|
isCollapsed: function(id) {
|
||||||
|
return this.collapseState.get(id)?.collapsed ?? false;
|
||||||
|
},
|
||||||
|
|
||||||
|
expandNode: function(id) {
|
||||||
|
const state = this.collapseState.get(id);
|
||||||
|
if (state !== undefined)
|
||||||
|
state.collapsed = false;
|
||||||
|
this._updateNodeState(id, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
collapseNode: function(id) {
|
||||||
|
const state = this.collapseState.get(id);
|
||||||
|
if (state !== undefined)
|
||||||
|
state.collapsed = true;
|
||||||
|
this._updateNodeState(id, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
expandAllNodes: function() {
|
||||||
|
for (const [key, _] of this.collapseState)
|
||||||
|
this.expandNode(key);
|
||||||
|
},
|
||||||
|
|
||||||
|
collapseAllNodes: function() {
|
||||||
|
for (const [key, state] of this.collapseState) {
|
||||||
|
// collapse all nodes except root
|
||||||
|
if (state.depth >= 1)
|
||||||
|
this.collapseNode(key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateNodeVisibility: (node, shouldHide) => {
|
||||||
|
const span = document.getElementById(`filesTablefileName${node.rowId}`);
|
||||||
|
// span won't exist if row has been filtered out
|
||||||
|
if (span === null)
|
||||||
|
return;
|
||||||
|
const tr = span.parentElement.parentElement;
|
||||||
|
tr.classList.toggle("invisible", shouldHide);
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateNodeCollapseIcon: (node, isCollapsed) => {
|
||||||
|
const span = document.getElementById(`filesTablefileName${node.rowId}`);
|
||||||
|
// span won't exist if row has been filtered out
|
||||||
|
if (span === null)
|
||||||
|
return;
|
||||||
|
const td = span.parentElement;
|
||||||
|
|
||||||
|
// rotate the collapse icon
|
||||||
|
const collapseIcon = td.firstElementChild;
|
||||||
|
collapseIcon.classList.toggle("rotate", isCollapsed);
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateNodeState: function(id, shouldCollapse) {
|
||||||
|
// collapsed rows will be filtered out when using virtual list
|
||||||
|
if (this.useVirtualList)
|
||||||
|
return;
|
||||||
|
const node = this.getNode(id);
|
||||||
|
if (!node.isFolder)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._updateNodeCollapseIcon(node, shouldCollapse);
|
||||||
|
|
||||||
|
for (const child of node.children)
|
||||||
|
this._updateNodeVisibility(child, shouldCollapse);
|
||||||
|
},
|
||||||
|
|
||||||
|
clear: function() {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this.collapseState.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
setupVirtualList: function() {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this.rowHeight = 29.5;
|
||||||
|
},
|
||||||
|
|
||||||
|
expandFolder: function(id) {
|
||||||
|
const node = this.getNode(id);
|
||||||
|
if (node.isFolder)
|
||||||
|
this.expandNode(node);
|
||||||
|
},
|
||||||
|
|
||||||
|
collapseFolder: function(id) {
|
||||||
|
const node = this.getNode(id);
|
||||||
|
if (node.isFolder)
|
||||||
|
this.collapseNode(node);
|
||||||
|
},
|
||||||
|
|
||||||
|
isAllCheckboxesChecked: function() {
|
||||||
|
return this.fileTree.toArray().every((node) => node.checked === 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
isAllCheckboxesUnchecked: function() {
|
||||||
|
return this.fileTree.toArray().every((node) => node.checked !== 1);
|
||||||
|
},
|
||||||
|
|
||||||
populateTable: function(root) {
|
populateTable: function(root) {
|
||||||
this.fileTree.setRoot(root);
|
this.fileTree.setRoot(root);
|
||||||
root.children.each((node) => {
|
root.children.each((node) => {
|
||||||
|
@ -2456,6 +2697,8 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
node.depth = depth;
|
node.depth = depth;
|
||||||
|
|
||||||
if (node.isFolder) {
|
if (node.isFolder) {
|
||||||
|
if (!this.collapseState.has(node.rowId))
|
||||||
|
this.collapseState.set(node.rowId, { depth: depth, collapsed: depth > 0 });
|
||||||
const data = {
|
const data = {
|
||||||
rowId: node.rowId,
|
rowId: node.rowId,
|
||||||
size: node.size,
|
size: node.size,
|
||||||
|
@ -2470,7 +2713,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
|
|
||||||
node.data = data;
|
node.data = data;
|
||||||
node.full_data = data;
|
node.full_data = data;
|
||||||
this.updateRowData(data);
|
this.updateRowData(data, depth);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.data.rowId = node.rowId;
|
node.data.rowId = node.rowId;
|
||||||
|
@ -2496,6 +2739,11 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
return this.rows.get(rowId);
|
return this.rows.get(rowId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getRowFileId: function(rowId) {
|
||||||
|
const row = this.rows.get(rowId);
|
||||||
|
return row?.full_data.fileId;
|
||||||
|
},
|
||||||
|
|
||||||
initColumns: function() {
|
initColumns: function() {
|
||||||
this.newColumn("checked", "", "", 50, true);
|
this.newColumn("checked", "", "", 50, true);
|
||||||
this.newColumn("name", "", "QBT_TR(Name)QBT_TR[CONTEXT=TrackerListWidget]", 300, true);
|
this.newColumn("name", "", "QBT_TR(Name)QBT_TR[CONTEXT=TrackerListWidget]", 300, true);
|
||||||
|
@ -2526,15 +2774,19 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const value = this.getRowValue(row);
|
const value = this.getRowValue(row);
|
||||||
|
|
||||||
if (window.qBittorrent.PropFiles.isDownloadCheckboxExists(id)) {
|
if (td.firstElementChild === null) {
|
||||||
window.qBittorrent.PropFiles.updateDownloadCheckbox(id, value);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const treeImg = document.createElement("img");
|
const treeImg = document.createElement("img");
|
||||||
treeImg.src = "images/L.gif";
|
treeImg.src = "images/L.gif";
|
||||||
treeImg.style.marginBottom = "-2px";
|
treeImg.style.marginBottom = "-2px";
|
||||||
td.append(treeImg, window.qBittorrent.PropFiles.createDownloadCheckbox(id, row.full_data.fileId, value));
|
td.append(treeImg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const downloadCheckbox = td.children[1];
|
||||||
|
if (downloadCheckbox === undefined)
|
||||||
|
td.append(window.qBittorrent.PropFiles.createDownloadCheckbox(id, row.full_data.fileId, value));
|
||||||
|
else
|
||||||
|
window.qBittorrent.PropFiles.updateDownloadCheckbox(downloadCheckbox, id, row.full_data.fileId, value);
|
||||||
|
|
||||||
};
|
};
|
||||||
this.columns["checked"].staticWidth = 50;
|
this.columns["checked"].staticWidth = 50;
|
||||||
|
|
||||||
|
@ -2543,46 +2795,56 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const fileNameId = `filesTablefileName${id}`;
|
const fileNameId = `filesTablefileName${id}`;
|
||||||
const node = that.getNode(id);
|
const node = that.getNode(id);
|
||||||
|
const value = this.getRowValue(row);
|
||||||
|
|
||||||
|
let collapseIcon = td.firstElementChild;
|
||||||
|
if (collapseIcon === null) {
|
||||||
|
collapseIcon = document.createElement("img");
|
||||||
|
collapseIcon.src = "images/go-down.svg";
|
||||||
|
collapseIcon.className = "filesTableCollapseIcon";
|
||||||
|
collapseIcon.addEventListener("click", (e) => {
|
||||||
|
const id = collapseIcon.dataset.id;
|
||||||
|
const node = that.getNode(id);
|
||||||
|
if (node !== null) {
|
||||||
|
if (that.isCollapsed(node.rowId))
|
||||||
|
that.expandNode(node.rowId);
|
||||||
|
else
|
||||||
|
that.collapseNode(node.rowId);
|
||||||
|
if (that.useVirtualList)
|
||||||
|
that.rerender();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
td.append(collapseIcon);
|
||||||
|
}
|
||||||
if (node.isFolder) {
|
if (node.isFolder) {
|
||||||
const value = this.getRowValue(row);
|
collapseIcon.style.marginLeft = `${node.depth * 20}px`;
|
||||||
const collapseIconId = `filesTableCollapseIcon${id}`;
|
collapseIcon.style.display = "inline";
|
||||||
const dirImgId = `filesTableDirImg${id}`;
|
collapseIcon.dataset.id = id;
|
||||||
if ($(dirImgId)) {
|
collapseIcon.classList.toggle("rotate", that.isCollapsed(node.rowId));
|
||||||
// just update file name
|
|
||||||
$(fileNameId).textContent = value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const collapseIcon = document.createElement("img");
|
|
||||||
collapseIcon.src = "images/go-down.svg";
|
|
||||||
collapseIcon.style.marginLeft = `${node.depth * 20}px`;
|
|
||||||
collapseIcon.className = "filesTableCollapseIcon";
|
|
||||||
collapseIcon.id = collapseIconId;
|
|
||||||
collapseIcon.setAttribute("data-id", id);
|
|
||||||
collapseIcon.addEventListener("click", function(e) { qBittorrent.PropFiles.collapseIconClicked(this); });
|
|
||||||
|
|
||||||
const span = document.createElement("span");
|
|
||||||
span.textContent = value;
|
|
||||||
span.id = fileNameId;
|
|
||||||
|
|
||||||
const dirImg = document.createElement("img");
|
|
||||||
dirImg.src = "images/directory.svg";
|
|
||||||
dirImg.style.width = "20px";
|
|
||||||
dirImg.style.paddingRight = "5px";
|
|
||||||
dirImg.style.marginBottom = "-3px";
|
|
||||||
dirImg.id = dirImgId;
|
|
||||||
|
|
||||||
td.replaceChildren(collapseIcon, dirImg, span);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const value = this.getRowValue(row);
|
collapseIcon.style.display = "none";
|
||||||
const span = document.createElement("span");
|
|
||||||
span.textContent = value;
|
|
||||||
span.id = fileNameId;
|
|
||||||
span.style.marginLeft = `${(node.depth + 1) * 20}px`;
|
|
||||||
td.replaceChildren(span);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dirImg = td.children[1];
|
||||||
|
if (dirImg === undefined) {
|
||||||
|
dirImg = document.createElement("img");
|
||||||
|
dirImg.src = "images/directory.svg";
|
||||||
|
dirImg.style.width = "20px";
|
||||||
|
dirImg.style.paddingRight = "5px";
|
||||||
|
dirImg.style.marginBottom = "-3px";
|
||||||
|
td.append(dirImg);
|
||||||
|
}
|
||||||
|
dirImg.style.display = node.isFolder ? "inline" : "none";
|
||||||
|
|
||||||
|
let span = td.children[2];
|
||||||
|
if (span === undefined) {
|
||||||
|
span = document.createElement("span");
|
||||||
|
td.append(span);
|
||||||
|
}
|
||||||
|
span.id = fileNameId;
|
||||||
|
span.textContent = value;
|
||||||
|
span.style.marginLeft = node.isFolder ? "0" : `${(node.depth + 1) * 20}px`;
|
||||||
};
|
};
|
||||||
this.columns["name"].calculateBuffer = (rowId) => {
|
this.columns["name"].calculateBuffer = (rowId) => {
|
||||||
const node = that.getNode(rowId);
|
const node = that.getNode(rowId);
|
||||||
|
@ -2599,10 +2861,9 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const value = Number(this.getRowValue(row));
|
const value = Number(this.getRowValue(row));
|
||||||
|
|
||||||
const progressBar = $(`pbf_${id}`);
|
const progressBar = td.firstElementChild;
|
||||||
if (progressBar === null) {
|
if (progressBar === null) {
|
||||||
td.append(new window.qBittorrent.ProgressBar.ProgressBar(value, {
|
td.append(new window.qBittorrent.ProgressBar.ProgressBar(value, {
|
||||||
id: `pbf_${id}`,
|
|
||||||
width: 80
|
width: 80
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -2617,10 +2878,11 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const id = row.rowId;
|
const id = row.rowId;
|
||||||
const value = this.getRowValue(row);
|
const value = this.getRowValue(row);
|
||||||
|
|
||||||
if (window.qBittorrent.PropFiles.isPriorityComboExists(id))
|
const priorityCombo = td.firstElementChild;
|
||||||
window.qBittorrent.PropFiles.updatePriorityCombo(id, value);
|
if (priorityCombo === null)
|
||||||
else
|
|
||||||
td.append(window.qBittorrent.PropFiles.createPriorityCombo(id, row.full_data.fileId, value));
|
td.append(window.qBittorrent.PropFiles.createPriorityCombo(id, row.full_data.fileId, value));
|
||||||
|
else
|
||||||
|
window.qBittorrent.PropFiles.updatePriorityCombo(priorityCombo, id, row.full_data.fileId, value);
|
||||||
};
|
};
|
||||||
this.columns["priority"].staticWidth = 140;
|
this.columns["priority"].staticWidth = 140;
|
||||||
|
|
||||||
|
@ -2652,7 +2914,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
},
|
},
|
||||||
|
|
||||||
_filterNodes: function(node, filterTerms, filteredRows) {
|
_filterNodes: function(node, filterTerms, filteredRows) {
|
||||||
if (node.isFolder) {
|
if (node.isFolder && (!this.useVirtualList || !this.isCollapsed(node.rowId))) {
|
||||||
const childAdded = node.children.reduce((acc, child) => {
|
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
|
// 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);
|
return (this._filterNodes(child, filterTerms, filteredRows) || acc);
|
||||||
|
@ -2689,19 +2951,11 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
const generateRowsSignature = () => {
|
const generateRowsSignature = () => {
|
||||||
const rowsData = [];
|
const rowsData = [];
|
||||||
for (const { full_data } of this.getRowValues())
|
for (const { full_data } of this.getRowValues())
|
||||||
rowsData.push(full_data);
|
rowsData.push({ ...full_data, collapsed: this.isCollapsed(full_data.rowId) });
|
||||||
return JSON.stringify(rowsData);
|
return JSON.stringify(rowsData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFilteredRows = function() {
|
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 = [];
|
const filteredRows = [];
|
||||||
this.getRoot().children.each((child) => {
|
this.getRoot().children.each((child) => {
|
||||||
this._filterNodes(child, this.filterTerms, filteredRows);
|
this._filterNodes(child, this.filterTerms, filteredRows);
|
||||||
|
@ -2758,11 +3012,11 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case "ArrowLeft":
|
case "ArrowLeft":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.qBittorrent.PropFiles.collapseFolder(this.getSelectedRowId());
|
this.collapseFolder(this.getSelectedRowId());
|
||||||
break;
|
break;
|
||||||
case "ArrowRight":
|
case "ArrowRight":
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.qBittorrent.PropFiles.expandFolder(this.getSelectedRowId());
|
this.expandFolder(this.getSelectedRowId());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3247,10 +3501,6 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
|
|
||||||
filterText: "",
|
filterText: "",
|
||||||
|
|
||||||
filteredLength: function() {
|
|
||||||
return this.tableBody.rows.length;
|
|
||||||
},
|
|
||||||
|
|
||||||
initColumns: function() {
|
initColumns: function() {
|
||||||
this.newColumn("rowId", "", "QBT_TR(ID)QBT_TR[CONTEXT=ExecutionLogWidget]", 50, true);
|
this.newColumn("rowId", "", "QBT_TR(ID)QBT_TR[CONTEXT=ExecutionLogWidget]", 50, true);
|
||||||
this.newColumn("message", "", "QBT_TR(Message)QBT_TR[CONTEXT=ExecutionLogWidget]", 350, true);
|
this.newColumn("message", "", "QBT_TR(Message)QBT_TR[CONTEXT=ExecutionLogWidget]", 350, true);
|
||||||
|
@ -3293,7 +3543,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
}
|
}
|
||||||
td.textContent = logLevel;
|
td.textContent = logLevel;
|
||||||
td.title = logLevel;
|
td.title = logLevel;
|
||||||
td.closest("tr").className = `logTableRow${addClass}`;
|
td.closest("tr").classList.add(`logTableRow${addClass}`);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3323,6 +3573,8 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
return (this.reverseSort === "0") ? res : -res;
|
return (this.reverseSort === "0") ? res : -res;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.filteredLength = filteredRows.length;
|
||||||
|
|
||||||
return filteredRows;
|
return filteredRows;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -3355,7 +3607,7 @@ window.qBittorrent.DynamicTable ??= (() => {
|
||||||
}
|
}
|
||||||
td.textContent = status;
|
td.textContent = status;
|
||||||
td.title = status;
|
td.title = status;
|
||||||
td.closest("tr").className = `logTableRow${addClass}`;
|
td.closest("tr").classList.add(`logTableRow${addClass}`);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -33,16 +33,11 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
const exports = () => {
|
const exports = () => {
|
||||||
return {
|
return {
|
||||||
normalizePriority: normalizePriority,
|
normalizePriority: normalizePriority,
|
||||||
isDownloadCheckboxExists: isDownloadCheckboxExists,
|
|
||||||
createDownloadCheckbox: createDownloadCheckbox,
|
createDownloadCheckbox: createDownloadCheckbox,
|
||||||
updateDownloadCheckbox: updateDownloadCheckbox,
|
updateDownloadCheckbox: updateDownloadCheckbox,
|
||||||
isPriorityComboExists: isPriorityComboExists,
|
|
||||||
createPriorityCombo: createPriorityCombo,
|
createPriorityCombo: createPriorityCombo,
|
||||||
updatePriorityCombo: updatePriorityCombo,
|
updatePriorityCombo: updatePriorityCombo,
|
||||||
updateData: updateData,
|
updateData: updateData,
|
||||||
collapseIconClicked: collapseIconClicked,
|
|
||||||
expandFolder: expandFolder,
|
|
||||||
collapseFolder: collapseFolder,
|
|
||||||
clear: clear
|
clear: clear
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -126,25 +121,20 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
updateGlobalCheckbox();
|
updateGlobalCheckbox();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDownloadCheckboxExists = (id) => {
|
|
||||||
return $(`cbPrio${id}`) !== null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createDownloadCheckbox = (id, fileId, checked) => {
|
const createDownloadCheckbox = (id, fileId, checked) => {
|
||||||
const checkbox = document.createElement("input");
|
const checkbox = document.createElement("input");
|
||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
checkbox.id = `cbPrio${id}`;
|
|
||||||
checkbox.setAttribute("data-id", id);
|
checkbox.setAttribute("data-id", id);
|
||||||
checkbox.setAttribute("data-file-id", fileId);
|
checkbox.setAttribute("data-file-id", fileId);
|
||||||
checkbox.className = "DownloadedCB";
|
|
||||||
checkbox.addEventListener("click", fileCheckboxClicked);
|
checkbox.addEventListener("click", fileCheckboxClicked);
|
||||||
|
|
||||||
updateCheckbox(checkbox, checked);
|
updateCheckbox(checkbox, checked);
|
||||||
return checkbox;
|
return checkbox;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDownloadCheckbox = (id, checked) => {
|
const updateDownloadCheckbox = (checkbox, id, fileId, checked) => {
|
||||||
const checkbox = $(`cbPrio${id}`);
|
checkbox.setAttribute("data-id", id);
|
||||||
|
checkbox.setAttribute("data-file-id", fileId);
|
||||||
updateCheckbox(checkbox, checked);
|
updateCheckbox(checkbox, checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,10 +152,6 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isPriorityComboExists = (id) => {
|
|
||||||
return $(`comboPrio${id}`) !== null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createPriorityCombo = (id, fileId, selectedPriority) => {
|
const createPriorityCombo = (id, fileId, selectedPriority) => {
|
||||||
const createOption = (priority, isSelected, text) => {
|
const createOption = (priority, isSelected, text) => {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
|
@ -195,8 +181,10 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
return select;
|
return select;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updatePriorityCombo = (id, selectedPriority) => {
|
const updatePriorityCombo = (combobox, id, fileId, selectedPriority) => {
|
||||||
const combobox = $(`comboPrio${id}`);
|
combobox.id = `comboPrio${id}`;
|
||||||
|
combobox.setAttribute("data-id", id);
|
||||||
|
combobox.setAttribute("data-file-id", fileId);
|
||||||
if (Number(combobox.value) !== selectedPriority)
|
if (Number(combobox.value) !== selectedPriority)
|
||||||
selectComboboxPriority(combobox, selectedPriority);
|
selectComboboxPriority(combobox, selectedPriority);
|
||||||
};
|
};
|
||||||
|
@ -258,9 +246,9 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
|
|
||||||
const updateGlobalCheckbox = () => {
|
const updateGlobalCheckbox = () => {
|
||||||
const checkbox = $("tristate_cb");
|
const checkbox = $("tristate_cb");
|
||||||
if (isAllCheckboxesChecked())
|
if (torrentFilesTable.isAllCheckboxesChecked())
|
||||||
setCheckboxChecked(checkbox);
|
setCheckboxChecked(checkbox);
|
||||||
else if (isAllCheckboxesUnchecked())
|
else if (torrentFilesTable.isAllCheckboxesUnchecked())
|
||||||
setCheckboxUnchecked(checkbox);
|
setCheckboxUnchecked(checkbox);
|
||||||
else
|
else
|
||||||
setCheckboxPartial(checkbox);
|
setCheckboxPartial(checkbox);
|
||||||
|
@ -283,10 +271,6 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
checkbox.indeterminate = true;
|
checkbox.indeterminate = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAllCheckboxesChecked = () => Array.prototype.every.call(document.querySelectorAll("input.DownloadedCB"), (checkbox => checkbox.checked));
|
|
||||||
|
|
||||||
const isAllCheckboxesUnchecked = () => Array.prototype.every.call(document.querySelectorAll("input.DownloadedCB"), (checkbox => !checkbox.checked));
|
|
||||||
|
|
||||||
const setFilePriority = (ids, fileIds, priority) => {
|
const setFilePriority = (ids, fileIds, priority) => {
|
||||||
if (current_hash === "")
|
if (current_hash === "")
|
||||||
return;
|
return;
|
||||||
|
@ -317,8 +301,6 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
if (combobox !== null)
|
if (combobox !== null)
|
||||||
selectComboboxPriority(combobox, priority);
|
selectComboboxPriority(combobox, priority);
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentFilesTable.updateTable(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let loadTorrentFilesDataTimer = -1;
|
let loadTorrentFilesDataTimer = -1;
|
||||||
|
@ -364,7 +346,7 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
else {
|
else {
|
||||||
handleNewTorrentFiles(files);
|
handleNewTorrentFiles(files);
|
||||||
if (loadedNewTorrent)
|
if (loadedNewTorrent)
|
||||||
collapseAllNodes();
|
torrentFilesTable.collapseAllNodes();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
@ -470,29 +452,6 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
torrentFilesTable.reselectRows(selectedFiles);
|
torrentFilesTable.reselectRows(selectedFiles);
|
||||||
};
|
};
|
||||||
|
|
||||||
const collapseIconClicked = (event) => {
|
|
||||||
const id = event.getAttribute("data-id");
|
|
||||||
const node = torrentFilesTable.getNode(id);
|
|
||||||
const isCollapsed = (event.parentElement.getAttribute("data-collapsed") === "true");
|
|
||||||
|
|
||||||
if (isCollapsed)
|
|
||||||
expandNode(node);
|
|
||||||
else
|
|
||||||
collapseNode(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandFolder = (id) => {
|
|
||||||
const node = torrentFilesTable.getNode(id);
|
|
||||||
if (node.isFolder)
|
|
||||||
expandNode(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
const collapseFolder = (id) => {
|
|
||||||
const node = torrentFilesTable.getNode(id);
|
|
||||||
if (node.isFolder)
|
|
||||||
collapseNode(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
const filesPriorityMenuClicked = (priority) => {
|
const filesPriorityMenuClicked = (priority) => {
|
||||||
const selectedRows = torrentFilesTable.selectedRowsIds();
|
const selectedRows = torrentFilesTable.selectedRowsIds();
|
||||||
if (selectedRows.length === 0)
|
if (selectedRows.length === 0)
|
||||||
|
@ -501,9 +460,8 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
const rowIds = [];
|
const rowIds = [];
|
||||||
const fileIds = [];
|
const fileIds = [];
|
||||||
selectedRows.forEach((rowId) => {
|
selectedRows.forEach((rowId) => {
|
||||||
const elem = $(`comboPrio${rowId}`);
|
|
||||||
rowIds.push(rowId);
|
rowIds.push(rowId);
|
||||||
fileIds.push(elem.getAttribute("data-file-id"));
|
fileIds.push(torrentFilesTable.getRowFileId(rowId));
|
||||||
});
|
});
|
||||||
|
|
||||||
const uniqueRowIds = {};
|
const uniqueRowIds = {};
|
||||||
|
@ -607,7 +565,7 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentFilesTable.setup("torrentFilesTableDiv", "torrentFilesTableFixedHeaderDiv", torrentFilesContextMenu);
|
torrentFilesTable.setup("torrentFilesTableDiv", "torrentFilesTableFixedHeaderDiv", torrentFilesContextMenu, true);
|
||||||
// inject checkbox into table header
|
// inject checkbox into table header
|
||||||
const tableHeaders = document.querySelectorAll("#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
const tableHeaders = document.querySelectorAll("#torrentFilesTableFixedHeaderDiv .dynamicTableHeader th");
|
||||||
if (tableHeaders.length > 0) {
|
if (tableHeaders.length > 0) {
|
||||||
|
@ -641,111 +599,12 @@ window.qBittorrent.PropFiles ??= (() => {
|
||||||
torrentFilesTable.updateTable();
|
torrentFilesTable.updateTable();
|
||||||
|
|
||||||
if (value.trim() === "")
|
if (value.trim() === "")
|
||||||
collapseAllNodes();
|
torrentFilesTable.collapseAllNodes();
|
||||||
else
|
else
|
||||||
expandAllNodes();
|
torrentFilesTable.expandAllNodes();
|
||||||
}, window.qBittorrent.Misc.FILTER_INPUT_DELAY);
|
}, window.qBittorrent.Misc.FILTER_INPUT_DELAY);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Show/hide a node's row
|
|
||||||
*/
|
|
||||||
const _hideNode = (node, shouldHide) => {
|
|
||||||
const span = $(`filesTablefileName${node.rowId}`);
|
|
||||||
// span won't exist if row has been filtered out
|
|
||||||
if (span === null)
|
|
||||||
return;
|
|
||||||
const rowElem = span.parentElement.parentElement;
|
|
||||||
rowElem.classList.toggle("invisible", shouldHide);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a node's collapsed state and icon
|
|
||||||
*/
|
|
||||||
const _updateNodeState = (node, isCollapsed) => {
|
|
||||||
const span = $(`filesTablefileName${node.rowId}`);
|
|
||||||
// span won't exist if row has been filtered out
|
|
||||||
if (span === null)
|
|
||||||
return;
|
|
||||||
const td = span.parentElement;
|
|
||||||
|
|
||||||
// store collapsed state
|
|
||||||
td.setAttribute("data-collapsed", isCollapsed);
|
|
||||||
|
|
||||||
// rotate the collapse icon
|
|
||||||
const collapseIcon = td.getElementsByClassName("filesTableCollapseIcon")[0];
|
|
||||||
collapseIcon.classList.toggle("rotate", isCollapsed);
|
|
||||||
};
|
|
||||||
|
|
||||||
const _isCollapsed = (node) => {
|
|
||||||
const span = $(`filesTablefileName${node.rowId}`);
|
|
||||||
if (span === null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
const td = span.parentElement;
|
|
||||||
return td.getAttribute("data-collapsed") === "true";
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandNode = (node) => {
|
|
||||||
_collapseNode(node, false, false, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const collapseNode = (node) => {
|
|
||||||
_collapseNode(node, true, false, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandAllNodes = () => {
|
|
||||||
const root = torrentFilesTable.getRoot();
|
|
||||||
root.children.each((node) => {
|
|
||||||
node.children.each((child) => {
|
|
||||||
_collapseNode(child, false, true, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const collapseAllNodes = () => {
|
|
||||||
const root = torrentFilesTable.getRoot();
|
|
||||||
root.children.each((node) => {
|
|
||||||
node.children.each((child) => {
|
|
||||||
_collapseNode(child, true, true, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collapses a folder node with the option to recursively collapse all children
|
|
||||||
* @param {FolderNode} node the node to collapse/expand
|
|
||||||
* @param {boolean} shouldCollapse true if the node should be collapsed, false if it should be expanded
|
|
||||||
* @param {boolean} applyToChildren true if the node's children should also be collapsed, recursively
|
|
||||||
* @param {boolean} isChildNode true if the current node is a child of the original node we collapsed/expanded
|
|
||||||
*/
|
|
||||||
const _collapseNode = (node, shouldCollapse, applyToChildren, isChildNode) => {
|
|
||||||
if (!node.isFolder)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const shouldExpand = !shouldCollapse;
|
|
||||||
const isNodeCollapsed = _isCollapsed(node);
|
|
||||||
const nodeInCorrectState = ((shouldCollapse && isNodeCollapsed) || (shouldExpand && !isNodeCollapsed));
|
|
||||||
const canSkipNode = (isChildNode && (!applyToChildren || nodeInCorrectState));
|
|
||||||
if (!isChildNode || applyToChildren || !canSkipNode)
|
|
||||||
_updateNodeState(node, shouldCollapse);
|
|
||||||
|
|
||||||
node.children.each((child) => {
|
|
||||||
_hideNode(child, shouldCollapse);
|
|
||||||
|
|
||||||
if (!child.isFolder)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// don't expand children that have been independently collapsed, unless applyToChildren is true
|
|
||||||
const shouldExpandChildren = (shouldExpand && applyToChildren);
|
|
||||||
const isChildCollapsed = _isCollapsed(child);
|
|
||||||
if (!shouldExpandChildren && isChildCollapsed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_collapseNode(child, shouldCollapse, applyToChildren, true);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
torrentFilesTable.clear();
|
torrentFilesTable.clear();
|
||||||
};
|
};
|
||||||
|
|
|
@ -187,7 +187,7 @@ window.qBittorrent.PropPeers ??= (() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentPeersTable.setup("torrentPeersTableDiv", "torrentPeersTableFixedHeaderDiv", torrentPeersContextMenu);
|
torrentPeersTable.setup("torrentPeersTableDiv", "torrentPeersTableFixedHeaderDiv", torrentPeersContextMenu, true);
|
||||||
|
|
||||||
return exports();
|
return exports();
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -248,7 +248,7 @@ window.qBittorrent.PropTrackers ??= (() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentTrackersTable.setup("torrentTrackersTableDiv", "torrentTrackersTableFixedHeaderDiv", torrentTrackersContextMenu);
|
torrentTrackersTable.setup("torrentTrackersTableDiv", "torrentTrackersTableFixedHeaderDiv", torrentTrackersContextMenu, true);
|
||||||
|
|
||||||
return exports();
|
return exports();
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -223,7 +223,7 @@ window.qBittorrent.PropWebseeds ??= (() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
torrentWebseedsTable.setup("torrentWebseedsTableDiv", "torrentWebseedsTableFixedHeaderDiv", torrentWebseedsContextMenu);
|
torrentWebseedsTable.setup("torrentWebseedsTableDiv", "torrentWebseedsTableFixedHeaderDiv", torrentWebseedsContextMenu, true);
|
||||||
|
|
||||||
return exports();
|
return exports();
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -223,8 +223,8 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tableInfo["main"].instance.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu);
|
tableInfo["main"].instance.setup("logMessageTableDiv", "logMessageTableFixedHeaderDiv", logTableContextMenu, true);
|
||||||
tableInfo["peer"].instance.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu);
|
tableInfo["peer"].instance.setup("logPeerTableDiv", "logPeerTableFixedHeaderDiv", logTableContextMenu, true);
|
||||||
|
|
||||||
MUI.Panels.instances.LogPanel.contentEl.style.height = "100%";
|
MUI.Panels.instances.LogPanel.contentEl.style.height = "100%";
|
||||||
|
|
||||||
|
@ -331,7 +331,7 @@
|
||||||
if (curTab === undefined)
|
if (curTab === undefined)
|
||||||
curTab = currentSelectedTab;
|
curTab = currentSelectedTab;
|
||||||
|
|
||||||
$("numFilteredLogs").textContent = tableInfo[curTab].instance.filteredLength();
|
$("numFilteredLogs").textContent = tableInfo[curTab].instance.filteredLength;
|
||||||
$("numTotalLogs").textContent = tableInfo[curTab].instance.getRowSize();
|
$("numTotalLogs").textContent = tableInfo[curTab].instance.getRowSize();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,10 @@
|
||||||
<input type="checkbox" id="displayFullURLTrackerColumn">
|
<input type="checkbox" id="displayFullURLTrackerColumn">
|
||||||
<label for="displayFullURLTrackerColumn">QBT_TR(Display full announce URL in the Tracker column)QBT_TR[CONTEXT=OptionsDialog]</label>
|
<label for="displayFullURLTrackerColumn">QBT_TR(Display full announce URL in the Tracker column)QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="formRow" style="margin-bottom: 3px;">
|
||||||
|
<input type="checkbox" id="useVirtualList">
|
||||||
|
<label for="useVirtualList">QBT_TR(Enable optimized table rendering (experimental))QBT_TR[CONTEXT=OptionsDialog]</label>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -2221,6 +2225,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
||||||
$("statusBarExternalIP").checked = pref.status_bar_external_ip;
|
$("statusBarExternalIP").checked = pref.status_bar_external_ip;
|
||||||
$("performanceWarning").checked = pref.performance_warning;
|
$("performanceWarning").checked = pref.performance_warning;
|
||||||
document.getElementById("displayFullURLTrackerColumn").checked = (LocalPreferences.get("full_url_tracker_column", "false") === "true");
|
document.getElementById("displayFullURLTrackerColumn").checked = (LocalPreferences.get("full_url_tracker_column", "false") === "true");
|
||||||
|
document.getElementById("useVirtualList").checked = (LocalPreferences.get("use_virtual_list", "false") === "true");
|
||||||
document.getElementById("hideZeroFiltersCheckbox").checked = (LocalPreferences.get("hide_zero_status_filters", "false") === "true");
|
document.getElementById("hideZeroFiltersCheckbox").checked = (LocalPreferences.get("hide_zero_status_filters", "false") === "true");
|
||||||
$("dblclickDownloadSelect").value = LocalPreferences.get("dblclick_download", "1");
|
$("dblclickDownloadSelect").value = LocalPreferences.get("dblclick_download", "1");
|
||||||
$("dblclickCompleteSelect").value = LocalPreferences.get("dblclick_complete", "1");
|
$("dblclickCompleteSelect").value = LocalPreferences.get("dblclick_complete", "1");
|
||||||
|
@ -2653,6 +2658,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
|
||||||
settings["status_bar_external_ip"] = $("statusBarExternalIP").checked;
|
settings["status_bar_external_ip"] = $("statusBarExternalIP").checked;
|
||||||
settings["performance_warning"] = $("performanceWarning").checked;
|
settings["performance_warning"] = $("performanceWarning").checked;
|
||||||
LocalPreferences.set("full_url_tracker_column", document.getElementById("displayFullURLTrackerColumn").checked.toString());
|
LocalPreferences.set("full_url_tracker_column", document.getElementById("displayFullURLTrackerColumn").checked.toString());
|
||||||
|
LocalPreferences.set("use_virtual_list", document.getElementById("useVirtualList").checked.toString());
|
||||||
LocalPreferences.set("hide_zero_status_filters", document.getElementById("hideZeroFiltersCheckbox").checked.toString());
|
LocalPreferences.set("hide_zero_status_filters", document.getElementById("hideZeroFiltersCheckbox").checked.toString());
|
||||||
LocalPreferences.set("dblclick_download", $("dblclickDownloadSelect").value);
|
LocalPreferences.set("dblclick_download", $("dblclickDownloadSelect").value);
|
||||||
LocalPreferences.set("dblclick_complete", $("dblclickCompleteSelect").value);
|
LocalPreferences.set("dblclick_complete", $("dblclickCompleteSelect").value);
|
||||||
|
|
|
@ -111,7 +111,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const setup = () => {
|
const setup = () => {
|
||||||
torrentsTable.setup("torrentsTableDiv", "torrentsTableFixedHeaderDiv", contextMenu);
|
torrentsTable.setup("torrentsTableDiv", "torrentsTableFixedHeaderDiv", contextMenu, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return exports();
|
return exports();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue