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

feat: Add simple multi-monitor support #4178

Merged
merged 44 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ed09808
feat: Multi monitor launch
wayfarer3130 May 27, 2024
efe4abd
fix: Added update to existing window
wayfarer3130 May 27, 2024
b6cf4c0
feat: Add preserved multimonitor mode
wayfarer3130 May 27, 2024
26d181d
Fix the default mode to allow multimonitor
wayfarer3130 May 27, 2024
06a4fae
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 May 27, 2024
9abbc6c
Fix load with multimonitor not configured
wayfarer3130 May 27, 2024
bcfed7e
Exclude the app-config from terser mucking so it is readable.
wayfarer3130 May 27, 2024
ab789d7
fix tests
wayfarer3130 May 27, 2024
15d5744
Undid accidental removal of name in package.json
wayfarer3130 May 27, 2024
7052f31
Remove console logs
wayfarer3130 May 28, 2024
00419a4
Merge remote-tracking branch 'ohif/master' into feat/multi-monitor-take2
wayfarer3130 Aug 6, 2024
e08ea76
docs
wayfarer3130 Nov 7, 2024
3bc9025
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Nov 25, 2024
709c7c1
fix: Launch using new multi-monitor setup
wayfarer3130 Nov 26, 2024
8eb5950
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Nov 29, 2024
d18ae3a
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Dec 4, 2024
6f7e5bf
fix: Allow commandsManager commands to run on other window
wayfarer3130 Dec 4, 2024
652b3e6
fix: Preserve annotations by allowing commands to be run in other window
wayfarer3130 Dec 4, 2024
d3b0df6
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Dec 12, 2024
d7aec61
fix: Preserve multimonitor configuration
wayfarer3130 Dec 12, 2024
fd17be7
Fix paths when publicUrl is set
wayfarer3130 Dec 12, 2024
90522c1
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Dec 13, 2024
46ec727
fix: Other monitor show command
wayfarer3130 Dec 13, 2024
96bae40
Remove console log
wayfarer3130 Dec 19, 2024
ebddf4d
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Dec 19, 2024
8a680dc
fix: Multi-monitor - mostly working variant
wayfarer3130 Dec 20, 2024
513d8c0
fix: Add some default options
wayfarer3130 Dec 20, 2024
351c909
Relaunch without refresh in any windows
wayfarer3130 Dec 20, 2024
7d06e7b
fix: Apply full hanging protocol when changing studies
wayfarer3130 Dec 30, 2024
879b221
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Dec 30, 2024
ddc7787
fix: loadStudy wasn't working on same window
wayfarer3130 Dec 31, 2024
f5e8873
Add the simplest multi-monitor display
wayfarer3130 Dec 31, 2024
cfa1a6a
fix: Improve context menu behavior by checking for annotation lock st…
sedghi Jan 9, 2025
9d09315
Merge branch 'master' of github.com:OHIF/Viewers into feat/multi-moni…
sedghi Jan 9, 2025
cf6640d
Merge remote-tracking branch 'origin/master' into feat/multi-monitor-…
wayfarer3130 Jan 10, 2025
4feeb6c
study item dropdown menu customizations (#4668)
sedghi Jan 10, 2025
06f3ab2
update docs
sedghi Jan 10, 2025
b714514
PR comments
sedghi Jan 13, 2025
9be7f45
Refactor CommandsManager: Introduce input validation and streamline c…
sedghi Jan 13, 2025
f452c92
Enhance Multi Monitor Service documentation with future plans
sedghi Jan 13, 2025
1d6839a
Merge branch 'master' of github.com:OHIF/Viewers into feat/multi-moni…
sedghi Jan 13, 2025
4dd6bd7
update yarn
sedghi Jan 13, 2025
45018dd
fix tests
sedghi Jan 13, 2025
fb94570
revert
sedghi Jan 13, 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
91 changes: 91 additions & 0 deletions extensions/default/src/Components/MoreDropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
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;

if (!items) {
return null;
}

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 @@ -47,15 +47,22 @@ export default class ContextMenuController {
}

const { event, subMenu, menuId, menus, selectorProps } = contextMenuProps;
if (!menus) {
console.warn('No menus found for', menuId);
return;
}

const { locking, visibility } = CsAnnotation;
const targetAnnotationId = selectorProps?.nearbyToolData?.annotationUID as string;
const isLocked = locking.isAnnotationLocked(targetAnnotationId);
const isVisible = visibility.isAnnotationVisible(targetAnnotationId);

if (isLocked || !isVisible) {
console.warn(`Annotation is ${isLocked ? 'locked' : 'not visible'}.`);
return;
if (targetAnnotationId) {
const isLocked = locking.isAnnotationLocked(targetAnnotationId);
const isVisible = visibility.isAnnotationVisible(targetAnnotationId);

if (isLocked || !isVisible) {
console.warn(`Annotation is ${isLocked ? 'locked' : 'not visible'}.`);
return;
}
}

const items = ContextMenuItemsBuilder.getMenuItems(
Expand All @@ -73,7 +80,7 @@ export default class ContextMenuController {
preventCutOf: true,
defaultPosition: ContextMenuController._getDefaultPosition(
defaultPointsPosition,
event?.detail,
event?.detail || event,
viewportElement
),
event,
Expand All @@ -89,7 +96,7 @@ export default class ContextMenuController {
menus,
event,
subMenu,
eventData: event?.detail,
eventData: event?.detail || event,

onClose: () => {
this.services.uiDialogService.dismiss({ id: 'context-menu' });
Expand Down Expand Up @@ -136,8 +143,8 @@ export default class ContextMenuController {
};

static _getEventDefaultPosition = eventDetail => ({
x: eventDetail && eventDetail.currentPoints.client[0],
y: eventDetail && eventDetail.currentPoints.client[1],
x: eventDetail?.currentPoints?.client[0] ?? eventDetail?.pageX,
y: eventDetail?.currentPoints?.client[1] ?? eventDetail?.pageY,
});

static _getElementDefaultPosition = element => {
Expand Down
21 changes: 14 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,7 @@ import { useNavigate } from 'react-router-dom';
import { Separator } from '@ohif/ui-next';
import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader';
import { defaultActionIcons, defaultViewPresets } from './constants';
import MoreDropdownMenu from '../../Components/MoreDropdownMenu';

const { sortStudyInstances, formatDate, createStudyBrowserTabs } = utils;

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

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

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

return (
<>
<>
Expand All @@ -304,16 +301,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
4 changes: 2 additions & 2 deletions extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStu
* @param {object} commandsManager
* @param {object} extensionManager
*/
function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesManager }) {
function WrappedPanelStudyBrowser({ extensionManager, servicesManager }) {
// TODO: This should be made available a different way; route should have
// already determined our datasource
const dataSource = extensionManager.getDataSources()[0];
const [dataSource] = extensionManager.getActiveDataSource();
const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(null, dataSource);
const _getImageSrcFromImageId = useCallback(
_createGetImageSrcFromImageIdFn(extensionManager),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function requestDisplaySetCreationForStudy(
return;
}

dataSource.retrieve.series.metadata({ StudyInstanceUID, madeInClient });
return dataSource.retrieve.series.metadata({ StudyInstanceUID, madeInClient });
}

export default requestDisplaySetCreationForStudy;
7 changes: 1 addition & 6 deletions extensions/default/src/ViewerLayout/ViewerHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ function ViewerHeader({
const onClickReturnButton = () => {
const { pathname } = location;
const dataSourceIdx = pathname.indexOf('/', 1);
const query = new URLSearchParams(window.location.search);
const configUrl = query.get('configUrl');

const dataSourceName = pathname.substring(dataSourceIdx + 1);
const existingDataSource = extensionManager.getDataSources(dataSourceName);
Expand All @@ -35,10 +33,7 @@ function ViewerHeader({
if (dataSourceIdx !== -1 && existingDataSource) {
searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1));
}

if (configUrl) {
searchQuery.append('configUrl', configUrl);
}
preserveQueryParameters(searchQuery);

navigate({
pathname: publicUrl,
Expand Down
Loading
Loading