From cd0f8afd30d62df6d62d81727072078894e2ca40 Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:21:16 +0100 Subject: [PATCH 1/9] Fix tree table rendering by flattening recursive rows with flatMap --- .../public/object/objectTreePage.js | 111 +++++++++++------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index f53b09d38..10df6ab2d 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -180,7 +180,7 @@ const treeRows = (model) => !model.object.tree ? model.object.tree.children.length === 0 ? h('.w-100.text-center', 'No objects found') - : model.object.tree.children.map((children) => treeRow(model, children, 0)); + : model.object.tree.children.map((children) => treeRow(model, children)); /** * Shows a line of object represented by parent node `tree`, also shows @@ -192,49 +192,68 @@ const treeRows = (model) => !model.object.tree ? * @param {number} level - used for indentation within recursive call of treeRow * @returns {vnode} - virtual node element */ -function treeRow(model, tree, level) { - const padding = `${level}em`; - const levelDeeper = level + 1; - const children = tree.open ? tree.children.map((children) => treeRow(model, children, levelDeeper)) : []; - const path = tree.name; - const className = tree.object && tree.object === model.object.selected ? 'table-primary' : ''; +function treeRow(model, tree, level = 0) { + const { pathString, open, children, object, name } = tree; - if (model.object.searchInput) { - return []; - } else { - if (tree.object && tree.children.length === 0) { - return [leafRow(path, () => model.object.select(tree.object), className, padding, tree.name)]; - } else if (tree.object && tree.children.length > 0) { - return [ - leafRow(path, () => model.object.select(tree.object), className, padding, tree.name), - branchRow(path, tree, padding), - children, - ]; - } - return [ - branchRow(path, tree, padding), - children, - ]; + const childRow = open + ? children.flatMap((children) => treeRow(model, children, level + 1)) + : []; + + const rows = []; + + if (object) { + // Add a leaf row (final element; cannot be expanded further) + const className = object && object === model.object.selected ? 'table-primary' : ''; + const leaf = leafRow( + pathString, + name, + () => model.object.select(object), + className, + { + paddingLeft: `${level}em`, + }, + ); + rows.push(leaf); + } + if (children.length > 0) { + // Add a branch row (expandable / collapsible element) + const branch = branchRow( + pathString, + open, + name, + () => tree.toggle(), + { + paddingLeft: `${level}em`, + }, + ); + rows.push(branch); } + + return [...rows, ...childRow]; } /** * Creates a row containing specific visuals for leaf object and on selection * it will plot the object with JSRoot - * @param {string} path - full name of the object - * @param {Action} selectItem - action for plotting the object - * @param {string} className - name of the row class - * @param {number} padding - space needed to be displayed so that leaf is within its parent - * @param {string} leafName - name of the object + * @param {string} key - An unique identifier for this branch row element (table row) + * @param {string} leafName - The name of this tree object element + * @param {() => void} onClick - The action (callback) to perform upon clicking this branch row element (table row) + * @param {string} className - Optional CSS class name(s) for the outer branch row element (table row) + * @param {object} styling - Optional CSS styling for the inner branch row element (table data) * @returns {vnode} - virtual node element */ -const leafRow = (path, selectItem, className, padding, leafName) => +const leafRow = (key, leafName, onClick, className = '', styling = {}) => h('tr.object-selectable', { - key: path, title: path, onclick: selectItem, class: className, id: path, + key: key, + id: key, + title: leafName, + onclick: onClick, + class: className, }, [ - h('td.highlight', [ - h('span', { style: { paddingLeft: padding } }, iconBarChart()), - ' ', + h('td.highlight.flex-row.items-center.g1', { + style: styling, + }, [ + iconBarChart(), leafName, ]), ]); @@ -242,16 +261,24 @@ const leafRow = (path, selectItem, className, padding, leafName) => /** * Creates a row containing specific visuals for branch object and on selection * it will open its children - * @param {string} path - full name of the object - * @param {ObjectTree} tree - current selected tree - * @param {number} padding - space needed to be displayed so that branch is within its parent + * @param {string} key - An unique identifier for this branch row element (table row) + * @param {boolean} isTreeOpen - Whether the current tree object is open (expanded) + * @param {string} branchName - The name of this tree object element + * @param {() => void} onClick - The action (callback) to perform upon clicking this branch row element (table row) + * @param {object} styling - Optional CSS styling for the inner branch row element (table data) * @returns {vnode} - virtual node element */ -const branchRow = (path, tree, padding) => - h('tr.object-selectable', { key: path, title: path, onclick: () => tree.toggle() }, [ - h('td.highlight', [ - h('span', { style: { paddingLeft: padding } }, tree.open ? iconCaretBottom() : iconCaretRight()), - ' ', - tree.name, +const branchRow = (key, isTreeOpen, branchName, onClick, styling = {}) => + h('tr.object-selectable', { + key: key, + id: key, + title: branchName, + onclick: onClick, + }, [ + h('td.highlight.flex-row.items-center.g1', { + style: styling, + }, [ + isTreeOpen ? iconCaretBottom() : iconCaretRight(), + branchName, ]), ]); From c973d8fd65d9f7443fad29094e4769abbbc67b2c Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:22:09 +0100 Subject: [PATCH 2/9] Optimize tree open/close operations by only calling notify() at the end --- .../public/object/ObjectTree.class.js | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/QualityControl/public/object/ObjectTree.class.js b/QualityControl/public/object/ObjectTree.class.js index 4135b5e9b..d2d474432 100644 --- a/QualityControl/public/object/ObjectTree.class.js +++ b/QualityControl/public/object/ObjectTree.class.js @@ -65,24 +65,36 @@ export default class ObjectTree extends Observable { /** * Open all nodes of the tree - * @returns {undefined} */ openAll() { - this.open = true; - this.children.forEach((child) => child.openAll()); + this._openAllRecursive(); this.notify(); } + /** + * Recursively open all nodes without notifying. + */ + _openAllRecursive() { + this.open = true; + this.children.forEach((child) => child._openAllRecursive()); + } + /** * Close all nodes of the tree - * @returns {undefined} */ closeAll() { - this.open = false; - this.children.forEach((child) => child.closeAll()); + this._closeAllRecursive(); this.notify(); } + /** + * Recursively close all nodes without notifying. + */ + _closeAllRecursive() { + this.open = false; + this.children.forEach((child) => child._closeAllRecursive()); + } + /** * Add recursively an object inside a tree * @param {object} object - The object to be inserted, property name must exist From c7f8ca1e75a675511ec349e28a4544d5c97b2189 Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:33:46 +0100 Subject: [PATCH 3/9] Fix useless conditional Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- QualityControl/public/object/objectTreePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index 10df6ab2d..77f04fd5c 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -203,7 +203,7 @@ function treeRow(model, tree, level = 0) { if (object) { // Add a leaf row (final element; cannot be expanded further) - const className = object && object === model.object.selected ? 'table-primary' : ''; + const className = object === model.object.selected ? 'table-primary' : ''; const leaf = leafRow( pathString, name, From 1e2518e1f42d85d85531a2ee460b50932dfd58ed Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:23:00 +0100 Subject: [PATCH 4/9] Remove the `toggleAll` and `openAll` and `_openAllRecursive` methods --- .../public/object/ObjectTree.class.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/QualityControl/public/object/ObjectTree.class.js b/QualityControl/public/object/ObjectTree.class.js index d2d474432..a59f46b78 100644 --- a/QualityControl/public/object/ObjectTree.class.js +++ b/QualityControl/public/object/ObjectTree.class.js @@ -55,30 +55,6 @@ export default class ObjectTree extends Observable { this.notify(); } - /** - * Open all or close all nodes of the tree - * @returns {undefined} - */ - toggleAll() { - this.open ? this.closeAll() : this.openAll(); - } - - /** - * Open all nodes of the tree - */ - openAll() { - this._openAllRecursive(); - this.notify(); - } - - /** - * Recursively open all nodes without notifying. - */ - _openAllRecursive() { - this.open = true; - this.children.forEach((child) => child._openAllRecursive()); - } - /** * Close all nodes of the tree */ From 5b4b606bb94fe67b732ee209b1a229e47cba6b6e Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:31:22 +0100 Subject: [PATCH 5/9] Refactor `branchRow` and `leafRow` into one `treeRowElement` function --- .../public/object/objectTreePage.js | 56 ++++++------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index 77f04fd5c..e87d57a6c 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -190,7 +190,7 @@ const treeRows = (model) => !model.object.tree ? * @param {Model} model - root model of the application * @param {ObjectTree} tree - data-structure containing an object per node * @param {number} level - used for indentation within recursive call of treeRow - * @returns {vnode} - virtual node element + * @returns {vnode[]} - virtual node element */ function treeRow(model, tree, level = 0) { const { pathString, open, children, object, name } = tree; @@ -204,10 +204,11 @@ function treeRow(model, tree, level = 0) { if (object) { // Add a leaf row (final element; cannot be expanded further) const className = object === model.object.selected ? 'table-primary' : ''; - const leaf = leafRow( + const leaf = treeRowElement( pathString, name, () => model.object.select(object), + iconBarChart, className, { paddingLeft: `${level}em`, @@ -217,11 +218,12 @@ function treeRow(model, tree, level = 0) { } if (children.length > 0) { // Add a branch row (expandable / collapsible element) - const branch = branchRow( + const branch = treeRowElement( pathString, - open, name, () => tree.toggle(), + open ? iconCaretBottom : iconCaretRight, + '', { paddingLeft: `${level}em`, }, @@ -233,52 +235,28 @@ function treeRow(model, tree, level = 0) { } /** - * Creates a row containing specific visuals for leaf object and on selection - * it will plot the object with JSRoot + * Creates a row containing specific visuals for either a branch or a leaf object + * and on click it will expand/collapse the branch or plot the leaf object with JSRoot * @param {string} key - An unique identifier for this branch row element (table row) - * @param {string} leafName - The name of this tree object element - * @param {() => void} onClick - The action (callback) to perform upon clicking this branch row element (table row) + * @param {string} name - The name of this tree object element + * @param {() => void} onclick - The action (callback) to perform upon clicking this branch row element (table row) + * @param {() => vnode} icon - Icon renderer for the row * @param {string} className - Optional CSS class name(s) for the outer branch row element (table row) * @param {object} styling - Optional CSS styling for the inner branch row element (table data) * @returns {vnode} - virtual node element */ -const leafRow = (key, leafName, onClick, className = '', styling = {}) => +const treeRowElement = (key, name, onclick, icon, className = '', styling = {}) => h('tr.object-selectable', { - key: key, + key, id: key, - title: leafName, - onclick: onClick, + title: name, + onclick, class: className, }, [ h('td.highlight.flex-row.items-center.g1', { style: styling, }, [ - iconBarChart(), - leafName, - ]), - ]); - -/** - * Creates a row containing specific visuals for branch object and on selection - * it will open its children - * @param {string} key - An unique identifier for this branch row element (table row) - * @param {boolean} isTreeOpen - Whether the current tree object is open (expanded) - * @param {string} branchName - The name of this tree object element - * @param {() => void} onClick - The action (callback) to perform upon clicking this branch row element (table row) - * @param {object} styling - Optional CSS styling for the inner branch row element (table data) - * @returns {vnode} - virtual node element - */ -const branchRow = (key, isTreeOpen, branchName, onClick, styling = {}) => - h('tr.object-selectable', { - key: key, - id: key, - title: branchName, - onclick: onClick, - }, [ - h('td.highlight.flex-row.items-center.g1', { - style: styling, - }, [ - isTreeOpen ? iconCaretBottom() : iconCaretRight(), - branchName, + icon(), + name, ]), ]); From b5634c964b6f3165daec013bd3e48f788a926191 Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:38:04 +0100 Subject: [PATCH 6/9] Add `.25rem` left padding (`--space-xs`) to each tree row element --- QualityControl/public/object/objectTreePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index e87d57a6c..e754aa12f 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -211,7 +211,7 @@ function treeRow(model, tree, level = 0) { iconBarChart, className, { - paddingLeft: `${level}em`, + paddingLeft: `${level + 0.25}em`, }, ); rows.push(leaf); @@ -225,7 +225,7 @@ function treeRow(model, tree, level = 0) { open ? iconCaretBottom : iconCaretRight, '', { - paddingLeft: `${level}em`, + paddingLeft: `${level + 0.25}em`, }, ); rows.push(branch); From ae946727beec78453f5c0b6e0669bb9acf7a4669 Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:39:29 +0100 Subject: [PATCH 7/9] Refactor of treeRowElement params --- QualityControl/public/object/objectTreePage.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index e754aa12f..c6fd69371 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -238,25 +238,25 @@ function treeRow(model, tree, level = 0) { * Creates a row containing specific visuals for either a branch or a leaf object * and on click it will expand/collapse the branch or plot the leaf object with JSRoot * @param {string} key - An unique identifier for this branch row element (table row) - * @param {string} name - The name of this tree object element + * @param {string} title - The name of this tree object element * @param {() => void} onclick - The action (callback) to perform upon clicking this branch row element (table row) * @param {() => vnode} icon - Icon renderer for the row * @param {string} className - Optional CSS class name(s) for the outer branch row element (table row) - * @param {object} styling - Optional CSS styling for the inner branch row element (table data) + * @param {object} style - Optional CSS styling for the inner branch row element (table data) * @returns {vnode} - virtual node element */ -const treeRowElement = (key, name, onclick, icon, className = '', styling = {}) => +const treeRowElement = (key, title, onclick, icon, className = '', style = {}) => h('tr.object-selectable', { key, id: key, - title: name, + title, onclick, class: className, }, [ h('td.highlight.flex-row.items-center.g1', { - style: styling, + style, }, [ icon(), - name, + title, ]), ]); From c4bed33a5474a3a2b241e17b4409d4c69de0d5b0 Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:40:32 +0100 Subject: [PATCH 8/9] Change default padding from `0.25em` to `0.3em` --- QualityControl/public/object/objectTreePage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index c6fd69371..178e1dd6a 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -211,7 +211,7 @@ function treeRow(model, tree, level = 0) { iconBarChart, className, { - paddingLeft: `${level + 0.25}em`, + paddingLeft: `${level + 0.3}em`, }, ); rows.push(leaf); @@ -225,7 +225,7 @@ function treeRow(model, tree, level = 0) { open ? iconCaretBottom : iconCaretRight, '', { - paddingLeft: `${level + 0.25}em`, + paddingLeft: `${level + 0.3}em`, }, ); rows.push(branch); From f72ec637999346956d66361d37a6ded6c561b60f Mon Sep 17 00:00:00 2001 From: hehoon <100522372+hehoon@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:44:22 +0100 Subject: [PATCH 9/9] Use the short version `{ style }` --- QualityControl/public/object/objectTreePage.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/QualityControl/public/object/objectTreePage.js b/QualityControl/public/object/objectTreePage.js index 178e1dd6a..e3596cfe5 100644 --- a/QualityControl/public/object/objectTreePage.js +++ b/QualityControl/public/object/objectTreePage.js @@ -253,9 +253,7 @@ const treeRowElement = (key, title, onclick, icon, className = '', style = {}) = onclick, class: className, }, [ - h('td.highlight.flex-row.items-center.g1', { - style, - }, [ + h('td.highlight.flex-row.items-center.g1', { style }, [ icon(), title, ]),