diff --git a/lib/public/app.css b/lib/public/app.css index fab7fc250a..74fc0facd2 100644 --- a/lib/public/app.css +++ b/lib/public/app.css @@ -176,7 +176,7 @@ th.text-center, td.text-center { background: var(--color-gray-light); position: sticky; left: 0; - z-index: 2; + z-index: 3; } .freeze-first-column td:first-child { @@ -207,6 +207,25 @@ th.text-center, td.text-center { } +.sticky-table-wrapper { + flex: 1 1 auto; + overflow-y: auto; + height: 100%; +} + +.sticky-table-wrapper thead { + position: sticky; + top: 0; + z-index: 3; +} + +.intermediate-flex-column { + display: flex; + flex-direction: column; + height: 100%; +} + + /* alerts */ .alert { diff --git a/lib/public/components/TabbedPanel/tabbedPanelComponent.js b/lib/public/components/TabbedPanel/tabbedPanelComponent.js index acb740b2d4..bcf78a6144 100644 --- a/lib/public/components/TabbedPanel/tabbedPanelComponent.js +++ b/lib/public/components/TabbedPanel/tabbedPanelComponent.js @@ -14,7 +14,6 @@ import { tabLink } from '../common/navigation/tabLink.js'; */ export const tabbedPanelComponent = (tabbedPanelModel, panelsTitles, panelsBodies, configuration) => { const { panelClass = 'p2' } = configuration || {}; - return [ h( 'ul.nav.nav-tabs', diff --git a/lib/public/components/common/table/table.js b/lib/public/components/common/table/table.js index fca01a49ed..5ce2d4fbe4 100644 --- a/lib/public/components/common/table/table.js +++ b/lib/public/components/common/table/table.js @@ -89,6 +89,9 @@ const parseColumnsConfiguration = (columns, currentProfile) => { * specific configuration. If not specified, any visible column will be displayed * @property {boolean} horizontalScrollEnabled if true, enable horizontal scroll in case of overflow, * fixed layout otherwise + * @property {boolean} verticallScrollEnabled if true, enable vertical (table internal) scroll in case of overflow, + * whole page vertical scroll otherwise. Note that for this option to work, + * all predecesors of the node returned by this function must have display property column-flex and height: 100% */ /** @@ -126,7 +129,12 @@ export const table = ( } // Extract the profile of the table - const { profile: currentProfile = profiles.none, horizontalScrollEnabled = false, freezeFirstColumn = false } = tableConfiguration || {}; + const { + profile: currentProfile = profiles.none, + horizontalScrollEnabled = false, + freezeFirstColumn = false, + verticalScrollEnabled = false, + } = tableConfiguration || {}; const { idKey, displayedColumns } = parseColumnsConfiguration(columns, currentProfile); let remoteData; @@ -138,18 +146,29 @@ export const table = ( Error(`Unhandled type <${typeof data}> of data : ${data ? JSON.stringify(data) : data}`); } - return h(`${horizontalScrollEnabled ? '.scroll-auto.shadow-level1' : ''}`, h( - `table.table.table-hover.shadow-level1.no-z-index${freezeFirstColumn ? '.freeze-first-column' : ''}`, - { - style: `table-layout: ${horizontalScrollEnabled ? 'auto' : 'fixed'}`, - }, - [ - headers(displayedColumns, models), - remoteDataTableBody( - remoteData, - (payload) => rows(payload, idKey, displayedColumns, rowsConfiguration), - displayedColumns.length, - ), - ], - )); + const scrollEnabled = horizontalScrollEnabled || verticalScrollEnabled; + const wrapperClassesExpression = scrollEnabled ? '.sticky-table-wrapper.scroll-auto' : ''; + + const optionalTableClassesExpression = freezeFirstColumn && horizontalScrollEnabled ? '.freeze-first-column' : ''; + + const wrappedTableNode = h( + wrapperClassesExpression, + h( + `table.table.table-hover.shadow-level1${optionalTableClassesExpression}`, + { + style: `table-layout: ${horizontalScrollEnabled ? 'auto' : 'fixed'}`, + }, + [ + headers(displayedColumns, models), + remoteDataTableBody( + remoteData, + (payload) => rows(payload, idKey, displayedColumns, rowsConfiguration), + displayedColumns.length, + ), + ], + ), + ); + return scrollEnabled && !verticalScrollEnabled + ? h('', wrappedTableNode) // Disable y-scroll + : wrappedTableNode; }; diff --git a/lib/public/view.js b/lib/public/view.js index e82b80ab3a..774651a0ad 100644 --- a/lib/public/view.js +++ b/lib/public/view.js @@ -120,7 +120,7 @@ export default (model) => { }; return [ - h('.flex-column.absolute-fill', [ + h('.intermediate-flex-column.absolute-fill', [ modalContainer(model.modalModel), NavBar(model), content(model, pages), @@ -140,7 +140,7 @@ const content = (model, pages) => h( '.flex-column', { key: model.router.params.page, - style: 'min-height: 100%', + style: 'height: 100%', onupdate: () => { }, }, diff --git a/lib/public/views/Runs/Overview/RunsWithQcModel.js b/lib/public/views/Runs/Overview/RunsWithQcModel.js index 08219bdd93..4dff88c1f1 100644 --- a/lib/public/views/Runs/Overview/RunsWithQcModel.js +++ b/lib/public/views/Runs/Overview/RunsWithQcModel.js @@ -26,6 +26,10 @@ export class RunsWithQcModel extends RunsOverviewModel { constructor(model) { super(model); - this.patchDisplayOptions({ horizontalScrollEnabled: true, freezeFirstColumn: true }); + this.patchDisplayOptions({ + horizontalScrollEnabled: true, + verticalScrollEnabled: true, + freezeFirstColumn: true, + }); } } diff --git a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js index 381d5998b5..e629b13730 100644 --- a/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js +++ b/lib/public/views/Runs/RunPerDataPass/RunsPerDataPassOverviewPage.js @@ -91,7 +91,6 @@ const skimmableControl = (dataPass, onclick, requestResult) => { NotAsked: () => h('button.btn.primary', { onclick }, buttonContent), }); } - return; }; /** @@ -131,7 +130,7 @@ export const RunsPerDataPassOverviewPage = ({ const runDetectorsSelectionIsEmpty = perDataPassOverviewModel.runDetectorsSelectionModel.selectedQueryString.length === 0; return h( - '', + '.intermediate-flex-column', { onremove: () => perDataPassOverviewModel.reset(false) }, mergeRemoteData([remoteDataPass, remoteRuns, remoteDetectors, remoteQcSummary, remoteGaqSummary]).match({ NotAsked: () => null, @@ -303,19 +302,17 @@ export const RunsPerDataPassOverviewPage = ({ Failure: (errors) => errorAlert(errors), Other: () => null, }), - h('.flex-column.w-100', [ - table( - runs, - activeColumns, - { classes: getRowClasses }, - { - profile: 'runsPerDataPass', - ...displayOptions, - }, - { sort: sortModel }, - ), - paginationComponent(perDataPassOverviewModel.pagination), - ]), + table( + runs, + activeColumns, + { classes: getRowClasses }, + { + profile: 'runsPerDataPass', + ...displayOptions, + }, + { sort: sortModel }, + ), + paginationComponent(perDataPassOverviewModel.pagination), ]; }, Loading: () => spinner(), diff --git a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js index a0cd4a9ff5..a527fed549 100644 --- a/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js +++ b/lib/public/views/Runs/RunPerPeriod/RunsPerLhcPeriodOverviewPage.js @@ -82,7 +82,7 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel * @param {object} detectorsActiveColumns active columns * @return {Component} table with pagination */ - const getTableWithGivenDetectorsColumns = (detectorsActiveColumns) => h('.flex-column.w-100', [ + const getTableWithGivenDetectorsColumns = (detectorsActiveColumns) => table( /* @@ -100,16 +100,14 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel ...displayOptions, }, { sort: sortModel }, - ), - paginationComponent(perLhcPeriodOverviewModel.pagination), - ]); + ); - return h('', [ + return h('.intermediate-flex-column', [ h('.flex-row.justify-between.g2', [ h('h2', `Good, physics runs of ${lhcPeriodName}`), exportRunsTriggerAndModal(perLhcPeriodOverviewModel, modalModel), ]), - tabbedPanelComponent( + ...tabbedPanelComponent( tabbedPanelModel, { [RUNS_PER_LHC_PERIOD_PANELS_KEYS.DETECTOR_QUALITIES]: 'Qualities of detectors', @@ -131,7 +129,10 @@ export const RunsPerLhcPeriodOverviewPage = ({ runs: { perLhcPeriodOverviewModel }, )), }, + { + panelClass: ['p2', 'scroll-auto'], + }, ), - + paginationComponent(perLhcPeriodOverviewModel.pagination), ]); }; diff --git a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js index fd2780cab1..75f6a88736 100644 --- a/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js +++ b/lib/public/views/Runs/RunsPerSimulationPass/RunsPerSimulationPassOverviewPage.js @@ -68,7 +68,7 @@ export const RunsPerSimulationPassOverviewPage = ({ const commonTitle = h('h2', 'Runs per MC'); - return h('', mergeRemoteData([remoteSimulationPass, remoteRuns, remoteDetectors, remoteQcSummary]).match({ + return h('.intermediate-flex-column', mergeRemoteData([remoteSimulationPass, remoteRuns, remoteDetectors, remoteQcSummary]).match({ NotAsked: () => null, Failure: (errors) => errorAlert(errors), Success: ([simulationPass, runs, detectors, qcSummary]) => { @@ -111,19 +111,17 @@ export const RunsPerSimulationPassOverviewPage = ({ }, ), ]), - h('.flex-column.w-100', [ - table( - runs, - activeColumns, - { classes: getRowClasses }, - { - profile: 'runsPerSimulationPass', - ...displayOptions, - }, - { sort: sortModel }, - ), - paginationComponent(perSimulationPassOverviewModel.pagination), - ]), + table( + runs, + activeColumns, + { classes: getRowClasses }, + { + profile: 'runsPerSimulationPass', + ...displayOptions, + }, + { sort: sortModel }, + ), + paginationComponent(perSimulationPassOverviewModel.pagination), ]; }, Loading: () => spinner(),