Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

study item dropdown menu customizations #4668

Open
wants to merge 25 commits into
base: feat/multi-monitor-take2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d074c25
Merge branch 'master' of github.com:OHIF/Viewers into feat/multi-moni…
sedghi Jan 8, 2025
c82c3ec
fix(ContextMenu): Ensure annotation visibility and locking checks onl…
sedghi Jan 8, 2025
1d8a83e
refactor(ViewportActionBar): Change import of PatientInfo to named im…
sedghi Jan 8, 2025
a9e802e
feat(ThumbnailMenu): Implement dropdown menu for thumbnail actions an…
sedghi Jan 8, 2025
a5e8c0e
feat(ThumbnailMenu): Enhance thumbnail interactions with customizable…
sedghi Jan 8, 2025
74065e8
feat(StudyMenu): Introduce StudyMenuItems for enhanced study interact…
sedghi Jan 8, 2025
c8f1dc0
feat(MultiMonitor): Enhance multi-monitor functionality and command e…
sedghi Jan 9, 2025
ba4d42c
fix(MultiMonitorService): Correct parameter casing for StudyInstanceUIDs
sedghi Jan 9, 2025
0eafda5
feat(StudyBrowser): Enhance study and thumbnail menu items with custo…
sedghi Jan 9, 2025
4a5fefc
feat(Customization): Add 'View Options' menu with 2D and 3D view sele…
sedghi Jan 9, 2025
d5d6c72
feat(Customization): Implement dynamic dropdown menu rendering for st…
sedghi Jan 9, 2025
f8c816a
refactor(PanelStudyBrowserTracking): Add comment for clarity in menu …
sedghi Jan 9, 2025
a420c08
refactor(commandsModule): Remove unused getStudiesfromDisplaySets fun…
sedghi Jan 9, 2025
47b7de7
refactor(Customization): Update dropdown menu items and protocols
sedghi Jan 9, 2025
5f22515
refactor(Customization): Update dropdown menu item styling and remove…
sedghi Jan 9, 2025
48f1131
refactor(Customization): Improve dropdown menu item handling and upda…
sedghi Jan 9, 2025
13e1fd8
Merge branch 'feat/multi-monitor-take2' of github.com:OHIF/Viewers in…
sedghi Jan 9, 2025
5d8cb4c
fix: Mostly preserve the settings on refresh of source window
wayfarer3130 Jan 9, 2025
336c24c
fix: Preserve destination measurements
wayfarer3130 Jan 9, 2025
02fedce
Cleanup
wayfarer3130 Jan 9, 2025
85fbb38
refactor(Customization): Update dropdown menu item labels and improve…
sedghi Jan 10, 2025
612ce4f
Merge remote-tracking branch 'origin/master' into feat/mm-some-custom…
wayfarer3130 Jan 10, 2025
1324e4e
Merge remote-tracking branch 'origin/feat/multi-monitor-take2' into f…
wayfarer3130 Jan 10, 2025
39f5cd6
fix: Externalize the more drop down menu customizability
wayfarer3130 Jan 10, 2025
d43db73
Fix unit test
wayfarer3130 Jan 10, 2025
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
7 changes: 1 addition & 6 deletions extensions/cornerstone/src/panels/PanelMeasurement.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import React, { useEffect, useRef, useState } from 'react';
import { utils } from '@ohif/core';
import { useViewportGrid } from '@ohif/ui-next';
import { MeasurementTable } from '@ohif/ui-next';
import debounce from 'lodash.debounce';
import { useMeasurements } from '../hooks/useMeasurements';

const {
filterAdditionalFindings: filterAdditionalFinding,
filterOr,
filterAny,
} = utils.MeasurementFilters;
const { filterAdditionalFindings: filterAdditionalFinding, filterAny } = utils.MeasurementFilters;

export type withAppAndFilters = withAppTypes & {
measurementFilter: (item) => boolean;
Expand Down
86 changes: 86 additions & 0 deletions extensions/default/src/Components/MoreDropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
Icons,
Button,
} from '@ohif/ui-next';

/**
* The default sub-menu appearance and setup is defined here, but this can be
* replaced by
*/
const getMenuItemsDefault = ({ commandsManager, items, servicesManager, ...props }) => {
const { customizationService } = servicesManager.services;

// This allows replacing the default child item for menus, whereas the entire
// getMenuItems can also be replaced by providing it to the MoreDropdownMenu
const menuContent = customizationService.getCustomization('ohif.menuContent');

return (
<DropdownMenuContent
hideWhenDetached
align="start"
onClick={e => {
e.stopPropagation();
e.preventDefault();
}}
>
{items?.map(item =>
menuContent.content({
key: item.id,
item,
commandsManager,
servicesManager,
...props,
})
)}
</DropdownMenuContent>
);
};

/**
* The component provides a ... sub-menu for various components which appears
* on hover over the main component.
*
* @param bindProps - properties to define the sub-menu
* @returns Component bound to the bindProps
*/
export default function MoreDropdownMenu(bindProps) {
const {
menuItemsKey,
getMenuItems = getMenuItemsDefault,
commandsManager,
servicesManager,
} = bindProps;
const { customizationService } = servicesManager.services;

const items = customizationService.getCustomization(menuItemsKey)?.value;

function BoundMoreDropdownMenu(props) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="hidden group-hover:inline-flex data-[state=open]:inline-flex"
onClick={e => {
e.preventDefault();
e.stopPropagation();
}}
>
<Icons.More />
</Button>
</DropdownMenuTrigger>
{getMenuItems({
...props,
commandsManager: commandsManager,
servicesManager: servicesManager,
items,
})}
</DropdownMenu>
);
}
return BoundMoreDropdownMenu;
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ export default class ContextMenuController {

const { locking, visibility } = CsAnnotation;
const targetAnnotationId = selectorProps?.nearbyToolData?.annotationUID as string;

if (targetAnnotationId) {
const isLocked = locking.isAnnotationLocked(
annotationManager.getAnnotation(targetAnnotationId)
);
const isLocked = locking.isAnnotationLocked(targetAnnotationId);
const isVisible = visibility.isAnnotationVisible(targetAnnotationId);

if (isLocked) {
console.warn('Annotation is locked.');
if (isLocked || !isVisible) {
console.warn(`Annotation is ${isLocked ? 'locked' : 'not visible'}.`);
return;
}
}
Expand Down
28 changes: 21 additions & 7 deletions extensions/default/src/Panels/StudyBrowser/PanelStudyBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import { useNavigate } from 'react-router-dom';
import { Separator } from '@ohif/ui-next';
import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader';
import { defaultActionIcons, defaultViewPresets } from './constants';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
Icons,
Button,
} from '@ohif/ui-next';
import MoreDropdownMenu from '../../Components/MoreDropdownMenu';

const { sortStudyInstances, formatDate, createStudyBrowserTabs } = utils;

Expand Down Expand Up @@ -280,10 +288,6 @@ function PanelStudyBrowser({

const activeDisplaySetInstanceUIDs = viewports.get(activeViewportId)?.displaySetInstanceUIDs;

const onThumbnailContextMenu = (commandName, options) => {
commandsManager.runCommand(commandName, options);
};

return (
<>
<>
Expand All @@ -304,16 +308,26 @@ function PanelStudyBrowser({
tabs={tabs}
servicesManager={servicesManager}
activeTabName={activeTabName}
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
activeDisplaySetInstanceUIDs={activeDisplaySetInstanceUIDs}
expandedStudyInstanceUIDs={expandedStudyInstanceUIDs}
onClickStudy={_handleStudyClick}
onClickTab={clickedTabName => {
setActiveTabName(clickedTabName);
}}
onClickThumbnail={() => {}}
onDoubleClickThumbnail={onDoubleClickThumbnailHandler}
activeDisplaySetInstanceUIDs={activeDisplaySetInstanceUIDs}
showSettings={actionIcons.find(icon => icon.id === 'settings').value}
viewPresets={viewPresets}
onThumbnailContextMenu={onThumbnailContextMenu}
ThumbnailMenuItems={MoreDropdownMenu({
commandsManager,
servicesManager,
menuItemsKey: 'studyBrowser.thumbnailMenuItems',
})}
StudyMenuItems={MoreDropdownMenu({
commandsManager,
servicesManager,
menuItemsKey: 'studyBrowser.studyMenuItems',
})}
/>
</>
);
Expand Down
135 changes: 135 additions & 0 deletions extensions/default/src/getCustomizationModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import DataSourceConfigurationComponent from './Components/DataSourceConfigurati
import { GoogleCloudDataSourceConfigurationAPI } from './DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI';
import { utils } from '@ohif/core';
import studyBrowserContextMenu from './customizations/studyBrowserContextMenu';
import {
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuPortal,
DropdownMenuSubContent,
DropdownMenuItem,
Icons,
} from '@ohif/ui-next';

const formatDate = utils.formatDate;

Expand Down Expand Up @@ -202,6 +210,133 @@ export default function getCustomizationModule({ servicesManager, extensionManag
},
],
},
{
id: 'studyBrowser.thumbnailMenuItems',
value: [
{
id: 'tagBrowser',
label: 'Tag Browser',
iconName: 'DicomTagBrowser',
commands: 'openDICOMTagViewer',
},
],
},
{
id: 'ohif.menuContent',
content: function (props) {
const { item, commandsManager, servicesManager, ...rest } = props;

// If item has sub-items, render a submenu
if (item.items) {
return (
<DropdownMenuSub>
<DropdownMenuSubTrigger className="gap-[6px]">
{item.iconName && (
<Icons.ByName
name={item.iconName}
className="-ml-1"
/>
)}
{item.label}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent>
{item.items.map(subItem => this.content({ ...props, item: subItem }))}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
);
}

// Regular menu item
const isDisabled = item.selector && !item.selector({ servicesManager });

return (
<DropdownMenuItem
disabled={isDisabled}
onSelect={() => {
commandsManager.runAsync(item.commands, {
...item.commandOptions,
...rest,
});
}}
className="gap-[6px]"
>
{item.iconName && (
<Icons.ByName
name={item.iconName}
className="-ml-1"
/>
)}
{item.label}
</DropdownMenuItem>
);
},
},
{
id: 'studyBrowser.studyMenuItems',
customizationType: 'ohif.menuContent',
value: [
{
id: 'applyHangingProtocol',
label: 'Apply Hanging Protocol',
iconName: 'ViewportViews',
items: [
{
id: 'applyDefaultProtocol',
label: 'Default',
commands: [
'loadStudy',
{
commandName: 'setHangingProtocol',
commandOptions: {
protocolId: 'default',
},
},
],
},
{
id: 'applyMPRProtocol',
label: '2x2 Grid',
commands: [
'loadStudy',
{
commandName: 'setHangingProtocol',
commandOptions: {
protocolId: '@ohif/mnGrid',
},
},
],
},
],
},
{
id: 'showInOtherMonitor',
label: 'Launch On Second Monitor',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is actually supposed to be other monitor since there might be three monitors, and it might be executed on the second monitor already, causing it to launch on the first monitor.

iconName: 'DicomTagBrowser',
// we should use evaluator for this, as these are basically toolbar buttons
selector: ({ servicesManager }) => {
const { multiMonitorService } = servicesManager.services;
return multiMonitorService.isMultimonitor;
},
commands: {
commandName: 'multimonitor',
commandOptions: {
hashParams: '&hangingProtocolId=@ohif/mnGrid8',
commands: [
'loadStudy',
{
commandName: 'setHangingProtocol',
commandOptions: {
protocolId: '@ohif/mnGrid8',
},
},
],
},
},
},
],
},
],
},
];
Expand Down
2 changes: 2 additions & 0 deletions extensions/default/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import promptLabelAnnotation from './utils/promptLabelAnnotation';
import usePatientInfo from './hooks/usePatientInfo';
import { PanelStudyBrowserHeader } from './Panels/StudyBrowser/PanelStudyBrowserHeader';
import * as utils from './utils';
import MoreDropdownMenu from './Components/MoreDropdownMenu';

const defaultExtension: Types.Extensions.Extension = {
/**
Expand Down Expand Up @@ -102,4 +103,5 @@ export {
usePatientInfo,
PanelStudyBrowserHeader,
utils,
MoreDropdownMenu,
};
Loading