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/common/utils.js b/QualityControl/public/common/utils.js index 14891f933..f457ca14d 100644 --- a/QualityControl/public/common/utils.js +++ b/QualityControl/public/common/utils.js @@ -171,3 +171,20 @@ 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 40e08d452..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'; @@ -26,7 +27,7 @@ import { h, iconResizeBoth, info } from '/js/src/index.js'; export const objectInfoResizePanel = (model, tabObject) => { const { name } = tabObject; const { filterModel, router, object, services } = model; - const isSelectedOpen = object.selectedOpen; + 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) @@ -35,7 +36,7 @@ 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' : '', @@ -46,7 +47,18 @@ export const objectInfoResizePanel = (model, tabObject) => { }, info()), h( '.dropdown-menu', - { style: 'right:0.1em; width: 35em;left: auto;' }, + { + style: 'right:0.1em; width: 35em;left: auto;', + onupdate: (vnode) => { + if (isOnLeftSideOfViewport(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 840215994..18c41c554 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); } } diff --git a/QualityControl/test/public/pages/layout-show.test.js b/QualityControl/test/public/pages/layout-show.test.js index 1d96724a3..301b153b7 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);