feat: Allow configurable context menus (#2894)
* feat: Context menu feat: Custom context menu Adding documentation PR updates * fix: Capture tool exception * PR updates * Add fully worked out examples in the basic test mode/extension * Fix the menu display * fix: Make the commands on clicks much more configurable * Wait for load before double clicking * docs * PR changes - nothing functional, just moving things endlessly * PR comments * PR changes - rename the default context menu * Renamed the cornerstoneContextMenu to measurementsContextMenu * Add chevron right to the sub-menus
This commit is contained in:
1 parent
4e94b588a7
commit
bc642fd2b6
40 files changed
+1472
-280
No files matched your search
@@ -1,20 +1,32 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Typography } from '../';
|
||||
import Typography from '../Typography';
|
||||
import Icon from '../Icon';
|
||||
|
||||
const ContextMenu = ({ items }) => {
|
||||
const ContextMenu = ({ items, ...props }) => {
|
||||
if (!items) {
|
||||
console.warn('No items for context menu');
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
data-cy="context-menu"
|
||||
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)}
|
||||
data-cy="context-menu-item"
|
||||
onClick={() => item.action(item, props)}
|
||||
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>
|
||||
<Typography>
|
||||
{item.label}
|
||||
{item.iconRight && (
|
||||
<Icon name={item.iconRight} className="inline" />
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -25,7 +37,6 @@ ContextMenu.propTypes = {
|
||||
items: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
label: PropTypes.string.isRequired,
|
||||
actionType: PropTypes.string.isRequired,
|
||||
action: PropTypes.func.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
|
||||
File renamed without changes.
@@ -1,44 +0,0 @@
|
||||
import { ContextMenu } from '../';
|
||||
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;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './ContextMenuMeasurements';
|
||||
@@ -1,36 +1,52 @@
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classNames from 'classnames';
|
||||
import { NavBar, Svg, Icon, IconButton, Dropdown } from '../';
|
||||
|
||||
function Header({ children, menuOptions, isReturnEnabled, onClickReturnButton, isSticky, WhiteLabeling }) {
|
||||
function Header({
|
||||
children,
|
||||
menuOptions,
|
||||
isReturnEnabled,
|
||||
onClickReturnButton,
|
||||
isSticky,
|
||||
WhiteLabeling,
|
||||
...props
|
||||
}): ReactNode {
|
||||
const { t } = useTranslation('Header');
|
||||
|
||||
// TODO: this should be passed in as a prop instead and the react-router-dom
|
||||
// dependency should be dropped
|
||||
const onClickReturn = () => {
|
||||
if (isReturnEnabled && onClickReturnButton) {
|
||||
onClickReturnButton()
|
||||
onClickReturnButton();
|
||||
}
|
||||
};
|
||||
|
||||
const CustomLogo = (React) => {
|
||||
return WhiteLabeling.createLogoComponentFn(React)
|
||||
}
|
||||
|
||||
return (
|
||||
<NavBar className='justify-between border-b-4 border-black' isSticky={isSticky}>
|
||||
<NavBar
|
||||
className="justify-between border-b-4 border-black"
|
||||
isSticky={isSticky}
|
||||
>
|
||||
<div className="flex justify-between flex-1">
|
||||
<div className="flex items-center">
|
||||
{/* // TODO: Should preserve filter/sort
|
||||
// Either injected service? Or context (like react router's `useLocation`?) */}
|
||||
<div
|
||||
className={classNames("inline-flex items-center mr-3", isReturnEnabled && 'cursor-pointer')}
|
||||
className={classNames(
|
||||
'inline-flex items-center mr-3',
|
||||
isReturnEnabled && 'cursor-pointer'
|
||||
)}
|
||||
onClick={onClickReturn}
|
||||
>
|
||||
{isReturnEnabled && <Icon name="chevron-left" className="w-8 text-primary-active" />}
|
||||
<div className="ml-4">{WhiteLabeling ? CustomLogo(React) : <Svg name="logo-ohif" />}</div>
|
||||
{isReturnEnabled && (
|
||||
<Icon name="chevron-left" className="w-8 text-primary-active" />
|
||||
)}
|
||||
<div className="ml-4">
|
||||
{WhiteLabeling?.createLogoComponentFn?.(React, props) || (
|
||||
<Svg name="logo-ohif" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center">{children}</div>
|
||||
@@ -40,7 +56,7 @@ function Header({ children, menuOptions, isReturnEnabled, onClickReturnButton, i
|
||||
</span>
|
||||
<Dropdown id="options" showDropdownIcon={false} list={menuOptions}>
|
||||
<IconButton
|
||||
id={"options-settings-icon"}
|
||||
id={'options-settings-icon'}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
@@ -49,7 +65,7 @@ function Header({ children, menuOptions, isReturnEnabled, onClickReturnButton, i
|
||||
<Icon name="settings" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
id={"options-chevron-down-icon"}
|
||||
id={'options-chevron-down-icon'}
|
||||
variant="text"
|
||||
color="inherit"
|
||||
size="initial"
|
||||
@@ -76,12 +92,12 @@ Header.propTypes = {
|
||||
isReturnEnabled: PropTypes.bool,
|
||||
isSticky: PropTypes.bool,
|
||||
onClickReturnButton: PropTypes.func,
|
||||
WhiteLabeling: PropTypes.element,
|
||||
WhiteLabeling: PropTypes.object,
|
||||
};
|
||||
|
||||
Header.defaultProps = {
|
||||
isReturnEnabled: true,
|
||||
isSticky: false
|
||||
isSticky: false,
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@@ -48,7 +48,6 @@ 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';
|
||||
@@ -134,7 +133,6 @@ export {
|
||||
ThumbnailTracked,
|
||||
ThumbnailList,
|
||||
ToolbarButton,
|
||||
ContextMenuMeasurements,
|
||||
Tooltip,
|
||||
TooltipClipboard,
|
||||
Typography,
|
||||
|
||||
@@ -94,7 +94,6 @@ export {
|
||||
ThumbnailTracked,
|
||||
ThumbnailList,
|
||||
ToolbarButton,
|
||||
ContextMenuMeasurements,
|
||||
Tooltip,
|
||||
TooltipClipboard,
|
||||
Typography,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export type ContextMenuItem = {
|
||||
// A label to show for the item.
|
||||
label: string;
|
||||
// An icon to show the on right of the text - typically used for submenus
|
||||
iconRight?: string;
|
||||
action: (item, component) => void;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export type Predicate = (props: Record<string, unknown>) => boolean;
|
||||
@@ -4,6 +4,9 @@ import { PresentationIds } from '../contextProviders/getPresentationIds';
|
||||
|
||||
// A few miscellaneous types declared inline here.
|
||||
|
||||
export * from './Predicate';
|
||||
export * from './ContextMenuItem';
|
||||
|
||||
/**
|
||||
* StringNumber often comes back from DICOMweb for integer valued items.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user