Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
53 changes: 53 additions & 0 deletions QualityControl/public/common/chevronButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @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 { h, iconChevronLeft, iconChevronRight } from '/js/src/index.js';

/**
* @typedef {object} ChevronButtonOptions
* @property {string} [id] - html id property.
* @property {string} [class] - html class property, multiple classes can be passed as a single space seperated string.
* @property {string} [title] - title shown on hover.
* @property {boolean} [isVisible=false] - shows right chevron if true, left chevron if false.
*/

/**
* Chevron direction toggle button (forward/backward).
* Creates an anchor element that displays a right chevron icon if forward or a left chevron icon if backward.
* @param {() => void} onclick - Callback invoked when the button is clicked.
* @param {ChevronButtonOptions} options - Additional options for the button element
* @returns {vnode} - Chevron direction toggle button vnode.
* @example
* chevronButton(
* () => {
* objectViewModel.toggleObjectInfoVisible();
* },
* {
* isVisible: objectViewModel.getObjectInfoVisible(),
* title: 'Toggle object information visibility',
* },
* );
*/
export function chevronButton(onclick, options = {}) {
const { isVisible = false, ...restOptions } = options;
const mergedOptions = {
class: `chevron-button chevron-${isVisible ? 'right' : 'left'}`,
...restOptions,
};

return h('a.btn', {
...mergedOptions,
onclick,
}, isVisible ? iconChevronRight() : iconChevronLeft());
}
20 changes: 20 additions & 0 deletions QualityControl/public/common/constants/drawingOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @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.p
*/

const DRAW_OPTIONS = ['lego', 'colz', 'lcolz', 'text'];
const DISPLAY_HINTS = ['logx', 'logy', 'logz', 'gridx', 'gridy', 'gridz', 'stat'];

export { DRAW_OPTIONS, DISPLAY_HINTS };

export const DRAWING_OPTIONS = new Set([...DRAW_OPTIONS, ...DISPLAY_HINTS]);
95 changes: 95 additions & 0 deletions QualityControl/public/common/object/objectDrawingOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* @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.p
*/

import { h } from '/js/src/index.js';
import { DRAW_OPTIONS } from '../constants/drawingOptions.js';
import { DISPLAY_HINTS } from '../constants/drawingOptions.js';

/**
* Display options overlay for a QC object
* @param {object} options - The options object
* @param {string} options.id - The unique identifier for the object
* @param {boolean} options.ignoreDefaults - Whether to ignore default drawing options
* @param {Array<string>} options.options - Array of selected draw options and display hints
* @param {Array<string>} options.nonRecognizedDrawingOptions - Array of non-recognized drawing options
* @param {() => void} options.onToggleIgnoreDefaults - Callback to toggle ignore defaults
* @param {(option: string) => void} options.onToggleOption - Callback to toggle a drawing option or display hint
* @returns {vnode} Virtual DOM node representing the display options panel
*/
export const objectDrawingOptions = ({
id,
ignoreDefaults,
options,
nonRecognizedDrawingOptions,
onToggleIgnoreDefaults,
onToggleOption,
}) =>
h('.absolute-fill.level1.scroll-y.#objectDrawingOptions', [
h('.absolute.right-0.top-0.bg-white.shadow-lg.w-100.h-100.overflow-auto', [
h('.flex-row.items-center.justify-between.mb2.g2', [
h('span', 'Drawing Options:'),
checkboxWithTooltip({
id: `${id}ignoreDefaults`,
label: 'Ignore defaults',
tooltipText: 'Set by ROOT (fOption) and QC Metadata',
checked: ignoreDefaults,
onChange: onToggleIgnoreDefaults,
}),
]),
nonRecognizedDrawingOptions && nonRecognizedDrawingOptions.length > 0 &&
h('.flex-row.label.mv2.danger', `Non-recognized options: ${nonRecognizedDrawingOptions.join(', ')}`),
sectionTitle('Draw Options:', ' ROOT draw options'),
checkboxGrid(DRAW_OPTIONS.map((option) =>
checkBox(id + option, option, options.includes(option), () => onToggleOption(option)))),
sectionTitle('Display Hints:', ' Canvas display hints'),
checkboxGrid(DISPLAY_HINTS.map((option) =>
checkBox(id + option, option, options.includes(option), () => onToggleOption(option)))),
]),
]);

const checkboxGrid = (children) =>
h('.flex-column.g2', {
style: {
display: 'grid',
gap: '8px 12px',
gridTemplateColumns: 'repeat(auto-fit, minmax(115px, 115px))',
maxWidth: '400px',
},
}, children);

const sectionTitle = (label, tooltipText) =>
h('.flex-row.mv2', h('.tooltip', [h('label.m0', label), h('.tooltiptext', tooltipText)]));

const checkBox = (id, option, checked, onChange) =>
h('.form-check', [
h('input.form-check-input', {
type: 'checkbox',
id: id,
checked,
onchange: onChange,
}),
h('label.m0', { for: id }, option),
]);

const checkboxWithTooltip = ({ id, label, tooltipText, checked, onChange }) =>
h('.form-check.tooltip.mt2-sm.mh2', [
h('input.form-check-input', {
type: 'checkbox',
id: id,
checked,
onchange: onChange,
}),
h('label.m0', { for: id }, label),
h('span.tooltiptext', tooltipText),
]);
91 changes: 84 additions & 7 deletions QualityControl/public/common/object/objectInfoCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import { h } from '/js/src/index.js';
import { camelToTitleCase, copyToClipboard, isContextSecure, prettyFormatDate } from './../utils.js';
import { visibilityButton } from '../visibilityButton.js';

const SPECIFIC_KEY_LABELS = {
id: 'ID (etag)',
Expand All @@ -22,6 +23,8 @@ const SPECIFIC_KEY_LABELS = {
const DATE_FIELDS = ['validFrom', 'validUntil', 'createdAt', 'lastModified'];
const TO_REMOVE_FIELDS = ['etag', 'qcObject', 'versions', 'name', 'location'];
const HIGHLIGHTED_FIELDS = ['runNumber', 'runType', 'path', 'qcVersion'];
const DRAW_OPTIONS_FIELD = 'drawOptions';
const DISPLAY_HINTS_FIELD = 'displayHints';

const KEY_TO_RENDER_FIRST = 'path';

Expand All @@ -32,23 +35,95 @@ const KEY_TO_RENDER_FIRST = 'path';
* @param {function(Notification): function(string, string): object} rowAttributes -
* An optional curried function that returns the VNode attribute builder.
* Use {@link defaultRowAttributes} exported from this module, supplying the Notification API.
* @param {() => void} onToggleDrawingOptions - Callback function to for displaying drawing options
* @returns {vnode} - panel with information about the object
* @example
* ```
* qcObjectInfoPanel(qcObject, {}, defaultRowAttributes(model.notification))
* qcObjectInfoPanel(
* qcObject,
* { gap: '.5em' },
* defaultRowAttributes(model.notification),
* () => objectViewModel.toggleDrawingOptionsVisible(),
* )
* ```
*/
export const qcObjectInfoPanel = (qcObject, style = {}, rowAttributes = () => undefined) =>
export const qcObjectInfoPanel = (
qcObject,
style = {},
rowAttributes = () => undefined,
onToggleDrawingOptions = null, // Default to null to indicate it's optional
) =>
h('.flex-column.scroll-y#qcObjectInfoPanel', { style }, [
[
KEY_TO_RENDER_FIRST,
...Object.keys(qcObject)
.filter((key) =>
key !== KEY_TO_RENDER_FIRST && !TO_REMOVE_FIELDS.includes(key)),
.filter((k) => k !== KEY_TO_RENDER_FIRST && !TO_REMOVE_FIELDS.includes(k)),
]
.map((key) => infoRow(key, qcObject[key], rowAttributes)),
.flatMap((key) => {
if (key === DRAW_OPTIONS_FIELD && typeof onToggleDrawingOptions === 'function') {
return [
groupedInfoRow({
keyValuePair1: { key: DRAW_OPTIONS_FIELD, value: qcObject.drawOptions },
keyValuePair2: { key: DISPLAY_HINTS_FIELD, value: qcObject.displayHints },
infoRowAttributes: defaultRowAttributes,
buttonElement: visibilityButton(onToggleDrawingOptions, { title: 'Toggle drawing options' }),
}),
];
}
if (key === DISPLAY_HINTS_FIELD && typeof onToggleDrawingOptions === 'function') {
return []; // Hide displayHints (it is shown inside the grouped info row)
}
return [infoRow(key, qcObject[key], rowAttributes)];
}),
]);

/**
* Builds two info rows grouped together with an action button on the side
* @param {object} params - parameters object
* @param {object} params.keyValuePair1 - first key value pair
* @param {object} params.keyValuePair2 - second key value pair
* @param {function(string, string): object} params.infoRowAttributes - function that return given attributes
* for the row
* @param {vnode} params.buttonElement - button element to be displayed on the side
* @returns {vnode} - grouped info row with action button
*/
const groupedInfoRow = ({ keyValuePair1, keyValuePair2, infoRowAttributes, buttonElement }) => {
const { key: key1, value: value1 } = keyValuePair1;
const { key: key2, value: value2 } = keyValuePair2;
const highlightedClassesKey1 = HIGHLIGHTED_FIELDS.includes(key1) ? '.highlighted' : '';
const highlightedClassesKey2 = HIGHLIGHTED_FIELDS.includes(key2) ? '.highlighted' : '';
const formattedValue1 = infoPretty(key1, value1);
const formattedKey1 = getUILabel(key1);
const hasValue1 = value1 != null && value1 !== '' && (!Array.isArray(value1) || value1.length !== 0);
const formattedValue2 = infoPretty(key2, value2);
const formattedKey2 = getUILabel(key2);
const hasValue2 = value2 != null && value2 !== '' && (!Array.isArray(value2) || value2.length !== 0);
return h('.flex-row.relative', [
h('.flex-column.w-100.g2', [
h(`.flex-row.g2.info-row${highlightedClassesKey1}`, [
h('b.w-25.w-wrapped', formattedKey1),
h('.w-75', { style: 'padding-right: 50px', // avoid overlap with button
...hasValue1
? infoRowAttributes(formattedKey1, formattedValue1)
: {} }, formattedValue1),
]),
h(`.flex-row.g2.info-row${highlightedClassesKey2}`, [
h('b.w-25.w-wrapped', formattedKey2),
h('.w-75', { style: 'padding-right: 50px', // avoid overlap with button
...hasValue2
? infoRowAttributes(formattedKey2, formattedValue2)
: {} }, formattedValue2),
]),
]),
h(
'.absolute.items-center.level3',
{ style: 'top:50%;right:0;transform:translateY(-50%)' },
buttonElement,
),
]);
};

/**
* Builds a raw with the key and value information parsed based on their type
* @param {string} key - key of the object info
Expand All @@ -60,12 +135,14 @@ const infoRow = (key, value, infoRowAttributes) => {
const highlightedClasses = HIGHLIGHTED_FIELDS.includes(key) ? '.highlighted' : '';
const formattedValue = infoPretty(key, value);
const formattedKey = getUILabel(key);

const hasValue = value != null && value !== '' && (!Array.isArray(value) || value.length !== 0);

return h(`.flex-row.g2.info-row${highlightedClasses}`, [
h('b.w-25.w-wrapped', formattedKey),
h('.w-75.cursor-pointer', hasValue && infoRowAttributes(formattedKey, formattedValue), formattedValue),
h(
`.w-75 ${hasValue ? 'cursor-pointer' : 'cursor-none'}`,
hasValue ? infoRowAttributes(formattedKey, formattedValue) : {},
formattedValue,
),
]);
};

Expand Down
36 changes: 10 additions & 26 deletions QualityControl/public/common/visibilityButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,26 @@
* or submit itself to any jurisdiction.
*/

import { h, iconChevronLeft, iconChevronRight } from '/js/src/index.js';
import { h, iconEye } from '/js/src/index.js';

/**
* @typedef {object} VisibilityToggleButtonOptions
* @typedef {object} VisibilityButtonOptions
* @property {string} [id] - html id property.
* @property {string} [class] - html class property, multiple classes can be passed as a single space seperated string.
* @property {string} [title] - title shown on hover.
* @property {boolean} [isVisible=true] - determines which icon is rendered.
*/

/**
* Visibility toggle button.
* Creates an anchor element that displays an **eye** icon if visible or a **closed eye / no-eye** icon if hidden.
* @param {VisibilityToggleButtonOptions} options - Virtual node options.
* Creates a visibility toggle button with an eye icon
* @param {() => void} onclick - Callback invoked when the button is clicked.
* @returns {vnode} - Visibility toggle button vnode.
* @example
* visibilityToggleButton(
* {
* isVisible: objectViewModel.getObjectInfoVisible(),
* title: 'Toggle object information visibility',
* },
* () => {
* objectViewModel.toggleObjectInfoVisible();
* },
* );
* @param {VisibilityButtonOptions} options - Additional options for the button element
* @returns {object} - Visibility toggle button vnode.
*/
export function visibilityToggleButton(options = {}, onclick) {
const { isVisible = true, ...restOptions } = options;
const mergedOptions = {
class: `visibility-toggle-button visibility-toggle-${isVisible ? 'on' : 'off'}`,
...restOptions,
};

export function visibilityButton(onclick, options = {}) {
const { ...rest } = options;
return h('a.btn', {
...mergedOptions,
...rest,
class: ['visibility-toggle-button', rest.class || ''].join(' ').trim(),
onclick,
}, isVisible ? iconChevronRight() : iconChevronLeft());
}, iconEye());
}
Loading
Loading