import { Types } from '@ohif/ui'; import { Menu, SelectorProps, MenuItem, ContextMenuProps } from './types'; type ContextMenuItem = Types.ContextMenuItem; /** * Finds menu by menu id * * @returns Menu having the menuId */ export function findMenuById(menus: Menu[], menuId?: string): Menu { if (!menuId) { return; } return menus.find(menu => menu.id === menuId); } /** * Default finding menu method. This method will go through * the list of menus until it finds the first one which * has no selector, OR has the selector, when applied to the * check props, return true. * The selectorProps are a set of provided properties which can be * passed into the selector function to determine when to display a menu. * For example, a selector function of: * `({displayset}) => displaySet?.SeriesDescription?.indexOf?.('Left')!==-1 * would match series descriptions containing 'Left'. * * @param {Object[]} menus List of menus * @param {*} subProps * @returns */ export function findMenuDefault(menus: Menu[], subProps: Record): Menu { if (!menus) { return null; } return menus.find(menu => !menu.selector || menu.selector(subProps.selectorProps)); } /** * Finds the menu to be used for different scenarios: * This will first look for a subMenu with the specified subMenuId * Next it will look for the first menu whose selector returns true. * * @param menus - List of menus * @param props - root props * @param menuIdFilter - menu id identifier (to be considered on selection) * This is intended to support other types of filtering in the future. */ export function findMenu(menus: Menu[], props?: Types.IProps, menuIdFilter?: string) { const { subMenu } = props; function* findMenuIterator() { yield findMenuById(menus, menuIdFilter || subMenu); yield findMenuDefault(menus, props); } const findIt = findMenuIterator(); let current = findIt.next(); let menu = current.value; while (!current.done) { menu = current.value; if (menu) { findIt.return(); } current = findIt.next(); } return menu; } /** * Returns the menu from a list of possible menus, based on the actual state of component props and tool data nearby. * This uses the findMenu command above to first find the appropriate * menu, and then it chooses the actual contents of that menu. * A menu item can be optional by implementing the 'selector', * which will be called with the selectorProps, and if it does not return true, * then the item is excluded. * * Other menus can be delegated to by setting the delegating value to * a string id for another menu. That menu's content will replace the * current menu item (only if the item would be included). * * This allows single id menus to be chosen by id, but have varying contents * based on the delegated menus. * * Finally, for each item, the adaptItem call is made. This allows * items to modify themselves before being displayed, such as * incorporating additional information from translation sources. * See the `test-mode` examples for details. * * @param selectorProps * @param {*} event event that originates the context menu * @param {*} menus List of menus * @param {*} menuIdFilter * @returns */ export function getMenuItems( selectorProps: SelectorProps, event: Event, menus: Menu[], menuIdFilter?: string ): MenuItem[] | void { // Include both the check props and the ...check props as one is used // by the child menu and the other used by the selector function const subProps = { selectorProps, event }; const menu = findMenu(menus, subProps, menuIdFilter); if (!menu) { return undefined; } if (!menu.items) { console.warn('Must define items in menu', menu); return []; } let menuItems = []; menu.items.forEach(item => { const { delegating, selector, subMenu } = item; if (!selector || selector(selectorProps)) { if (delegating) { menuItems = [...menuItems, ...getMenuItems(selectorProps, event, menus, subMenu)]; } else { const toAdd = adaptItem(item, subProps); menuItems.push(toAdd); } } }); return menuItems; } /** * Returns item adapted to be consumed by ContextMenu component * and then goes through the item to add action behaviour for clicking the item, * making it compatible with the default ContextMenu display. * * @param {Object} item * @param {Object} subProps * @returns a MenuItem that is compatible with the base ContextMenu * This requires having a label and set of actions to be called. */ export function adaptItem(item: MenuItem, subProps: ContextMenuProps): ContextMenuItem { const newItem: ContextMenuItem = { ...item, value: subProps.selectorProps?.value, }; if (item.actionType === 'ShowSubMenu' && !newItem.iconRight) { newItem.iconRight = 'chevron-menu'; } if (!item.action) { newItem.action = (itemRef, componentProps) => { const { event = {} } = componentProps; const { detail = {} } = event; newItem.element = detail.element; componentProps.onClose(); const action = componentProps[`on${itemRef.actionType || 'Default'}`]; if (action) { action.call(componentProps, newItem, itemRef, subProps); } else { console.warn('No action defined for', itemRef); } }; } return newItem; }