[OHIF-193 + OHIF-313] (#1955)

* create context menu

* OHIF-193: Add context menu integration with measurement service

* OHIF-193: Update comments

* OHIF-CR Update: Add requested updates

* CR Update: Update measurement service remove function

* CR Update: Update casing

* CR Updates

* Update cornerstone measurments when the label is edited in the measurement service.

* Implement click outside behavior

* Address reviewer comments.

Co-authored-by: Rodrigo Antinarelli <rodrigoantinarelli@gmail.com>
Co-authored-by: igoroctaviano <igoroctaviano@gmail.com>
This commit is contained in:
authored and GitHub committed 2020-08-05 14:56:02 +02:00
1 parent 39e68f3193
commit 401a2e38c8
20 files changed
+575 -196

No files matched your search

@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Typography } from '@ohif/ui';
const ContextMenu = ({ items }) => {
return (
<div
className="relative bg-secondary-dark rounded z-50 block w-48"
onContextMenu={e => e.preventDefault()}
>
{items.map((item, index) => (
<div
key={index}
onClick={() => item.action(item)}
className="flex px-4 py-3 cursor-pointer items-center transition duration-300 hover:bg-primary-dark border-b border-primary-dark last:border-b-0"
>
<Typography>{item.label}</Typography>
</div>
))}
</div>
);
};
ContextMenu.propTypes = {
items: PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
actionType: PropTypes.string.isRequired,
action: PropTypes.func.isRequired,
})
).isRequired,
};
export default ContextMenu;
@@ -0,0 +1,49 @@
---
name: Context Menu
menu: General
route: components/contextMenu
---
import { Playground, Props } from 'docz';
import { ContextMenu } from '@ohif/ui';
# Context Menu
...
## Import
```javascript
import { ContextMenu } from '@ohif/ui';
```
<Playground>
{() => {
const items = [
{
label: 'Delete measurement',
actionType: 'Delete',
action: () => alert('Delete'),
},
{
label: 'Relabel',
actionType: 'setLabel',
action: () => alert('Relabel'),
},
{
label: 'Add Description',
actionType: 'setDescription',
action: () => alert('Add Description'),
},
];
return (
<div className="p-4">
<ContextMenu items={items} />
</div>
);
}}
</Playground>
## Properties
<Props of={ContextMenu} />
@@ -0,0 +1 @@
export { default } from './ContextMenu';
@@ -0,0 +1,44 @@
import { ContextMenu } from '@ohif/ui';
import PropTypes from 'prop-types';
import React from 'react';
const ContextMenuMeasurements = ({
onGetMenuItems,
onSetLabel,
onClose,
onDelete,
}) => {
const defaultMenuItems = [
{
label: 'Delete measurement',
actionType: 'Delete',
action: item => {
onDelete(item);
onClose();
},
value: {},
},
{
label: 'Add Label',
actionType: 'setLabel',
action: item => {
onSetLabel(item);
onClose();
},
value: {},
},
];
const menuItems = onGetMenuItems(defaultMenuItems);
return <ContextMenu items={menuItems} />;
};
ContextMenuMeasurements.propTypes = {
onClose: PropTypes.func.isRequired,
onSetLabel: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onGetMenuItems: PropTypes.func.isRequired,
};
export default ContextMenuMeasurements;
@@ -0,0 +1 @@
export { default } from './ContextMenuMeasurements';
+4
View File
@@ -1,5 +1,6 @@
import Button from './Button';
import ButtonGroup from './ButtonGroup';
import ContextMenu from './ContextMenu';
import DateRange from './DateRange';
import Dialog from './Dialog';
import Dropdown from './Dropdown';
@@ -41,6 +42,7 @@ import ThumbnailNoImage from './ThumbnailNoImage';
import ThumbnailTracked from './ThumbnailTracked';
import ThumbnailList from './ThumbnailList';
import ToolbarButton from './ToolbarButton';
import ContextMenuMeasurements from './ContextMenuMeasurements';
import ExpandableToolbarButton from './ExpandableToolbarButton';
import ListMenu from './ListMenu';
import Tooltip from './Tooltip';
@@ -55,6 +57,7 @@ import ViewportPane from './ViewportPane';
export {
Button,
ButtonGroup,
ContextMenu,
DateRange,
Dialog,
Dropdown,
@@ -99,6 +102,7 @@ export {
ThumbnailTracked,
ThumbnailList,
ToolbarButton,
ContextMenuMeasurements,
Tooltip,
TooltipClipboard,
Typography,
@@ -4,7 +4,7 @@ import React, {
useContext,
useCallback,
useEffect,
useRef
useRef,
} from 'react';
import PropTypes from 'prop-types';
@@ -154,6 +154,7 @@ const DialogProvider = ({ children, service }) => {
onStart,
onStop,
onDrag,
onClickOutside,
showOverlay,
} = dialog;
@@ -169,7 +170,7 @@ const DialogProvider = ({ children, service }) => {
disabled={!isDraggable}
position={position}
defaultPosition={position}
bounds='parent'
bounds="parent"
onStart={event => {
const e = event || window.event;
const target = e.target || e.srcElement;
@@ -225,16 +226,27 @@ const DialogProvider = ({ children, service }) => {
const background = 'bg-black bg-opacity-50';
const overlay = 'fixed z-50 left-0 top-0 w-full h-full overflow-auto';
return (
<div
className={classNames(overlay, background)}
key={id}
>
<div className={classNames(overlay, background)} key={id}>
{component}
</div>
);
};
return showOverlay ? withOverlay(dragableItem()) : dragableItem();
let result = dragableItem();
if (showOverlay) {
result = withOverlay(result);
}
if (typeof onClickOutside === 'function') {
result = (
<OutsideAlerter onClickOutside={onClickOutside}>
{result}
</OutsideAlerter>
);
}
return result;
});
/**
@@ -253,7 +265,7 @@ const DialogProvider = ({ children, service }) => {
};
const onKeyDownHandler = event => {
if (event.key === "Escape") {
if (event.key === 'Escape') {
dismissAll();
}
};
@@ -262,11 +274,11 @@ const DialogProvider = ({ children, service }) => {
return (
<DialogContext.Provider value={{ create, dismiss, dismissAll, isEmpty }}>
{!isEmpty() &&
<div className='w-full h-full absolute' onKeyDown={onKeyDownHandler}>
{!isEmpty() && (
<div className="w-full h-full absolute" onKeyDown={onKeyDownHandler}>
{renderDialogs()}
</div>
}
)}
{children}
</DialogContext.Provider>
);
@@ -302,3 +314,29 @@ DialogProvider.propTypes = {
};
export default DialogProvider;
function OutsideAlerter(props) {
const wrapperRef = useRef(null);
useEffect(() => {
/**
* Alert if clicked on outside of element
*/
function handleInteractionOutside(event) {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
props.onClickOutside();
}
}
// Bind the event listener
document.addEventListener('mousedown', handleInteractionOutside);
document.addEventListener('touchstart', handleInteractionOutside);
return () => {
// Unbind the event listener on clean up
document.removeEventListener('mousedown', handleInteractionOutside);
document.removeEventListener('touchstart', handleInteractionOutside);
};
}, [wrapperRef]);
return <div ref={wrapperRef}>{props.children}</div>;
}