2023-03-22 17:45:28 -04:00
import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder' ;
import ContextMenu from '../../../../platform/ui/src/components/ContextMenu/ContextMenu' ;
import { CommandsManager , ServicesManager , Types } from '@ohif/core' ;
import { Menu , MenuItem , Point , ContextMenuProps } from './types' ;
/**
* The context menu controller is a helper class that knows how
* to manage context menus based on the UI Customization Service.
* There are a few parts to this:
* 1. Basic controls to manage displaying and hiding context menus
* 2. Menu selection services, which use the UI customization service
* to choose which menu to display
* 3. Menu item adapter services to convert menu items into displayable and actionable items.
*
* The format for a menu is defined in the exported type MenuItem
*/
export default class ContextMenuController {
commandsManager : CommandsManager ;
services : Types.Services ;
menuItems : Menu [] | MenuItem [];
2023-09-01 16:19:39 -04:00
constructor ( servicesManager : ServicesManager , commandsManager : CommandsManager ) {
2023-03-22 17:45:28 -04:00
this . services = servicesManager . services as Obj ;
this . commandsManager = commandsManager ;
}
closeContextMenu() {
this . services . uiDialogService . dismiss ({ id : 'context-menu' });
}
/**
* Figures out which context menu is appropriate to display and shows it.
*
* @param contextMenuProps - the context menu properties, see ./types.ts
* @param viewportElement - the DOM element this context menu is related to
* @param defaultPointsPosition - a default position to show the context menu
*/
showContextMenu (
contextMenuProps : ContextMenuProps ,
viewportElement ,
defaultPointsPosition
) : void {
if ( ! this . services . uiDialogService ) {
console . warn ( 'Unable to show dialog; no UI Dialog Service available.' );
return ;
}
const { event , subMenu , menuId , menus , selectorProps } = contextMenuProps ;
console . log ( 'Getting items from' , menus );
const items = ContextMenuItemsBuilder . getMenuItems (
selectorProps || contextMenuProps ,
event ,
menus ,
menuId
);
this . services . uiDialogService . dismiss ({ id : 'context-menu' });
this . services . uiDialogService . create ({
id : 'context-menu' ,
isDraggable : false ,
preservePosition : false ,
preventCutOf : true ,
defaultPosition : ContextMenuController._getDefaultPosition (
defaultPointsPosition ,
event ? . detail ,
viewportElement
),
event ,
content : ContextMenu ,
// This naming is part of hte uiDialogService convention
// Clicking outside simpy closes the dialog box.
2023-09-01 16:19:39 -04:00
onClickOutside : () => this . services . uiDialogService . dismiss ({ id : 'context-menu' }),
2023-03-22 17:45:28 -04:00
contentProps : {
items ,
selectorProps ,
menus ,
event ,
subMenu ,
eventData : event?.detail ,
onClose : () => {
this . services . uiDialogService . dismiss ({ id : 'context-menu' });
},
/**
* Displays a sub-menu, removing this menu
* @param {*} item
* @param {*} itemRef
* @param {*} subProps
*/
onShowSubMenu : ( item , itemRef , subProps ) => {
if ( ! itemRef . subMenu ) {
console . warn ( 'No submenu defined for' , item , itemRef , subProps );
return ;
}
this . showContextMenu (
{
... contextMenuProps ,
menuId : itemRef.subMenu ,
},
viewportElement ,
defaultPointsPosition
);
},
// Default is to run the specified commands.
onDefault : ( item , itemRef , subProps ) => {
this . commandsManager . run ( item , {
... selectorProps ,
... itemRef ,
subProps ,
});
},
},
});
}
static getDefaultPosition = () : Point => {
return {
x : 0 ,
y : 0 ,
};
};
static _getEventDefaultPosition = eventDetail => ({
x : eventDetail && eventDetail . currentPoints . client [ 0 ],
y : eventDetail && eventDetail . currentPoints . client [ 1 ],
});
static _getElementDefaultPosition = element => {
if ( element ) {
const boundingClientRect = element . getBoundingClientRect ();
return {
x : boundingClientRect.x ,
y : boundingClientRect.y ,
};
}
return {
x : undefined ,
y : undefined ,
};
};
static _getCanvasPointsPosition = ( points = [], element ) => {
const viewerPos = ContextMenuController . _getElementDefaultPosition ( element );
for ( let pointIndex = 0 ; pointIndex < points . length ; pointIndex ++ ) {
const point = {
x : points [ pointIndex ][ 0 ] || points [ pointIndex ][ 'x' ],
y : points [ pointIndex ][ 1 ] || points [ pointIndex ][ 'y' ],
};
if (
ContextMenuController . _isValidPosition ( point ) &&
ContextMenuController . _isValidPosition ( viewerPos )
) {
return {
x : point.x + viewerPos . x ,
y : point.y + viewerPos . y ,
};
}
}
};
static _isValidPosition = ( source ) : boolean => {
2023-09-01 16:19:39 -04:00
return source && typeof source . x === 'number' && typeof source . y === 'number' ;
2023-03-22 17:45:28 -04:00
};
/**
* Returns the context menu default position. It look for the positions of: canvasPoints (got from selected), event that triggers it, current viewport element
*/
static _getDefaultPosition = ( canvasPoints , eventDetail , viewerElement ) => {
function * getPositionIterator() {
2023-09-01 16:19:39 -04:00
yield ContextMenuController . _getCanvasPointsPosition ( canvasPoints , viewerElement );
2023-03-22 17:45:28 -04:00
yield ContextMenuController . _getEventDefaultPosition ( eventDetail );
yield ContextMenuController . _getElementDefaultPosition ( viewerElement );
yield ContextMenuController . getDefaultPosition ();
}
const positionIterator = getPositionIterator ();
let current = positionIterator . next ();
let position = current . value ;
while ( ! current . done ) {
position = current . value ;
if ( ContextMenuController . _isValidPosition ( position )) {
positionIterator . return ();
}
current = positionIterator . next ();
}
return position ;
};
}