Skip to content
Merged
Changes from all commits
Commits
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
130 changes: 78 additions & 52 deletions Control/public/common/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,68 +18,94 @@ import {
iconCalculator, iconLockLocked, iconWrench
} from '/js/src/icons.js';

const HEADER = 'header';
const ITEM = 'item';
/**
* Sidebar is the main navigation menu to choose pages though QueryRouter instance
* @param {Object} model
* @return {vnode}
* Ordered list of sidebar menu entries
* @type {Array<Object>}
*/
export default (model) => h('.absolute-fill scroll-y.flex-column', [
h('h5.menu-title-large.mh1',
model.sideBarMenu ? 'Environments' : 'ENVS'),
menuItem(model, 'Global Runs', 'newEnvironment', iconPlus()),
menuItem(model, 'Calibration Runs', 'calibrationRuns', iconWrench()),
menuItem(model, 'Active Environments', 'environments', iconGridTwoUp()),
menuItem(model, 'Create', 'newEnvironmentAdvanced', iconPlus()),
menuItem(model, 'Task list', 'taskList', iconGridThreeUp()),
h('h5.menu-title-large.mh1',
model.sideBarMenu ? 'Hardware' : 'HDW'),
menuItem(model, 'Links', 'configuration', iconCog()),
menuItem(model, 'FLPs', 'hardware', iconCalculator()),
h('h5.menu-title-large.mh1',
model.sideBarMenu ? 'Admin' : 'ADM'),
menuItem(model, 'Locks', 'locks', iconLockLocked()),
h('', {style: 'flex-grow:1'}), // empty item to fill in space
menuItem(model, 'About', 'about', iconExcerpt()),
collapseSidebarMenuItem(model),
]);
const sideBarMenuConfiguration = [
{ type: HEADER, title: 'Environments', acronym: 'ENVS' },
{ type: ITEM, title: 'Global Runs', navigateToPage: 'newEnvironment', icon: iconPlus },
{ type: ITEM, title: 'Calibration Runs', navigateToPage: 'calibrationRuns', icon: iconWrench },
{ type: ITEM, title: 'Active Environments', navigateToPage: 'environments', icon: iconGridTwoUp },
{ type: ITEM, title: 'Locks', navigateToPage: 'locks', icon: iconLockLocked },
{ type: HEADER, title: 'Expert', acronym: 'EXP' },
{ type: ITEM, title: 'Create', navigateToPage: 'newEnvironmentAdvanced', icon: iconPlus },
{ type: ITEM, title: 'Task list', navigateToPage: 'taskList', icon: iconGridThreeUp },
{ type: HEADER, title: 'Hardware', acronym: 'HDW' },
{ type: ITEM, title: 'Links', navigateToPage: 'configuration', icon: iconCog },
{ type: ITEM, title: 'FLPs', navigateToPage: 'hardware', icon: iconCalculator },
];

/**
* Create a menu-item
* @param {Object} model
* @param {string} title
* @param {string} pageParam - where onclick() should navigate to
* @param {icon} icon
* Sidebar is the main navigation menu to choose pages though QueryRouter instance
* @param {Model} model - application model
* @return {vnode}
*/
const menuItem = (model, title, pageParam, icon) =>
h('a.menu-item', {
title: title,
style: 'display: flex',
href: `?page=${pageParam}`,
onclick: (e) => model.router.handleLinkEvent(e),
class: model.router.params.page === pageParam ? 'selected' : ''
}, [
h('span', icon),
model.sideBarMenu && itemMenuText(title)
export default (model) => {
const { page: currentPage = '' } = model?.router?.params ?? {};
const { sideBarMenu: isSidebarVisible = true } = model;

/**
* Onclick handler to delegate link handling to the QueryRouter
* @param {Event} page - click event
*/
const onclick = (page) => model.router.go(`?page=${page}`);

return h('.absolute-fill.scroll-y.flex-column', [
sideBarMenuConfiguration.map((menuEntry) => {
const { type } = menuEntry;
if (type === HEADER) {
const { title, acronym } = menuEntry;
const label = isSidebarVisible ? title : acronym;
return menuHeader(label);
} else if (type === ITEM) {
const { title, navigateToPage, icon } = menuEntry;
return menuItem({
...(isSidebarVisible && { title }),
icon,
onclick: () => onclick(navigateToPage),
classes: currentPage === navigateToPage ? '.selected' : ''
});
}
}),
h('', { style: 'flex-grow:1' }), // empty item to fill in space
menuItem({
...(isSidebarVisible && { title: 'About' }),
icon: iconExcerpt,
onclick: () => onclick('about'),
classes: currentPage === 'about' ? '.selected' : ''
}),
menuItem({
...(isSidebarVisible && { title: 'Collapse Sidebar' }),
icon: isSidebarVisible ? iconMediaSkipBackward : iconMediaSkipForward,
onclick: () => model.toggleSideBarMenu(),
}),
]);
};

/**
* Show link to status page
* @param {Object} model
* @return {vnode}
*/
const collapseSidebarMenuItem = (model) =>
h('a.menu-item', {
title: 'Toggle Sidebar',
onclick: () => model.toggleSideBarMenu(),
}, model.sideBarMenu ?
[iconMediaSkipBackward(), itemMenuText('Collapse Sidebar')]
: iconMediaSkipForward(),
);
* Create a non-clickable menu header to separate menu sections
* @param {string} title - full title to be displayed when sidebar extended
* @return {vnode}
*/
const menuHeader = (title) => h(`h5.menu-title-large.mh1$.text-center`, title);

/**
* Display text with item properties
* @param {string} text
* Create a clickable menu-item vnode
* @param {function} icon - function to be used to build a vnode with icon to be displayed
* @param {string} [title = undefined] - full title to be displayed when sidebar extended
* @param {function} [onclick = () => { }] - onclick handler
* @param {string} [classes = ''] - additional classes to be added to the menu item (e.g. .selected)
* @return {vnode}
*/
const itemMenuText = (text) => h('span.ph2', text);
const menuItem = ({ icon, title = undefined, onclick = () => { }, classes = '' }) => {
return h(`a.menu-item.flex-row${classes}`, {
title,
onclick,
}, [
h('span', icon()),
title && h('span.ph2', title)
]);
};