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

Merged
merged 25 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 20 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
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
59 changes: 53 additions & 6 deletions extensions/default/src/Panels/StudyBrowser/PanelStudyBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,49 @@ import { useNavigate } from 'react-router-dom';
import { Separator } from '@ohif/ui-next';
import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader';
import { defaultActionIcons, defaultViewPresets } from './constants';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Icons,
Button,
} from '@ohif/ui-next';

const { sortStudyInstances, formatDate, createStudyBrowserTabs } = utils;

const StudyMenuItems = ({ StudyInstanceUID, commandsManager }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="hidden group-hover:inline-flex data-[state=open]:inline-flex"
>
<Icons.More />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
hideWhenDetached
align="start"
>
<DropdownMenuItem
onSelect={() => {
commandsManager.run('openDICOMTagViewer', {
StudyInstanceUID,
});
}}
className="gap-[6px]"
>
<Icons.DicomTagBrowser />
Study Tags
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

/**
*
* @param {*} param0
Expand Down Expand Up @@ -280,9 +320,10 @@ function PanelStudyBrowser({

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

const onThumbnailContextMenu = (commandName, options) => {
commandsManager.runCommand(commandName, options);
};
const CustomizedThumbnailMenuItems = customizationService.getCustomComponent(
'PanelStudyBrowser.ThumbnailMenuItems',
StudyMenuItems
);

return (
<>
Expand All @@ -304,16 +345,22 @@ 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={props => (
<CustomizedThumbnailMenuItems
{...props}
commandsManager={commandsManager}
/>
)}
/>
</>
);
Expand Down
75 changes: 35 additions & 40 deletions extensions/default/src/commandsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,26 +26,6 @@ export type HangingProtocolParams = {
reset?: false;
};

/**
* The studies from display sets gets the studies in study date
* order or in study instance UID order - not very useful, but
* if not specifically specified then at least making it consistent is useful.
*/
const getStudiesfromDisplaySets = displaySets => {
const studyMap = {};

const ret = displaySets.reduce((prev, curr) => {
const { StudyInstanceUID } = curr;
if (!studyMap[StudyInstanceUID]) {
const study = DicomMetadataStore.getStudy(StudyInstanceUID);
studyMap[StudyInstanceUID] = study;
prev.push(study);
}
return prev;
}, []);
return ret;
};

export type UpdateViewportDisplaySetParams = {
direction: number;
excludeNonImageModalities?: boolean;
Expand Down Expand Up @@ -74,14 +54,24 @@ const commandsModule = ({
* Runs a command in multi-monitor mode. No-op if not multi-monitor.
*/
multimonitor: async options => {
const { commands, screenDelta, studyInstanceUID } = options;
const { screenDelta, StudyInstanceUID, commands, hashParams } = options;
if (multiMonitorService.numberOfScreens < 2) {
return options.fallback?.(options);
}

await multiMonitorService.launchWindow(studyInstanceUID, screenDelta, options);
if (commands) {
multiMonitorService.run(screenDelta, commands, options);
const newWindow = await multiMonitorService.launchWindow(
StudyInstanceUID,
screenDelta,
hashParams
);

// Only run commands if we successfully got a window with a commands manager
if (newWindow && commands) {
// Todo: fix this properly, but it takes time for the new window to load
// and then the commandsManager is available for it
setTimeout(() => {
multiMonitorService.run(screenDelta, commands, options);
}, 1000);
}
},

Expand All @@ -90,17 +80,17 @@ const commandsModule = ({
* Then, if commands is specified, runs the given commands list/instance
*/
loadStudy: async options => {
const { studyInstanceUID, commands } = options;
if (hangingProtocolService.hasStudyUID(studyInstanceUID)) {
return commands && commandsManager.run(commands, options);
const { StudyInstanceUID } = options;
const displaySets = displaySetService.getActiveDisplaySets();
const isActive = displaySets.find(ds => ds.StudyInstanceUID === StudyInstanceUID);
if (isActive) {
return;
}
const [dataSource] = extensionManager.getActiveDataSource();
await requestDisplaySetCreationForStudy(dataSource, displaySetService, studyInstanceUID);
const activeStudy = DicomMetadataStore.getStudy(studyInstanceUID);
hangingProtocolService.addStudy(activeStudy);
const displaySets = displaySetService.getActiveDisplaySets();
hangingProtocolService.setDisplaySets(displaySets);
return commands && commandsManager.run(commands, options);
await requestDisplaySetCreationForStudy(dataSource, displaySetService, StudyInstanceUID);

const study = DicomMetadataStore.getStudy(StudyInstanceUID);
hangingProtocolService.addStudy(study);
},

/**
Expand Down Expand Up @@ -191,13 +181,14 @@ const commandsModule = ({
*/
setHangingProtocol: ({
activeStudyUID = '',
StudyInstanceUID = '',
protocolId,
stageId,
stageIndex,
reset = false,
}: HangingProtocolParams): boolean => {
const toUseStudyInstanceUID = activeStudyUID || StudyInstanceUID;
try {
console.log('******** Set hanging protocol', activeStudyUID);
// Stores in the state the display set selector id to displaySetUID mapping
// Pass in viewportId for the active viewport. This item will get set as
// the activeViewportId
Expand All @@ -215,7 +206,7 @@ const commandsModule = ({
}
} else if (stageIndex === undefined && stageId === undefined) {
// Re-set the same stage as was previously used
const hangingId = `${activeStudyUID || hpInfo.activeStudyUID}:${protocolId}`;
const hangingId = `${toUseStudyInstanceUID || hpInfo.activeStudyUID}:${protocolId}`;
stageIndex = hangingProtocolStageIndexMap[hangingId]?.stageIndex;
}

Expand All @@ -226,9 +217,9 @@ const commandsModule = ({
stageIndex,
});

const activeStudyChanged = hangingProtocolService.setActiveStudyUID(activeStudyUID);
const activeStudyChanged = hangingProtocolService.setActiveStudyUID(toUseStudyInstanceUID);

const storedHanging = `${activeStudyUID || hangingProtocolService.getState().activeStudyUID}:${protocolId}:${
const storedHanging = `${toUseStudyInstanceUID || hangingProtocolService.getState().activeStudyUID}:${protocolId}:${
useStageIdx || 0
}`;

Expand All @@ -245,11 +236,15 @@ const commandsModule = ({
// Run the hanging protocol fresh, re-using the existing study data
// This is done on reset or when the study changes and we haven't yet
// applied it, and don't specify exact stage to use.
hangingProtocolService.run({ activeStudyUID }, protocolId);
const displaySets = displaySetService.getActiveDisplaySets();
hangingProtocolService.run(
{ activeStudyUID: toUseStudyInstanceUID, displaySets },
protocolId
);
} else if (
protocolId === hpInfo.protocolId &&
useStageIdx === hpInfo.stageIndex &&
!activeStudyUID
!toUseStudyInstanceUID
) {
// Clear the HP setting to reset them
hangingProtocolService.setProtocol(protocolId, {
Expand All @@ -270,7 +265,7 @@ const commandsModule = ({
// Do this after successfully applying the update
const { setDisplaySetSelector } = useDisplaySetSelectorStore.getState();
setDisplaySetSelector(
`${activeStudyUID || hpInfo.activeStudyUID}:activeDisplaySet:0`,
`${toUseStudyInstanceUID || hpInfo.activeStudyUID}:activeDisplaySet:0`,
null
);
return true;
Expand Down
Loading