From 4a4e9fab7a66a1b355bba741b516aa46bfb894b0 Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 12:11:38 +0100 Subject: [PATCH 1/7] feat: add side to open the info dropdown on --- .../view/panels/objectInfoResizePanel.js | 9 +++-- QualityControl/public/object/QCObject.js | 36 +++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js index 40e08d452..44c3b9212 100644 --- a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js +++ b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js @@ -24,9 +24,9 @@ import { h, iconResizeBoth, info } from '/js/src/index.js'; * @returns {vnode} - virtual node element */ export const objectInfoResizePanel = (model, tabObject) => { - const { name } = tabObject; + const { name, id } = tabObject; const { filterModel, router, object, services } = model; - const isSelectedOpen = object.selectedOpen; + const isSelectedOpen = object.selectedOpenName === id; const objectRemoteData = services.object.objectsLoadedMap[name]; let uri = `?page=objectView&objectId=${tabObject.id}&layoutId=${router.params.layoutId}`; Object.entries(filterModel.filterMap) @@ -42,7 +42,10 @@ export const objectInfoResizePanel = (model, tabObject) => { }, [ h('button.btn', { title: 'View details about histogram', - onclick: () => object.toggleInfoArea(name), + onclick: (e) => { + object.alignInfoArea(e); + object.toggleInfoArea(id); + }, }, info()), h( '.dropdown-menu', diff --git a/QualityControl/public/object/QCObject.js b/QualityControl/public/object/QCObject.js index 840215994..9faf80d48 100644 --- a/QualityControl/public/object/QCObject.js +++ b/QualityControl/public/object/QCObject.js @@ -100,15 +100,12 @@ export default class QCObject extends BaseViewModel { * @returns {undefined} */ toggleInfoArea(objectName) { - this.selectedOpen = !this.selectedOpen; - this.notify(); - if (objectName) { + this.selectedOpenName = this.selectedOpenName === objectName ? null : objectName; + + if (this.selectedOpenName && objectName) { if (!this.list) { this.selected = { name: objectName }; - } else if (this.selectedOpen && this.list - && (this.selected && !this.selected.lastModified - || !this.selected) - ) { + } else { this.selected = this.list.find((object) => object.name === objectName); } } @@ -653,4 +650,29 @@ export default class QCObject extends BaseViewModel { } this.loadList(); } + + /** + * Aligns a dropdown menu to the left or right side based on screen position. + * @param {Event} event - The click event from the button + * @returns {undefined} + */ + alignInfoArea(event) { + const button = event.currentTarget; + const menu = button.nextElementSibling; + + if (!menu) { + return; + } + + const rect = button.getBoundingClientRect(); + const isLeft = rect.left + rect.width / 2 < window.innerWidth / 2; + + if (isLeft) { + menu.style.left = '0.1em'; + menu.style.right = 'auto'; + } else { + menu.style.right = '0.1em'; + menu.style.left = 'auto'; + } + } } From c1972bde0fa09ac4937d531a92d34a0694ec6a5d Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 12:48:45 +0100 Subject: [PATCH 2/7] feat: switch to oncreate for the calculation of the dropdown side --- QualityControl/public/app.css | 2 +- .../view/panels/objectInfoResizePanel.js | 20 +++++++++----- QualityControl/public/object/QCObject.js | 26 +++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/QualityControl/public/app.css b/QualityControl/public/app.css index 956128933..4cea50556 100644 --- a/QualityControl/public/app.css +++ b/QualityControl/public/app.css @@ -78,7 +78,7 @@ /* overwrite jsroot styles */ .jsroot-container {} .jsroot-container pre { background-color: initial; } -.jsrootdiv:hover + .resize-element, .resize-element:hover{ display: flex !important; } +.jsrootdiv:hover + .resize-element, .resize-element:hover{ display: flex !important; visibility: visible !important; } .item-action-row { position: absolute; right: 0%; z-index: 100 } diff --git a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js index 44c3b9212..56b6b7ad3 100644 --- a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js +++ b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js @@ -35,21 +35,29 @@ export const objectInfoResizePanel = (model, tabObject) => { uri += `&${key}=${encodeURI(value)}`; }); return h('.text-right.resize-element.item-action-row.flex-row.g1', { - style: 'display: none; padding: .25rem .25rem 0rem .25rem;', + style: 'visibility: hidden; padding: .25rem .25rem 0rem .25rem;', }, [ h('.dropdown', { class: isSelectedOpen ? 'dropdown-open' : '', }, [ h('button.btn', { title: 'View details about histogram', - onclick: (e) => { - object.alignInfoArea(e); - object.toggleInfoArea(id); - }, + onclick: () => object.toggleInfoArea(id), }, info()), h( '.dropdown-menu', - { style: 'right:0.1em; width: 35em;left: auto;' }, + { + style: 'right:0.1em; width: 35em;left: auto;', + oncreate: (vnode) => { + if (object.isOnLeftSide(vnode.dom.parentElement)) { + vnode.dom.style.left = '0.1em'; + vnode.dom.style.right = 'auto'; + } else { + vnode.dom.style.right = '0.1em'; + vnode.dom.style.left = 'auto'; + } + }, + }, objectRemoteData.isSuccess() && h('.p1', qcObjectInfoPanel(objectRemoteData.payload, {}, defaultRowAttributes(model.notification))), ), diff --git a/QualityControl/public/object/QCObject.js b/QualityControl/public/object/QCObject.js index 9faf80d48..c267a6f95 100644 --- a/QualityControl/public/object/QCObject.js +++ b/QualityControl/public/object/QCObject.js @@ -652,27 +652,19 @@ export default class QCObject extends BaseViewModel { } /** - * Aligns a dropdown menu to the left or right side based on screen position. - * @param {Event} event - The click event from the button - * @returns {undefined} + * Determines whether the element is positioned on the left half of the viewport. + * This is used to decide which way a dropdown should anchor to stay within view. + * @param {HTMLElement} element - The DOM element (usually the button or container) to measure. + * @returns {boolean|undefined} Returns true if the element is on the left half of the window, + * false if it is on the right half, or undefined if no element is provided. */ - alignInfoArea(event) { - const button = event.currentTarget; - const menu = button.nextElementSibling; - - if (!menu) { + isOnLeftSide(element) { + if (!element) { return; } - const rect = button.getBoundingClientRect(); + const rect = element.getBoundingClientRect(); const isLeft = rect.left + rect.width / 2 < window.innerWidth / 2; - - if (isLeft) { - menu.style.left = '0.1em'; - menu.style.right = 'auto'; - } else { - menu.style.right = '0.1em'; - menu.style.left = 'auto'; - } + return isLeft; } } From cc5768fe560c0aea962785c8b5f9811faff43634 Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 13:08:32 +0100 Subject: [PATCH 3/7] fix: update the side whenever the grid updates --- .../public/layout/view/panels/objectInfoResizePanel.js | 2 +- QualityControl/public/object/QCObject.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js index 56b6b7ad3..3dbca8a65 100644 --- a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js +++ b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js @@ -48,7 +48,7 @@ export const objectInfoResizePanel = (model, tabObject) => { '.dropdown-menu', { style: 'right:0.1em; width: 35em;left: auto;', - oncreate: (vnode) => { + onupdate: (vnode) => { if (object.isOnLeftSide(vnode.dom.parentElement)) { vnode.dom.style.left = '0.1em'; vnode.dom.style.right = 'auto'; diff --git a/QualityControl/public/object/QCObject.js b/QualityControl/public/object/QCObject.js index c267a6f95..708b29be7 100644 --- a/QualityControl/public/object/QCObject.js +++ b/QualityControl/public/object/QCObject.js @@ -664,7 +664,7 @@ export default class QCObject extends BaseViewModel { } const rect = element.getBoundingClientRect(); - const isLeft = rect.left + rect.width / 2 < window.innerWidth / 2; + const isLeft = rect.left - rect.width < window.innerWidth / 2; return isLeft; } } From 2380da5017e42566f195a87f2f9a5dce566a05de Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 13:59:23 +0100 Subject: [PATCH 4/7] test: add test for info panel renderen to the right side --- QualityControl/test/public/pages/layout-show.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/QualityControl/test/public/pages/layout-show.test.js b/QualityControl/test/public/pages/layout-show.test.js index 1d96724a3..82e1bc782 100644 --- a/QualityControl/test/public/pages/layout-show.test.js +++ b/QualityControl/test/public/pages/layout-show.test.js @@ -168,6 +168,17 @@ export const layoutShowTests = async (url, page, timeout = 5000, testParent) => }, ); + await testParent.test( + 'should align info dropdown to the right when container is on the left', + { timeout }, + async () => { + await page.click('button.btn[title*="View details"]'); + const leftStyle = await page.evaluate(() => document.querySelector('#subcanvas .dropdown-menu').style.left); + + strictEqual(leftStyle, '0.1em'); + } + ) + await testParent.test('should have second tab to be empty (according to demo data)', { timeout }, async () => { await page.locator('#tab-1').click(); await delay(50); From aee8e0aeca3ece1e873a58e1419b281d8f99f1a4 Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 14:05:56 +0100 Subject: [PATCH 5/7] style: add missing semicolon --- QualityControl/test/public/pages/layout-show.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QualityControl/test/public/pages/layout-show.test.js b/QualityControl/test/public/pages/layout-show.test.js index 82e1bc782..301b153b7 100644 --- a/QualityControl/test/public/pages/layout-show.test.js +++ b/QualityControl/test/public/pages/layout-show.test.js @@ -177,7 +177,7 @@ export const layoutShowTests = async (url, page, timeout = 5000, testParent) => strictEqual(leftStyle, '0.1em'); } - ) + ); await testParent.test('should have second tab to be empty (according to demo data)', { timeout }, async () => { await page.locator('#tab-1').click(); From 30b6b60fafb604e760199b7f59c1cb6712c4b82c Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 21:27:58 +0100 Subject: [PATCH 6/7] feat: add util function and use name for opening the info panels --- QualityControl/public/common/utils.js | 19 +++++++++++++++++++ .../view/panels/objectInfoResizePanel.js | 9 +++++---- QualityControl/public/object/QCObject.js | 17 ----------------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/QualityControl/public/common/utils.js b/QualityControl/public/common/utils.js index 14891f933..e6cf25062 100644 --- a/QualityControl/public/common/utils.js +++ b/QualityControl/public/common/utils.js @@ -171,3 +171,22 @@ export const camelToTitleCase = (text) => { const titleCase = spaced.charAt(0).toUpperCase() + spaced.slice(1); return titleCase; }; + + + +/** + * Determines whether the element is positioned on the left half of the viewport. + * This is used to decide which way a dropdown should anchor to stay within view. + * @param {HTMLElement} element - The DOM element (usually the button or container) to measure. + * @returns {boolean|undefined} Returns true if the element is on the left half of the window, + * false if it is on the right half, or undefined if no element is provided. + */ +export const isOnLeftSideOfViewport = (element) => { + if (!element) { + return; + } + + const rect = element.getBoundingClientRect(); + const isLeft = rect.left - rect.width < window.innerWidth / 2; + return isLeft; +} diff --git a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js index 3dbca8a65..1d2fb90c0 100644 --- a/QualityControl/public/layout/view/panels/objectInfoResizePanel.js +++ b/QualityControl/public/layout/view/panels/objectInfoResizePanel.js @@ -13,6 +13,7 @@ */ import { downloadButton } from '../../../common/downloadButton.js'; +import { isOnLeftSideOfViewport } from '../../../common/utils.js'; import { defaultRowAttributes, qcObjectInfoPanel } from './../../../common/object/objectInfoCard.js'; import { h, iconResizeBoth, info } from '/js/src/index.js'; @@ -24,9 +25,9 @@ import { h, iconResizeBoth, info } from '/js/src/index.js'; * @returns {vnode} - virtual node element */ export const objectInfoResizePanel = (model, tabObject) => { - const { name, id } = tabObject; + const { name } = tabObject; const { filterModel, router, object, services } = model; - const isSelectedOpen = object.selectedOpenName === id; + const isSelectedOpen = object.selectedOpenName === name; const objectRemoteData = services.object.objectsLoadedMap[name]; let uri = `?page=objectView&objectId=${tabObject.id}&layoutId=${router.params.layoutId}`; Object.entries(filterModel.filterMap) @@ -42,14 +43,14 @@ export const objectInfoResizePanel = (model, tabObject) => { }, [ h('button.btn', { title: 'View details about histogram', - onclick: () => object.toggleInfoArea(id), + onclick: () => object.toggleInfoArea(name), }, info()), h( '.dropdown-menu', { style: 'right:0.1em; width: 35em;left: auto;', onupdate: (vnode) => { - if (object.isOnLeftSide(vnode.dom.parentElement)) { + if (isOnLeftSideOfViewport(vnode.dom.parentElement)) { vnode.dom.style.left = '0.1em'; vnode.dom.style.right = 'auto'; } else { diff --git a/QualityControl/public/object/QCObject.js b/QualityControl/public/object/QCObject.js index 708b29be7..18c41c554 100644 --- a/QualityControl/public/object/QCObject.js +++ b/QualityControl/public/object/QCObject.js @@ -650,21 +650,4 @@ export default class QCObject extends BaseViewModel { } this.loadList(); } - - /** - * Determines whether the element is positioned on the left half of the viewport. - * This is used to decide which way a dropdown should anchor to stay within view. - * @param {HTMLElement} element - The DOM element (usually the button or container) to measure. - * @returns {boolean|undefined} Returns true if the element is on the left half of the window, - * false if it is on the right half, or undefined if no element is provided. - */ - isOnLeftSide(element) { - if (!element) { - return; - } - - const rect = element.getBoundingClientRect(); - const isLeft = rect.left - rect.width < window.innerWidth / 2; - return isLeft; - } } From bf703b6abd45f4327629527c26fc2cc901fe3431 Mon Sep 17 00:00:00 2001 From: Alex Janson Date: Thu, 18 Dec 2025 21:29:17 +0100 Subject: [PATCH 7/7] style: fix linting errors --- QualityControl/public/common/utils.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/QualityControl/public/common/utils.js b/QualityControl/public/common/utils.js index e6cf25062..f457ca14d 100644 --- a/QualityControl/public/common/utils.js +++ b/QualityControl/public/common/utils.js @@ -172,8 +172,6 @@ export const camelToTitleCase = (text) => { return titleCase; }; - - /** * Determines whether the element is positioned on the left half of the viewport. * This is used to decide which way a dropdown should anchor to stay within view. @@ -189,4 +187,4 @@ export const isOnLeftSideOfViewport = (element) => { const rect = element.getBoundingClientRect(); const isLeft = rect.left - rect.width < window.innerWidth / 2; return isLeft; -} +};