WebUI: Improve table performance

PR #23752.
This commit is contained in:
Tom Piccirello 2026-02-01 01:46:33 -08:00 committed by GitHub
parent 752fb60a2a
commit 1821cc4dcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 54 deletions

View File

@ -83,7 +83,7 @@ window.qBittorrent.DynamicTable ??= (() => {
this.rowHeight = (clientData.get("display_density") === "compact") ? 22 : 26;
this.rows = new Map();
this.cachedElements = [];
this.selectedRows = [];
this.selectedRows = new Set();
this.columns = [];
this.contextMenu = contextMenu;
this.sortedColumn = localPreferences.get(`sorted_column_${this.dynamicTableDivId}`, 0);
@ -144,7 +144,7 @@ window.qBittorrent.DynamicTable ??= (() => {
else
this.selectRow(tr.rowId);
}
else if (e.shiftKey && (this.selectedRows.length === 1)) {
else if (e.shiftKey && (this.selectedRows.size === 1)) {
// Shift key was pressed
this.selectRows(this.getSelectedRowId(), tr.rowId);
}
@ -717,13 +717,13 @@ window.qBittorrent.DynamicTable ??= (() => {
}
getSelectedRowId() {
if (this.selectedRows.length > 0)
return this.selectedRows[0];
if (this.selectedRows.size > 0)
return this.selectedRows.values().next().value;
return "";
}
isRowSelected(rowId) {
return this.selectedRows.contains(rowId);
return this.selectedRows.has(rowId);
}
setupAltRow() {
@ -735,22 +735,22 @@ window.qBittorrent.DynamicTable ??= (() => {
selectAll() {
this.deselectAll();
for (const row of this.getFilteredAndSortedRows())
this.selectedRows.push(row.rowId);
this.selectedRows.add(row.rowId);
this.setRowClass();
}
deselectAll() {
this.selectedRows.empty();
this.selectedRows.clear();
}
selectRow(rowId) {
this.selectedRows.push(rowId);
this.selectedRows.add(rowId);
this.setRowClass();
this.onSelectedRowChanged();
}
deselectRow(rowId) {
this.selectedRows.erase(rowId);
this.selectedRows.delete(rowId);
this.setRowClass();
this.onSelectedRowChanged();
}
@ -766,10 +766,10 @@ window.qBittorrent.DynamicTable ??= (() => {
for (const row of this.getFilteredAndSortedRows()) {
if ((row.rowId === rowId1) || (row.rowId === rowId2)) {
select = !select;
this.selectedRows.push(row.rowId);
this.selectedRows.add(row.rowId);
}
else if (select) {
this.selectedRows.push(row.rowId);
this.selectedRows.add(row.rowId);
}
}
this.setRowClass();
@ -778,7 +778,7 @@ window.qBittorrent.DynamicTable ??= (() => {
reselectRows(rowIds) {
this.deselectAll();
this.selectedRows = rowIds.slice();
this.selectedRows = new Set(rowIds);
this.setRowClass();
}
@ -813,6 +813,24 @@ window.qBittorrent.DynamicTable ??= (() => {
}
}
/**
* Replaces all rows with the provided set.
* More efficient than calling clear() + updateRowData() because existing <tr>
* elements are reused.
* @param {Object[]} rows - Array of row objects, each with a rowId property
*/
setRows(rows) {
const newRowIds = new Set();
for (const row of rows) {
this.updateRowData(row);
newRowIds.add(`${row.rowId}`);
}
for (const rowId of this.rows.keys()) {
if (!newRowIds.has(rowId))
this.rows.delete(rowId);
}
}
getTrs() {
return this.tableBody.querySelectorAll("tr");
}
@ -849,11 +867,9 @@ window.qBittorrent.DynamicTable ??= (() => {
updateTable(fullUpdate = false) {
const rows = this.getFilteredAndSortedRows();
for (let i = 0; i < this.selectedRows.length; ++i) {
if (!(this.selectedRows[i] in rows)) {
this.selectedRows.splice(i, 1);
--i;
}
for (const rowId of this.selectedRows) {
if (!(rowId in rows))
this.selectedRows.delete(rowId);
}
if (this.useVirtualList) {
@ -1011,7 +1027,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}
removeRow(rowId) {
this.selectedRows.erase(rowId);
this.selectedRows.delete(rowId);
this.rows.delete(rowId);
if (this.useVirtualList) {
this.rerender();
@ -1035,7 +1051,7 @@ window.qBittorrent.DynamicTable ??= (() => {
}
selectedRowsIds() {
return this.selectedRows.slice();
return [...this.selectedRows];
}
getRowIds() {
@ -1058,18 +1074,11 @@ window.qBittorrent.DynamicTable ??= (() => {
const visibleRows = Array.prototype.filter.call(this.getTrs(), (tr => !tr.classList.contains("invisible") && (tr.style.display !== "none")));
const selectedRowId = this.getSelectedRowId();
let selectedIndex = -1;
for (const [i, row] of visibleRows.entries()) {
if (row.getAttribute("data-row-id") === selectedRowId) {
selectedIndex = i;
break;
}
}
const selectedIndex = visibleRows.findIndex(row => (row.getAttribute("data-row-id") === selectedRowId));
const isLastRowSelected = (selectedIndex >= (visibleRows.length - 1));
if (!isLastRowSelected) {
this.deselectAll();
const newRow = visibleRows[selectedIndex + 1];
this.selectRow(newRow.getAttribute("data-row-id"));
}
@ -1079,18 +1088,11 @@ window.qBittorrent.DynamicTable ??= (() => {
const visibleRows = Array.prototype.filter.call(this.getTrs(), (tr => !tr.classList.contains("invisible") && (tr.style.display !== "none")));
const selectedRowId = this.getSelectedRowId();
let selectedIndex = -1;
for (const [i, row] of visibleRows.entries()) {
if (row.getAttribute("data-row-id") === selectedRowId) {
selectedIndex = i;
break;
}
}
const selectedIndex = visibleRows.findIndex(row => (row.getAttribute("data-row-id") === selectedRowId));
const isFirstRowSelected = selectedIndex <= 0;
if (!isFirstRowSelected) {
this.deselectAll();
const newRow = visibleRows[selectedIndex - 1];
this.selectRow(newRow.getAttribute("data-row-id"));
}

View File

@ -99,11 +99,10 @@ window.qBittorrent.PropTrackers ??= (() => {
const trackers = await response.json();
if (trackers) {
torrentTrackersTable.clear();
const notApplicable = "QBT_TR(N/A)QBT_TR[CONTEXT=TrackerListWidget]";
const rows = [];
trackers.each((tracker) => {
const row = {
rows.push({
rowId: tracker.url,
tier: (tracker.tier >= 0) ? tracker.tier : "",
btVersion: "",
@ -119,13 +118,11 @@ window.qBittorrent.PropTrackers ??= (() => {
_isTracker: true,
_hasEndpoints: tracker.endpoints && (tracker.endpoints.length > 0),
_sortable: !tracker.url.startsWith("** [")
};
torrentTrackersTable.updateRowData(row);
});
if (tracker.endpoints !== undefined) {
for (const endpoint of tracker.endpoints) {
const row = {
rows.push({
rowId: `endpoint|${tracker.url}|${endpoint.name}|${endpoint.bt_version}`,
tier: "",
btVersion: `v${endpoint.bt_version}`,
@ -141,12 +138,12 @@ window.qBittorrent.PropTrackers ??= (() => {
_isTracker: false,
_tracker: tracker.url,
_sortable: true,
};
torrentTrackersTable.updateRowData(row);
});
}
}
});
torrentTrackersTable.setRows(rows);
torrentTrackersTable.updateTable(false);
if (selectedTrackers.length > 0)

View File

@ -74,18 +74,10 @@ window.qBittorrent.PropWebseeds ??= (() => {
return;
const selectedWebseeds = torrentWebseedsTable.selectedRowsIds();
torrentWebseedsTable.clear();
const webseeds = await response.json();
if (webseeds) {
// Update WebSeeds data
webseeds.each((webseed) => {
torrentWebseedsTable.updateRowData({
rowId: webseed.url,
url: webseed.url,
});
});
}
const rows = (webseeds instanceof Array) ? webseeds.map(ws => ({ rowId: ws.url, url: ws.url })) : [];
torrentWebseedsTable.setRows(rows);
torrentWebseedsTable.updateTable(false);