From 4b07597d54086c642c547f4750c269315e154208 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 31 May 2025 17:38:05 +0800 Subject: [PATCH] WebUI: migrate away from recursion PR #22791. --- src/webui/www/private/scripts/dynamicTable.js | 140 +++++++++++------- 1 file changed, 87 insertions(+), 53 deletions(-) diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index f1c5c2444..f15ae585a 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -2476,27 +2476,51 @@ window.qBittorrent.DynamicTable ??= (() => { }); } - #filterNodes(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); + #filterNodes(root, filterTerms) { + const ret = []; + const stack = [root]; + const visited = []; - if (childAdded) { - const row = this.getRow(node); - filteredRows.push(row); - return true; + 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(); } - if (window.qBittorrent.Misc.containsAllTerms(node.original, filterTerms)) { - const row = this.getRow(node); - filteredRows.push(row); - return true; - } - - return false; + ret.reverse(); + return ret; } setFilter(text) { @@ -2518,22 +2542,15 @@ window.qBittorrent.DynamicTable ??= (() => { return JSON.stringify(rowsData); }; - const getFilteredRows = function() { + const getFilteredRows = () => { if (this.filterTerms.length === 0) { const nodeArray = this.fileTree.toArray(); - const filteredRows = nodeArray.map((node) => { - return this.getRow(node); - }); + const filteredRows = nodeArray.map(node => this.getRow(node)); return filteredRows; } - const filteredRows = []; - this.getRoot().children.each((child) => { - this.#filterNodes(child, this.filterTerms, filteredRows); - }); - filteredRows.reverse(); - return filteredRows; - }.bind(this); + return this.#filterNodes(this.getRoot().children[0], this.filterTerms); + }; const hasRowsChanged = function(rowsString, prevRowsStringString) { const rowsChanged = (rowsString !== prevRowsStringString); @@ -2922,27 +2939,51 @@ window.qBittorrent.DynamicTable ??= (() => { }); } - #filterNodes(node, filterTerms, filteredRows) { - if (node.isFolder && (!this.useVirtualList || !this.isCollapsed(node.rowId))) { - const childAdded = node.children.toReversed().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); + #filterNodes(root, filterTerms) { + const ret = []; + const stack = [root]; + const visited = []; - if (childAdded) { - const row = this.getRow(node); - filteredRows.push(row); - return true; + while (stack.length > 0) { + const node = stack.at(-1); + + if (node.isFolder && (!this.useVirtualList || !this.isCollapsed(node.rowId))) { + 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.name, filterTerms)) { + ret.push(this.getRow(node)); + + const parent = node.root; + if (parent !== undefined) + parent.has_children_added = true; + } + } + + stack.pop(); } - if (window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) { - const row = this.getRow(node); - filteredRows.push(row); - return true; - } - - return false; + ret.reverse(); + return ret; } setFilter(text) { @@ -2964,14 +3005,7 @@ window.qBittorrent.DynamicTable ??= (() => { return JSON.stringify(rowsData); }; - const getFilteredRows = function() { - const filteredRows = []; - this.getRoot().children.each((child) => { - this.#filterNodes(child, this.filterTerms, filteredRows); - }); - filteredRows.reverse(); - return filteredRows; - }.bind(this); + const getFilteredRows = () => this.#filterNodes(this.getRoot().children[0], this.filterTerms); const hasRowsChanged = function(rowsString, prevRowsStringString) { const rowsChanged = (rowsString !== prevRowsStringString);