Skip to content
Open
17 changes: 17 additions & 0 deletions QualityControl/public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,20 @@
.whitespace-nowrap {
white-space: nowrap;
}

.sort-button {
.hover-icon {
display: none;
opacity: 0.6;
}

&:hover {
.current-icon {
display: none;
}

.hover-icon {
display: inline-block;
}
}
}
24 changes: 24 additions & 0 deletions QualityControl/public/common/enums/columnSort.enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @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.
*/

/**
* Enumeration for sort directions
* @enum {number}
* @readonly
*/
export const SortDirectionsEnum = Object.freeze({
NONE: 0,
ASC: 1,
DESC: -1,
});
81 changes: 81 additions & 0 deletions QualityControl/public/common/sortButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @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 { SortDirectionsEnum } from '../common/enums/columnSort.enum.js';
import { h, iconCircleX, iconArrowBottom, iconArrowTop } from '/js/src/index.js';

/**
* Get the icon for the sort direction.
* @param {SortDirectionsEnum} direction - direction of the sort.
* @returns {vnode} the correct icon related to the direction.
*/
const getSortIcon = (direction) => {
if (direction === SortDirectionsEnum.ASC) {
return iconArrowTop();
}
if (direction === SortDirectionsEnum.DESC) {
return iconArrowBottom();
}
return iconCircleX();
};

/**
* @callback SortClickCallback
* @param {string} label - The label of the column being sorted.
* @param {number} order - The next sort direction in the cycle.
* @param {vnode} icon - The VNode for the icon representing the next sort state.
* @returns {void}
*/

/**
* Renders a sortable table header button that cycles through sort states.
* Displays the current sort icon and a preview icon of the next state on hover.
* @param {object} props - The component properties.
* @param {number} props.order - The current sort direction value from SortDirectionsEnum.
* @param {object|undefined} props.icon - The VNode/element for the current active sort icon.
* @param {string} props.label - The display text for the column header.
* @param {SortClickCallback} props.onclick - Callback triggered on click.
* @param {Array<number>} [props.sortOptions] - Array of SortDirectionsEnum values defining the
* order of the sort cycle. Defaults to all enum values.
* @returns {object} A HyperScript VNode representing the sortable button.
*/
export const sortableTableHead = ({
order,
icon,
label,
onclick,
sortOptions = [...Object.values(SortDirectionsEnum)],
}) => {
const currentIndex = sortOptions.indexOf(order);
const nextIndex = (currentIndex + 1) % sortOptions.length;
const nextSortOrder = sortOptions[nextIndex];
const hoverIcon = getSortIcon(nextSortOrder);

const directionLabel = Object.keys(SortDirectionsEnum).find((key) => SortDirectionsEnum[key] === nextSortOrder);

return h(
'button.btn.sort-button',
{
onclick: () => onclick(label, nextSortOrder, hoverIcon),
title: `Sort by ${directionLabel}`,
},
[
label,
h('span.icon-container.mh1', [
h('span.current-icon', [order != SortDirectionsEnum.NONE ? icon : undefined]),
h('span.hover-icon', [getSortIcon(nextSortOrder)]),
]),
],
);
};
13 changes: 1 addition & 12 deletions QualityControl/public/object/QCObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export default class QCObject extends BaseViewModel {
title: 'Name',
order: 1,
icon: iconArrowTop(),
open: false,
};

this.tree = new ObjectTree('database');
Expand Down Expand Up @@ -115,15 +114,6 @@ export default class QCObject extends BaseViewModel {
this.notify();
}

/**
* Toggle the display of the sort by dropdown
* @returns {undefined}
*/
toggleSortDropdown() {
this.sortBy.open = !this.sortBy.open;
this.notify();
}

/**
* Computes the final list of objects to be seen by user depending on search input from user
* If any of those changes, this method should be called to update the outputs.
Expand Down Expand Up @@ -189,7 +179,7 @@ export default class QCObject extends BaseViewModel {

this._computeFilters();

this.sortBy = { field, title, order, icon, open: false };
this.sortBy = { field, title, order, icon };
this.notify();
}

Expand Down Expand Up @@ -253,7 +243,6 @@ export default class QCObject extends BaseViewModel {
title: 'Name',
order: 1,
icon: iconArrowTop(),
open: false,
};
this._computeFilters();

Expand Down
52 changes: 4 additions & 48 deletions QualityControl/public/object/objectTreeHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
*/

import { h } from '/js/src/index.js';
import { iconCollapseUp, iconArrowBottom, iconArrowTop } from '/js/src/icons.js';
import { filterPanelToggleButton } from '../common/filters/filterViews.js';

/**
Expand All @@ -39,52 +38,9 @@ export default function objectTreeHeader(qcObject, filterModel) {
qcObject.objectsRemote.isSuccess() && h('span', `(${howMany})`),
]),

rightCol: h('.w-25.flex-row.items-center.g2.justify-end', [
filterModel.isRunModeActivated ? null : filterPanelToggleButton(filterModel),
' ',
h('.dropdown', {
id: 'sortTreeButton', title: 'Sort by', class: qcObject.sortBy.open ? 'dropdown-open' : '',
}, [
h('button.btn', {
title: 'Sort by',
onclick: () => qcObject.toggleSortDropdown(),
}, [qcObject.sortBy.title, ' ', qcObject.sortBy.icon]),
h('.dropdown-menu.text-left', [
sortMenuItem(qcObject, 'Name', 'Sort by name ASC', iconArrowTop(), 'name', 1),
sortMenuItem(qcObject, 'Name', 'Sort by name DESC', iconArrowBottom(), 'name', -1),

]),
]),
' ',
h('button.btn', {
title: 'Close whole tree',
onclick: () => qcObject.tree.closeAll(),
disabled: Boolean(qcObject.searchInput),
}, iconCollapseUp()),
' ',
h('input.form-control.form-inline.mh1.w-33', {
id: 'searchObjectTree',
placeholder: 'Search',
type: 'text',
value: qcObject.searchInput,
disabled: qcObject.queryingObjects ? true : false,
oninput: (e) => qcObject.search(e.target.value),
}),
' ',
]),
rightCol: h(
'.w-25.flex-row.items-center.g2.justify-end',
[filterModel.isRunModeActivated ? null : filterPanelToggleButton(filterModel)],
),
};
}

/**
* Create a menu-item for sort-by dropdown
* @param {QcObject} qcObject - Model that manages the QCObject state.
* @param {string} shortTitle - title that gets displayed to the user
* @param {string} title - title that gets displayed to the user on hover
* @param {Icon} icon - svg icon to be used
* @param {string} field - field by which sorting should happen
* @param {number} order - {-1/1}/{DESC/ASC}
* @returns {vnode} - virtual node element
*/
const sortMenuItem = (qcObject, shortTitle, title, icon, field, order) => h('a.menu-item', {
title: title, style: 'white-space: nowrap;', onclick: () => qcObject.sortTree(shortTitle, field, order, icon),
}, [shortTitle, ' ', icon]);
59 changes: 55 additions & 4 deletions QualityControl/public/object/objectTreePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@
* or submit itself to any jurisdiction.
*/

import { h, iconBarChart, iconCaretRight, iconResizeBoth, iconCaretBottom, iconCircleX } from '/js/src/index.js';
import {
h,
iconCollapseUp,
iconBarChart,
iconCaretRight,
iconResizeBoth,
iconCaretBottom,
iconCircleX,
} from '/js/src/index.js';
import { spinner } from '../common/spinner.js';
import { draw } from '../common/object/draw.js';
import timestampSelectForm from './../common/timestampSelectForm.js';
import virtualTable from './virtualTable.js';
import { defaultRowAttributes, qcObjectInfoPanel } from '../common/object/objectInfoCard.js';
import { downloadButton } from '../common/downloadButton.js';
import { resizableDivider } from '../common/resizableDivider.js';
import { SortDirectionsEnum } from '../common/enums/columnSort.enum.js';
import { sortableTableHead } from '../common/sortButton.js';

/**
* Shows a page to explore though a tree of objects with a preview on the right if clicked
Expand All @@ -46,9 +56,15 @@ export default (model) => {
const objectsLoaded = object.list;
const objectsToDisplay = objectsLoaded.filter((qcObject) =>
qcObject.name.toLowerCase().includes(searchInput.toLowerCase()));
return virtualTable(model, 'main', objectsToDisplay);
return h('', [
tableHeader(model.object),
virtualTable(model, 'main', objectsToDisplay),
]);
}
return tableShow(model);
return h('', [
tableHeader(model.object),
tableShow(model),
]);
},
Failure: () => null, // Notification is displayed
})),
Expand Down Expand Up @@ -165,10 +181,45 @@ const statusBarRight = (model) => model.object.selected
*/
const tableShow = (model) =>
h('table.table.table-sm.text-no-select', [
h('thead', [h('tr', [h('th', 'Name')])]),
h('thead', [
h('tr', [
h('th', sortableTableHead({
order: model.object.sortBy.order,
icon: model.object.sortBy.icon,
label: 'Name',
sortOptions: [SortDirectionsEnum.ASC, SortDirectionsEnum.DESC],
onclick: (label, order, icon) => {
model.object.sortTree(label, 'name', order, icon);
},
})),
]),
]),
h('tbody', [treeRows(model)]),
]);

const tableHeader = (qcObject) =>
h('.flex-row.w-100', [
tableSearchInput(qcObject),
tableCollapseAll(qcObject),
]);

const tableCollapseAll = (qcObject) =>
h('button.btn.m2', {
title: 'Close whole tree',
onclick: () => qcObject.tree.closeAll(),
disabled: Boolean(qcObject.searchInput),
}, iconCollapseUp());

const tableSearchInput = (qcObject) =>
h('input.form-control.form-inline.m2.flex-grow', {
id: 'searchObjectTree',
placeholder: 'Search',
type: 'text',
value: qcObject.searchInput,
disabled: qcObject.queryingObjects ? true : false,
oninput: (e) => qcObject.search(e.target.value),
});

/**
* Shows a list of lines <tr> of objects
* @param {Model} model - root model of the application
Expand Down
16 changes: 15 additions & 1 deletion QualityControl/public/object/virtualTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* or submit itself to any jurisdiction.
*/

import { SortDirectionsEnum } from '../common/enums/columnSort.enum.js';
import { sortableTableHead } from '../common/sortButton.js';
import { h, iconBarChart } from '/js/src/index.js';

let ROW_HEIGHT = 33.6;
Expand Down Expand Up @@ -102,7 +104,19 @@ const objectFullRow = (model, item, location) =>
const tableHeader = () =>
h('table.table.table-sm.text-no-select', {
style: 'margin-bottom:0',
}, h('thead', [h('tr', [h('th', 'Name')])]));
}, h('thead', [
h('tr', [
h('th', sortableTableHead({
order: model.object.sortBy.order,
icon: model.object.sortBy.icon,
label: 'Name',
sortOptions: [SortDirectionsEnum.ASC, SortDirectionsEnum.DESC],
onclick: (label, order, icon) => {
model.object.sortTree(label, 'name', order, icon);
},
})),
]),
]));

/**
* Set styles of the floating table and its position inside the big div .tableLogsContentPlaceholder
Expand Down
Loading
Loading