Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9c45255
[OGUI-1216] Override browser zoom to scale log table only
isaachilly May 20, 2026
dcb30cf
[OGUI-1216] Add zoom feature test suite
isaachilly May 20, 2026
b301dfe
Merge branch 'dev' into feature/ILG/OGUI-1216/Override-browser-zoom-t…
isaachilly May 21, 2026
616d55c
[OGUI-1216] Fix failing CI/CD tests
isaachilly May 21, 2026
7e62328
Merge branch 'feature/ILG/OGUI-1216/Override-browser-zoom-to-increase…
isaachilly May 21, 2026
99304c8
[OGUI-1216] Try again to fix CI/CD failing test
isaachilly May 21, 2026
16a4651
[OGUI-1216] Increase tiemout for failing CI/CD test
isaachilly May 21, 2026
569df46
[OGUI-1216] Calling Zoom In via keyboard
isaachilly May 21, 2026
80eba66
[OGUI-1216] Revert to original, still unsure why failing
isaachilly May 21, 2026
259badf
[OGUI-1216] Request the animation frame instead of polling
isaachilly May 21, 2026
e4549d5
[OGUI-1216] Revert again
isaachilly May 21, 2026
f99a286
[OGUI-1216] Extract zoom into its own model
isaachilly May 22, 2026
0a97d60
[OGUI-1216] Update zoom calls to use new Zoom model
isaachilly May 22, 2026
6d12199
[OGUI-1216] Add support for macOS cmd key
isaachilly May 22, 2026
114db03
[OGUI-1216] Reorganise zoom tests, add macOS Meta key tests and add r…
isaachilly May 22, 2026
7ab5f59
Merge branch 'dev' into feature/ILG/OGUI-1216/Override-browser-zoom-t…
isaachilly May 22, 2026
422d405
[OGUI-1216] Attempt to fix CI failing tests
isaachilly May 22, 2026
3b2e67e
Merge branch 'dev' into feature/ILG/OGUI-1216/Override-browser-zoom-t…
isaachilly May 22, 2026
9cc691c
[OGUI-1216] Move zoom handlers to Model
isaachilly May 22, 2026
99286b2
[OGUI-1216] Add zoom in/out buttons and tests
isaachilly May 22, 2026
39c7b98
[OGUI-1216] Extract zoom controls into zoomButtonGroup
isaachilly May 22, 2026
48c48cd
[OGUI-1216] Wait for zoom reset in beforeEach
isaachilly May 22, 2026
41ed25d
[OGUI-1216] Fixes to help solve CI/CD issues
isaachilly May 22, 2026
a7468ac
[OGUI-1216] Make viewport for mocha ILG tests 15"
isaachilly May 22, 2026
0339258
[OGUI-1216] Fix codeQL raised issue
isaachilly May 22, 2026
d1a7283
[OGUI-1216] Remove QC erroneous commit
isaachilly May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions InfoLogger/public/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { callRateLimiter, setBrowserTabTitle } from './common/utils.js';
import { ConfigurationService } from './services/ConfigurationService.js';
import { MODE } from './constants/mode.const.js';
import Log from './log/Log.js';
import Zoom from './log/Zoom.js';
import Table from './table/Table.js';
import Timezone from './common/Timezone.js';

Expand Down Expand Up @@ -68,8 +69,9 @@ export default class Model extends Observable {
this.router.bubbleTo(this);
this.handleLocationChange(); // Init first page

// Setup keyboard dispatcher
// Setup keyboard and wheel dispatchers
window.addEventListener('keydown', this.handleKeyboardDown.bind(this));
window.addEventListener('wheel', this.handleWheel.bind(this), { passive: false });

// Setup WS connection
this.ws = new WebSocketClient();
Expand All @@ -81,6 +83,9 @@ export default class Model extends Observable {
// Model can change very often we protect router with callRateLimiter
// Router limit: 100 calls per 30 seconds max = 30ms, 2 FPS is enough (500ms)
this.observe(callRateLimiter(this.updateRouteOnModelChange.bind(this), 500));

this.zoom = new Zoom();
this.zoom.bubbleTo(this);
}

/**
Expand Down Expand Up @@ -196,14 +201,44 @@ export default class Model extends Observable {
return;
}

/**
* Handles wheel events for zoom control
* @param {WheelEvent} e - wheel event
*/
handleWheel(e) {
// Only trigger zoom if Ctrl (or Cmd on Mac) is pressed
// Windows intercepts the Windows key events, so these do not reach the browser
if (!e.ctrlKey && !e.metaKey) {
return;
}
e.preventDefault();
const now = Date.now();
// throttle zoom to avoid too many events on fast scroll, especially on trackpads
if (now - this.zoom.lastScrollTime < 50) {
return;
}
this.zoom.lastScrollTime = now;
e.deltaY < 0 ? this.zoom.zoomIn() : this.zoom.zoomOut();
}

/**
* Delegates sub-model actions depending on incoming keyboard event
* @param {Event} e - keyboard event
*/
handleKeyboardDown(e) {
// console.log(
// e.code, e.key,e.keyCode, e.metaKey, e.ctrlKey, e.altKey`
// );
// Zoom shortcuts regardless of focus
if (e.ctrlKey || e.metaKey) {
if (e.key === '=' || e.key === '+') {
e.preventDefault();
this.zoom.zoomIn();
return;
} else if (e.key === '-') {
e.preventDefault();
this.zoom.zoomOut();
return;
}
}

const code = e.keyCode;

// Enter
Expand Down
10 changes: 7 additions & 3 deletions InfoLogger/public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
.table-filters { width: 100%; }

/* logs div */
.logs-container { cursor: default; }
.logs-container {
cursor: default;
--log-font-size: 0.7rem; /* default, overridden by JS zoom */
--row-height: 0.91rem; /* default, overridden by JS zoom */
}
.logs-content { border-top: 1px solid #aaa; }

/* logs tables */
Expand All @@ -35,12 +39,12 @@
.table-logs-content td {}

.table-logs-header td,
.table-logs-content td { font-size: 0.7rem; }
.table-logs-content td { font-size: var(--log-font-size); }

td,
th { max-width: 0; /* allow ellipsis on tables */ vertical-align: top; }

.cell { line-height: 1rem; font-size: 1rem; padding: 0rem 0.2rem; font-weight: 100; line-height: 18px; /* must be sync with rowHeight constant in view */ }
.cell { line-height: var(--row-height); font-size: 1rem; padding: 0rem 0.2rem; font-weight: 100; }
.cell-bordered { border-left: 1px solid rgb(170, 170, 170); }
.cell-content { display: flex; justify-content: space-between; align-items: center; max-width: 100%; }
.cell-text {
Expand Down
9 changes: 6 additions & 3 deletions InfoLogger/public/log/Log.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import LogFilter from '../logFilter/LogFilter.js';
import ContextMenu from './ContextMenu.js';
import { MODE } from '../constants/mode.const.js';
import { TIME_MS } from '../common/Timezone.js';
import { ROW_HEIGHT } from '../constants/visual.const.js';

/**
* Model Log, encapsulate all log management and queries
Expand Down Expand Up @@ -599,8 +598,12 @@ export default class Log extends Observable {
*/
listLogsInViewportOnly() {
return this.list.slice(
Math.floor(this.scrollTop / ROW_HEIGHT),
Math.floor(this.scrollTop / ROW_HEIGHT) + Math.ceil(this.scrollHeight / ROW_HEIGHT) + 1,
Math.floor(this.scrollTop / this.rowHeight),
Math.floor(this.scrollTop / this.rowHeight) + Math.ceil(this.scrollHeight / this.rowHeight) + 1,
);
}

get rowHeight() {
return this.model.zoom.rowHeightPx;
}
}
94 changes: 94 additions & 0 deletions InfoLogger/public/log/Zoom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

import { Observable } from '/js/src/index.js';

/**
* Model for log table zoom, controls row font size and height scaling
*/
export default class Zoom extends Observable {
constructor() {
super();

this.level = 1;
this.min = 0.5;
this.max = 4;
this.step = 0.1;
this.baseFontSize = 0.7;
this.rowHeightRatio = 1.3;
this.lastScrollTime = 0;
}

/**
* Font size in rem units
* @returns {number} - font size in rem units
*/
get fontSize() {
return this.baseFontSize * this.level;
}

/**
* Row height in rem units, computed with font size to keep the same ratio across zoom levels
* @returns {number} - row height in rem units
*/
get rowHeightRem() {
return this.fontSize * this.rowHeightRatio;
}

/**
* Row height in pixels, used for the virtual scroll to know how many logs to render depending on the container size
* @returns {number} - row height in pixels
*/
get rowHeightPx() {
return this.rowHeightRem * parseFloat(getComputedStyle(document.documentElement).fontSize);
}

/**
* Zoom in by increasing zoom level by step, with a maximum of zoom.max
*/
zoomIn() {
this.#setZoomLevel(Math.min(this.level + this.step, this.max));
}

/**
* Zoom out by decreasing zoom level by step, with a minimum of zoom.min
*/
zoomOut() {
this.#setZoomLevel(Math.max(this.level - this.step, this.min));
}

/**
* Reset zoom to base level of 1
*/
resetZoom() {
this.#setZoomLevel(1);
}

/**
* Set zoom level
* @param {number} level - zoom level to set, should be between zoom.min and zoom.max
*/
#setZoomLevel(level) {
// Keep zoom to 2 d.p. to avoid floating-point artifacts (for example 1.2000000000000002)
// This keeps CSS values stable and ensures users can reliably return to default zoom (1)
this.level = parseFloat(level.toFixed(2));
const root = document.querySelector('.logs-container');
if (root) {
// Keep CSS sizes to 3 d.p. to avoid floating-point artifacts (for example 1.0920000000000002rem)
root.style.setProperty('--log-font-size', `${this.fontSize.toFixed(3)}rem`);
root.style.setProperty('--row-height', `${this.rowHeightRem.toFixed(3)}rem`);
}
this.notify();
}
}
38 changes: 37 additions & 1 deletion InfoLogger/public/log/commandLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@
* or submit itself to any jurisdiction.
*/

import { h, iconPerson, iconMediaPlay, iconMediaStop, iconDataTransferDownload } from '/js/src/index.js';
import { h,
iconPerson,
iconMediaPlay,
iconMediaStop,
iconDataTransferDownload,
iconMagnifyingGlass,
iconPlus,
iconMinus,
} from '/js/src/index.js';
import { BUTTON } from '../constants/button-states.const.js';
import { MODE } from '../constants/mode.const.js';
import { setBrowserTabTitle } from '../common/utils.js';
Expand Down Expand Up @@ -58,6 +66,7 @@ export default (model) => [
title: 'Go to last log message (ALT + down arrow)',
}, '↓'),
downloadButtonGroup(model.log),
zoomButtonGroup(model.zoom),
];

/**
Expand Down Expand Up @@ -150,6 +159,33 @@ const downloadButtonGroup = (logModel) =>
]),
]);

/**
* Group of buttons for controlling log table zoom level
* @param {Zoom} zoom - the zoom model
* @returns {vnode} - the view of the zoom button group
*/
const zoomButtonGroup = (zoom) =>
h('.btn-group', [
h('button.btn', {
onclick: () => zoom.zoomOut(),
disabled: zoom.level <= zoom.min,
id: 'zoom-out-button',
title: 'Zoom out (Ctrl/Cmd + -)',
}, h('span', { style: 'font-size:0.8em' }, iconMinus())),
h('button.btn', {
onclick: () => zoom.resetZoom(),
disabled: zoom.level === 1,
id: 'reset-zoom-button',
title: 'Reset zoom',
}, h('span', { style: 'font-size:0.9em' }, iconMagnifyingGlass())),
h('button.btn', {
onclick: () => zoom.zoomIn(),
disabled: zoom.level >= zoom.max,
id: 'zoom-in-button',
title: 'Zoom in (Ctrl/Cmd + +)',
}, h('span', { style: 'font-size:0.8em' }, iconPlus())),
]);

/**
* Live button final state depends on the following states
* - services lookup
Expand Down
9 changes: 4 additions & 5 deletions InfoLogger/public/log/tableLogsContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import { h } from '/js/src/index.js';

import { severityClass } from './severityUtils.js';
import tableColGroup from './tableColGroup.js';
import { ROW_HEIGHT } from './../constants/visual.const.js';

/**
* Main content of ILG - simulates a big table scrolling.
Expand All @@ -34,7 +33,7 @@ export default (model) =>
tableContainerHooks(model),
h('div.tableLogsContentPlaceholder', {
style: {
height: `${model.log.list.length * ROW_HEIGHT}px`,
height: `${model.log.list.length * model.log.rowHeight}px`,
position: 'relative',
},
}, [
Expand All @@ -55,7 +54,7 @@ export default (model) =>
const scrollStyling = (model) => ({
style: {
position: 'absolute',
top: `${model.log.scrollTop - model.log.scrollTop % ROW_HEIGHT}px`,
top: `${model.log.scrollTop - model.log.scrollTop % model.log.rowHeight}px`,
},
});

Expand Down Expand Up @@ -264,7 +263,7 @@ const autoscrollManager = (model, vnode) => {

if (previousLastLogId !== currentLastLogId) {
// scroll at maximum bottom possible
vnode.dom.scrollTo(0, ROW_HEIGHT * model.log.applicationLimit);
vnode.dom.scrollTo(0, model.log.rowHeight * model.log.applicationLimit);
vnode.dom.dataset.lastLogId = currentLastLogId;
}

Expand All @@ -281,7 +280,7 @@ const autoscrollManager = (model, vnode) => {
if (previousSelectedItemId !== currentSelectedItemId && model.log.autoScrollToItem) {
// scroll to an index * height of row, centered
const index = model.log.list.indexOf(model.log.item);
const positionRow = ROW_HEIGHT * index;
const positionRow = model.log.rowHeight * index;
const halfView = model.log.scrollHeight / 2;
vnode.dom.scrollTo(0, positionRow - halfView);
}
Expand Down
2 changes: 2 additions & 0 deletions InfoLogger/test/mocha-index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ describe('InfoLogger', function() {
// Start browser to test UI
browser = await puppeteer.launch({headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox']});
page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 }); // 15" screen equivalent

// Export page and configurations for the other mocha files
exports.page = page;
Expand Down Expand Up @@ -104,6 +105,7 @@ describe('InfoLogger', function() {
require('./public/log-filter-actions-mocha');
require('./public/live-mode-mocha');
require('./public/query-mode-mocha');
require('./public/zoom.mocha');
require('./public/log-context-menu-mocha');

after(async () => {
Expand Down
Loading
Loading