Reduce repeated logic between files tables

BulkRenameTorrentFilesTable now extends TorrentFilesTable, allowing for the removal of a lot of repeated logic.
This commit is contained in:
Thomas Piccirello 2025-06-12 19:47:24 -07:00
commit afa92d865f
No known key found for this signature in database

View file

@ -2344,13 +2344,20 @@ window.qBittorrent.DynamicTable ??= (() => {
prevSortedColumn = null; prevSortedColumn = null;
prevReverseSort = null; prevReverseSort = null;
fileTree = new window.qBittorrent.FileTree.FileTree(); fileTree = new window.qBittorrent.FileTree.FileTree();
supportCollapsing = true;
collapseState = new Map(); collapseState = new Map();
fileNameColumn = "name";
isCollapsed(id) { isCollapsed(id) {
if (!this.supportCollapsing)
return false;
return this.collapseState.get(id)?.collapsed ?? false; return this.collapseState.get(id)?.collapsed ?? false;
} }
expandNode(id) { expandNode(id) {
if (!this.supportCollapsing)
return;
const state = this.collapseState.get(id); const state = this.collapseState.get(id);
if (state !== undefined) if (state !== undefined)
state.collapsed = false; state.collapsed = false;
@ -2358,6 +2365,9 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
collapseNode(id) { collapseNode(id) {
if (!this.supportCollapsing)
return;
const state = this.collapseState.get(id); const state = this.collapseState.get(id);
if (state !== undefined) if (state !== undefined)
state.collapsed = true; state.collapsed = true;
@ -2365,11 +2375,17 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
expandAllNodes() { expandAllNodes() {
if (!this.supportCollapsing)
return;
for (const [key, _] of this.collapseState) for (const [key, _] of this.collapseState)
this.expandNode(key); this.expandNode(key);
} }
collapseAllNodes() { collapseAllNodes() {
if (!this.supportCollapsing)
return;
for (const [key, state] of this.collapseState) { for (const [key, state] of this.collapseState) {
// collapse all nodes except root // collapse all nodes except root
if (state.depth >= 1) if (state.depth >= 1)
@ -2433,12 +2449,18 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
expandFolder(id) { expandFolder(id) {
if (!this.supportCollapsing)
return;
const node = this.getNode(id); const node = this.getNode(id);
if (node.isFolder) if (node.isFolder)
this.expandNode(node.rowId); this.expandNode(node.rowId);
} }
collapseFolder(id) { collapseFolder(id) {
if (!this.supportCollapsing)
return;
const node = this.getNode(id); const node = this.getNode(id);
if (node.isFolder) if (node.isFolder)
this.collapseNode(node.rowId); this.collapseNode(node.rowId);
@ -2463,7 +2485,7 @@ window.qBittorrent.DynamicTable ??= (() => {
node.depth = depth; node.depth = depth;
node.parent = parent; node.parent = parent;
if (node.isFolder && !this.collapseState.has(node.rowId)) if (node.isFolder && this.supportCollapsing && !this.collapseState.has(node.rowId))
this.collapseState.set(node.rowId, { depth: depth, collapsed: false }); this.collapseState.set(node.rowId, { depth: depth, collapsed: false });
this.updateRowData({ this.updateRowData({
@ -2657,7 +2679,7 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
#sortNodesByColumn(root, column) { #sortNodesByColumn(root, column) {
const isColumnName = (column.name === "name"); const isColumnName = (column.name === this.fileNameColumn);
const isReverseSort = (this.reverseSort === "0"); const isReverseSort = (this.reverseSort === "0");
const stack = [root]; const stack = [root];
@ -2712,7 +2734,7 @@ window.qBittorrent.DynamicTable ??= (() => {
visited.pop(); visited.pop();
} }
else { else {
if (window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) { if (window.qBittorrent.Misc.containsAllTerms(node[this.fileNameColumn], filterTerms)) {
ret.push(this.getRow(node)); ret.push(this.getRow(node));
const parent = node.root; const parent = node.root;
@ -2736,18 +2758,18 @@ window.qBittorrent.DynamicTable ??= (() => {
this.filterTerms = filterTerms; this.filterTerms = filterTerms;
} }
generateRowsSignature() {
const rowsData = [];
for (const { rowId } of this.getRowValues())
rowsData.push({ ...this.getNode(rowId).serialize(), collapsed: this.isCollapsed(rowId) });
return JSON.stringify(rowsData);
}
getFilteredAndSortedRows() { getFilteredAndSortedRows() {
const root = this.getRoot(); const root = this.getRoot();
if (root === null) if (root === null)
return []; return [];
const generateRowsSignature = () => {
const rowsData = [];
for (const { rowId } of this.getRowValues())
rowsData.push({ ...this.getNode(rowId).serialize(), collapsed: this.isCollapsed(rowId) });
return JSON.stringify(rowsData);
};
const hasRowsChanged = function(rowsString, prevRowsStringString) { const hasRowsChanged = function(rowsString, prevRowsStringString) {
const rowsChanged = (rowsString !== prevRowsStringString); const rowsChanged = (rowsString !== prevRowsStringString);
const isFilterTermsChanged = this.filterTerms.reduce((acc, term, index) => { const isFilterTermsChanged = this.filterTerms.reduce((acc, term, index) => {
@ -2761,13 +2783,21 @@ window.qBittorrent.DynamicTable ??= (() => {
return (rowsChanged || isFilterChanged || isSortedColumnChanged || isReverseSortChanged); return (rowsChanged || isFilterChanged || isSortedColumnChanged || isReverseSortChanged);
}.bind(this); }.bind(this);
const rowsString = generateRowsSignature(); const rowsString = this.generateRowsSignature();
if (!hasRowsChanged(rowsString, this.prevRowsString)) if (!hasRowsChanged(rowsString, this.prevRowsString))
return this.prevFilteredRows; return this.prevFilteredRows;
// sort, then filter // sort, then filter
this.#sortNodesByColumn(root, this.columns[this.sortedColumn]); this.#sortNodesByColumn(root, this.columns[this.sortedColumn]);
const rows = this.#filterNodes(root.children[0], this.filterTerms); const rows = (() => {
if (this.filterTerms.length === 0) {
const nodeArray = this.fileTree.toArray();
const filteredRows = nodeArray.map(node => this.getRow(node));
return filteredRows;
}
return this.#filterNodes(root.children[0], this.filterTerms);
})();
this.prevFilterTerms = this.filterTerms; this.prevFilterTerms = this.filterTerms;
this.prevRowsString = rowsString; this.prevRowsString = rowsString;
@ -2798,61 +2828,18 @@ window.qBittorrent.DynamicTable ??= (() => {
} }
} }
class BulkRenameTorrentFilesTable extends DynamicTable { class BulkRenameTorrentFilesTable extends TorrentFilesTable {
filterTerms = [];
prevFilterTerms = [];
prevRowsString = null;
prevFilteredRows = [];
prevSortedColumn = null;
prevReverseSort = null;
prevCheckboxNum = null; prevCheckboxNum = null;
fileTree = new window.qBittorrent.FileTree.FileTree(); supportCollapsing = false;
fileNameColumn = "original";
setupVirtualList() { setupVirtualList() {
super.setupVirtualList(); super.setupVirtualList();
this.rowHeight = 29; this.rowHeight = 29;
} }
populateTable(root) {
this.fileTree.setRoot(root);
root.children.forEach((node) => {
this.#addNodeToTable(node, 0, root);
});
}
#addNodeToTable(node, depth, parent) {
node.depth = depth;
node.parent = parent;
this.updateRowData({
rowId: node.rowId
});
node.children.forEach((child) => {
this.#addNodeToTable(child, depth + 1, node);
});
}
getRoot() {
return this.fileTree.getRoot();
}
getNode(rowId) {
return this.fileTree.getNode(rowId);
}
getRow(node) {
const rowId = this.fileTree.getRowId(node).toString();
return this.rows.get(rowId);
}
getRowData(row, fullUpdate) {
return this.getNode(row.rowId);
}
getSelectedRows() { getSelectedRows() {
const nodes = this.fileTree.toArray(); const nodes = this.fileTree.toArray();
return nodes.filter(x => x.checked === 0); return nodes.filter(x => x.checked === 0);
} }
@ -3071,133 +3058,11 @@ window.qBittorrent.DynamicTable ??= (() => {
this.updateGlobalCheckbox(); this.updateGlobalCheckbox();
} }
#sortNodesByColumn(root, column) { generateRowsSignature() {
const isColumnOriginal = (column.name === "original"); const rowsData = [];
const isReverseSort = (this.reverseSort === "0"); for (const { full_data } of this.getRowValues())
rowsData.push(full_data);
const stack = [root]; return JSON.stringify(rowsData);
while (stack.length > 0) {
const node = stack.pop();
node.children.sort((node1, node2) => {
// list folders before files when sorting by name
if (isColumnOriginal) {
if (node1.isFolder && !node2.isFolder)
return -1;
if (!node1.isFolder && node2.isFolder)
return 1;
}
const result = column.compareRows(node1, node2);
return isReverseSort ? result : -result;
});
stack.push(...node.children);
}
}
#filterNodes(root, filterTerms) {
const ret = [];
const stack = [root];
const visited = [];
while (stack.length > 0) {
const node = stack.at(-1);
if (node.isFolder) {
const lastVisited = visited.at(-1);
if ((visited.length <= 0) || (lastVisited !== node)) {
visited.push(node);
stack.push(...node.children);
continue;
}
// has children added or itself matches
if (lastVisited.has_children_added || window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) {
ret.push(this.getRow(node));
delete node.has_children_added;
// propagate up
const parent = node.root;
if (parent !== undefined)
parent.has_children_added = true;
}
visited.pop();
}
else {
if (window.qBittorrent.Misc.containsAllTerms(node.original, filterTerms)) {
ret.push(this.getRow(node));
const parent = node.root;
if (parent !== undefined)
parent.has_children_added = true;
}
}
stack.pop();
}
ret.reverse();
return ret;
}
setFilter(text) {
const filterTerms = text.trim().toLowerCase().split(" ");
if ((filterTerms.length === 1) && (filterTerms[0] === ""))
this.filterTerms = [];
else
this.filterTerms = filterTerms;
}
getFilteredAndSortedRows() {
const root = this.getRoot();
if (root === null)
return [];
const generateRowsSignature = () => {
const rowsData = [];
for (const { full_data } of this.getRowValues())
rowsData.push(full_data);
return JSON.stringify(rowsData);
};
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
this.#sortNodesByColumn(root, this.columns[this.sortedColumn]);
const rows = (() => {
if (this.filterTerms.length === 0) {
const nodeArray = this.fileTree.toArray();
const filteredRows = nodeArray.map(node => this.getRow(node));
return filteredRows;
}
return this.#filterNodes(root.children[0], this.filterTerms);
})();
this.prevFilterTerms = this.filterTerms;
this.prevRowsString = rowsString;
this.prevFilteredRows = rows;
this.prevSortedColumn = this.sortedColumn;
this.prevReverseSort = this.reverseSort;
return rows;
} }
setupCommonEvents() { setupCommonEvents() {