feat(app): Decouple colorbar UI from service and enable action menu programatic control (#5018)

This commit is contained in:
Alireza authored and GitHub committed 2025-05-08 12:51:30 -04:00
1 parent f14d9ebdd7
commit 1445e12124
41 files changed
+1872 -659

No files matched your search

+3
View File
@@ -26,6 +26,7 @@ import {
ModalProvider,
ViewportDialogProvider,
UserAuthenticationProvider,
ViewportActionCornersProvider,
} from '@ohif/ui-next';
// Viewer Project
// TODO: Should this influence study list?
@@ -104,6 +105,7 @@ function App({
userAuthenticationService,
uiNotificationService,
customizationService,
viewportActionCornersService,
} = servicesManager.services;
const providers = [
@@ -119,6 +121,7 @@ function App({
[TooltipProvider],
[DialogProvider, { service: uiDialogService, dialog: ManagedDialog }],
[ModalProvider, { service: uiModalService, modal: ModalNext }],
[ViewportActionCornersProvider, { service: viewportActionCornersService }],
[ShepherdJourneyProvider],
];
+10 -2
View File
@@ -4,11 +4,13 @@ import { ViewportGrid, ViewportPane } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import EmptyViewport from './EmptyViewport';
import { useAppConfig } from '@state';
import { useViewportActionCornersWithGrid } from '../hooks';
function ViewerViewportGrid(props: withAppTypes) {
const { servicesManager, viewportComponents = [], dataSource, commandsManager } = props;
const [viewportGrid, viewportGridService] = useViewportGrid();
const [appConfig] = useAppConfig();
const { initializeViewportCorners } = useViewportActionCornersWithGrid();
const { layout, activeViewportId, viewports, isHangingProtocolLayout } = viewportGrid;
const { numCols, numRows } = layout;
@@ -415,8 +417,14 @@ function ViewerViewportGrid(props: withAppTypes) {
displaySetOptions={displaySetOptions}
needsRerendering={displaySetsNeedsRerendering}
isHangingProtocolLayout={isHangingProtocolLayout}
onElementEnabled={() => {
onElementEnabled={evt => {
viewportGridService.setViewportIsReady(viewportId, true);
// Initialize the viewport action corners for this viewport
if (evt?.detail?.element) {
const elementRef = { current: evt.detail.element };
initializeViewportCorners(viewportId, elementRef, displaySets, commandsManager);
}
}}
/>
</div>
@@ -425,7 +433,7 @@ function ViewerViewportGrid(props: withAppTypes) {
}
return viewportPanes;
}, [viewports, activeViewportId, viewportComponents, dataSource]);
}, [viewports, activeViewportId, viewportComponents, dataSource, initializeViewportCorners]);
/**
* Loading indicator until numCols and numRows are gotten from the HangingProtocolService
+3 -2
View File
@@ -1,4 +1,5 @@
import useDebounce from './useDebounce.js';
import useDebounce from './useDebounce';
import useSearchParams from './useSearchParams';
import useViewportActionCornersWithGrid from './useViewportActionCornersWithGrid';
export { useDebounce, useSearchParams };
export { useDebounce, useSearchParams, useViewportActionCornersWithGrid };
+5
View File
@@ -0,0 +1,5 @@
import useViewportActionCornersWithGrid from './useViewportActionCornersWithGrid';
import useDebounce from './useDebounce';
import useSearchParams from './useSearchParams';
export { useViewportActionCornersWithGrid, useDebounce, useSearchParams };
@@ -0,0 +1,112 @@
import { useCallback, MutableRefObject, useRef } from 'react';
import { useViewportActionCorners, ViewportActionCornersLocations } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
/**
* Hook that manages viewport action corner components for all viewports in the grid
* @returns A function that can be called to initialize action corners for a viewport
*/
export default function useViewportActionCornersWithGrid() {
const [, api] = useViewportActionCorners();
const { servicesManager } = useSystem();
const { customizationService } = servicesManager.services;
// Keep a ref to track processed viewports to avoid duplicates
const processedViewports = useRef<Set<string>>(new Set());
// Map of customization keys to their corresponding enum values
const locationMap = {
'viewportActionMenu.topLeft': ViewportActionCornersLocations.topLeft,
'viewportActionMenu.topRight': ViewportActionCornersLocations.topRight,
'viewportActionMenu.bottomLeft': ViewportActionCornersLocations.bottomLeft,
'viewportActionMenu.bottomRight': ViewportActionCornersLocations.bottomRight,
};
// Function to process customizations for a viewport
const initializeViewportCorners = useCallback(
(
viewportId: string,
elementRef: MutableRefObject<HTMLDivElement>,
displaySets: any[],
commandsManager: any
) => {
if (!viewportId || !elementRef?.current) {
return;
}
// Prevent duplicate processing
if (processedViewports.current.has(viewportId)) {
return;
}
// Mark this viewport as processed
processedViewports.current.add(viewportId);
// Clear any existing components for this viewport
api.clear(viewportId);
// Process each location
Object.entries(locationMap).forEach(([locationKey, locationValue]) => {
const items = customizationService.getCustomization(locationKey);
if (!items || !items.length) {
return;
}
items.forEach(item => {
try {
if (typeof item.component === 'function') {
// Use the component renderer provided directly in the item
const component = item.component({
viewportId,
element: elementRef.current,
displaySets,
location: locationValue,
commandsManager,
});
if (component) {
api.addComponent({
viewportId,
id: item.id,
component,
location: locationValue,
indexPriority: item.indexPriority,
});
}
} else if (item.component) {
// Handle static components
api.addComponent({
viewportId,
id: item.id,
component: item.component,
location: locationValue,
indexPriority: item.indexPriority,
});
}
} catch (error) {
console.error(`Error adding component ${item.id} to viewport corner:`, error);
}
});
});
},
[api, customizationService]
);
// Cleanup function for unmounting viewports
const cleanupViewportCorners = useCallback(
(viewportId: string) => {
if (!viewportId) {
return;
}
// Remove from processed set
processedViewports.current.delete(viewportId);
// Clear from the store
api.clear(viewportId);
},
[api]
);
return { initializeViewportCorners, cleanupViewportCorners };
}
+1 -1
View File
@@ -3,7 +3,7 @@ import { useParams, useLocation, useNavigate } from 'react-router';
import PropTypes from 'prop-types';
import { utils } from '@ohif/core';
import { ImageViewerProvider, DragAndDropProvider } from '@ohif/ui-next';
import { useSearchParams } from '@hooks';
import { useSearchParams } from '../../hooks';
import { useAppConfig } from '@state';
import ViewportGrid from '@components/ViewportGrid';
import Compose from './Compose';
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
//
import filtersMeta from './filtersMeta.js';
import { useAppConfig } from '@state';
import { useDebounce, useSearchParams } from '@hooks';
import { useDebounce, useSearchParams } from '../../hooks';
import { utils, Types as coreTypes } from '@ohif/core';
import {
+2
View File
@@ -16,6 +16,8 @@ export type DisplaySet = {
label?: string;
/** Flag indicating if this is an overlay display set (e.g., SEG, RTSTRUCT) */
isOverlayDisplaySet?: boolean;
/** flag indicating if it supports window level */
supportsWindowLevel?: boolean;
// Details about how to display:
/**
@@ -201,6 +201,31 @@ window.config = {
],
`,
},
{
id: 'viewportActionMenu.windowLevelActionMenu',
description:
'Configures the display and location of the window level action menu in the viewport.',
image: windowLevelActionMenu,
default: null,
configuration: `
window.config = {
// rest of window config
customizationService: [
{
'viewportActionMenu.windowLevelActionMenu': {
$merge: {
location: 0, // Set the location of the menu in the viewport.
// 0: topLeft
// 1: topRight
// 2: bottomLeft
// 3: bottomRight
}
},
},
],
};
`,
},
{
id: 'measurementLabels',
description: 'Labels for measurement tools in the viewer that are automatically asked for.',
@@ -937,7 +962,7 @@ window.config = {
`,
},
{
id: 'viewportActionMenu.dataOverlay',
id: 'viewportActionMenu.dataOverlayMenu',
description: 'Configures the display and location of the data overlay in the viewport.',
image: segmentationOverlay,
default: null,
@@ -946,7 +971,7 @@ window.config = {
// rest of window config
customizationService: [
{
'viewportActionMenu.dataOverlay': {
'viewportActionMenu.dataOverlayMenu': {
$merge: {
enabled: true,
location: 1, // Set the location of the overlay in the viewport.
@@ -1,11 +1,13 @@
import React from 'react';
import React, {
createContext,
useContext,
ReactNode,
useState,
useEffect,
useCallback,
} from 'react';
import classNames from 'classnames';
import PropTypes from 'prop-types';
/**
* A small container that can render multiple "corner" items (like icons, status)
* in each corner of the viewport: top-left, top-right, bottom-left, bottom-right.
*/
export enum ViewportActionCornersLocations {
topLeft,
topRight,
@@ -33,35 +35,102 @@ const locationClasses = {
),
};
function ViewportActionCorners({ cornerComponents }) {
if (!cornerComponents) {
return null;
}
const ViewportActionCornersContext = createContext<{
registerCorner: (location: ViewportActionCornersLocations, children: ReactNode) => void;
} | null>(null);
function Container({ children }: { children: ReactNode }) {
const [corners, setCorners] = useState({
[ViewportActionCornersLocations.topLeft]: null,
[ViewportActionCornersLocations.topRight]: null,
[ViewportActionCornersLocations.bottomLeft]: null,
[ViewportActionCornersLocations.bottomRight]: null,
});
const registerCorner = useCallback(
(location: ViewportActionCornersLocations, children: ReactNode) => {
setCorners(prev => {
// Only update if the children are different to avoid unnecessary renders
if (prev[location] === children) {
return prev;
}
return {
...prev,
[location]: children,
};
});
},
[]
);
return (
<div
className="pointer-events-none absolute h-full w-full select-none"
onDoubleClick={event => {
event.preventDefault();
event.stopPropagation();
}}
>
{Object.entries(cornerComponents).map(([location, locationArray]) => (
<div
key={location}
className={locationClasses[location]}
>
{locationArray.map(componentInfo => (
<div key={componentInfo.id}>{componentInfo.component}</div>
))}
</div>
))}
</div>
<ViewportActionCornersContext.Provider value={{ registerCorner }}>
{children}
<div
className="pointer-events-none absolute h-full w-full select-none"
onDoubleClick={event => {
event.preventDefault();
event.stopPropagation();
}}
>
{Object.entries(corners).map(([location, children]) => {
if (!children) {
return null;
}
return (
<div
key={location}
className={locationClasses[location]}
>
{children}
</div>
);
})}
</div>
</ViewportActionCornersContext.Provider>
);
}
ViewportActionCorners.propTypes = {
cornerComponents: PropTypes.object.isRequired,
};
function Corner({
location,
children,
}: {
location: ViewportActionCornersLocations;
children: ReactNode;
}) {
const context = useContext(ViewportActionCornersContext);
export { ViewportActionCorners };
if (!context) {
throw new Error('Corner component must be used within a ViewportActionCorners.Container');
}
useEffect(() => {
context.registerCorner(location, children);
}, [context, location, children]);
return null;
}
function TopLeft({ children }: { children: ReactNode }) {
return <Corner location={ViewportActionCornersLocations.topLeft}>{children}</Corner>;
}
function TopRight({ children }: { children: ReactNode }) {
return <Corner location={ViewportActionCornersLocations.topRight}>{children}</Corner>;
}
function BottomLeft({ children }: { children: ReactNode }) {
return <Corner location={ViewportActionCornersLocations.bottomLeft}>{children}</Corner>;
}
function BottomRight({ children }: { children: ReactNode }) {
return <Corner location={ViewportActionCornersLocations.bottomRight}>{children}</Corner>;
}
export const ViewportActionCorners = {
Container,
TopLeft,
TopRight,
BottomLeft,
BottomRight,
};
@@ -0,0 +1,550 @@
import React, {
createContext,
useCallback,
useContext,
useEffect,
useReducer,
ReactNode,
useMemo,
} from 'react';
import { ViewportActionCornersLocations } from '../components/Viewport/ViewportActionCorners';
import { ActionComponentInfo, AlignAndSide } from '../types';
const DEFAULT_STATE = {
viewports: {} as Record<string, Record<ViewportActionCornersLocations, ActionComponentInfo[]>>,
};
/**
* API for managing viewport action corner items.
* These methods control the state and behavior of the corner items themselves,
* not the content they might affect in the viewport.
*/
interface ViewportActionCornersApi {
getState: () => typeof DEFAULT_STATE;
addComponent: (component: ActionComponentInfo) => void;
addComponents: (components: Array<ActionComponentInfo>) => void;
clear: (viewportId: string) => void;
getAlignAndSide: (location: ViewportActionCornersLocations) => AlignAndSide;
lockItem: (viewportId: string, itemId: string) => void;
unlockItem: (viewportId: string, itemId: string) => void;
toggleLock: (viewportId: string, itemId: string) => void;
isItemLocked: (viewportId: string, itemId: string) => boolean;
showItem: (viewportId: string, itemId: string) => void;
hideItem: (viewportId: string, itemId: string) => void;
toggleVisibility: (viewportId: string, itemId: string) => void;
isItemVisible: (viewportId: string, itemId: string) => boolean;
openItem: (viewportId: string, itemId: string) => void;
closeItem: (viewportId: string, itemId: string) => void;
closeAllItems: (viewportId: string) => void;
isItemOpen: (viewportId: string, itemId: string) => boolean;
}
export const ViewportActionCornersContext = createContext<
[typeof DEFAULT_STATE, ViewportActionCornersApi]
>([DEFAULT_STATE, {} as ViewportActionCornersApi]);
interface ViewportActionCornersProviderProps {
children: ReactNode;
service: any;
}
export function ViewportActionCornersProvider({
children,
service,
}: ViewportActionCornersProviderProps) {
const viewportActionCornersReducer = (state = DEFAULT_STATE, action) => {
switch (action.type) {
case 'ADD_COMPONENT': {
const { viewportId, id, component, location, indexPriority = 0 } = action.payload;
const newState = { ...state };
if (!newState.viewports[viewportId]) {
newState.viewports[viewportId] = {
[ViewportActionCornersLocations.topLeft]: [],
[ViewportActionCornersLocations.topRight]: [],
[ViewportActionCornersLocations.bottomLeft]: [],
[ViewportActionCornersLocations.bottomRight]: [],
};
}
if (!newState.viewports[viewportId][location]) {
newState.viewports[viewportId][location] = [];
}
const componentInfo = {
id,
component,
indexPriority,
isOpen: false,
isVisible: true,
isLocked: false,
};
const components = [...newState.viewports[viewportId][location]];
const index = components.findIndex(item => item.indexPriority > indexPriority);
if (index === -1) {
components.push(componentInfo);
} else {
components.splice(index, 0, componentInfo);
}
newState.viewports[viewportId][location] = components;
return newState;
}
case 'ADD_COMPONENTS': {
const components = action.payload;
let newState = { ...state };
components.forEach(component => {
newState = viewportActionCornersReducer(newState, {
type: 'ADD_COMPONENT',
payload: component,
});
});
return newState;
}
case 'CLEAR': {
const viewportId = action.payload;
const newState = { ...state };
if (newState.viewports[viewportId]) {
const newViewports = { ...newState.viewports };
delete newViewports[viewportId];
newState.viewports = newViewports;
}
return newState;
}
case 'SET_LOCKED': {
const { viewportId, itemId, lockedStatus } = action.payload;
const newState = { ...state };
if (!newState.viewports[viewportId]) {
return state;
}
const viewportCopy = { ...newState.viewports[viewportId] };
Object.keys(viewportCopy).forEach(locationKey => {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = [...viewportCopy[location]];
const itemIndex = components.findIndex(item => item.id === itemId);
if (itemIndex !== -1) {
const updatedItem = { ...components[itemIndex], isLocked: lockedStatus };
components[itemIndex] = updatedItem;
viewportCopy[location] = components;
}
});
newState.viewports[viewportId] = viewportCopy;
return newState;
}
case 'SET_VISIBLE': {
const { viewportId, itemId, visibleStatus } = action.payload;
const newState = { ...state };
if (!newState.viewports[viewportId]) {
return state;
}
const viewportCopy = { ...newState.viewports[viewportId] };
Object.keys(viewportCopy).forEach(locationKey => {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = [...viewportCopy[location]];
const itemIndex = components.findIndex(item => item.id === itemId);
if (itemIndex !== -1) {
const updatedItem = { ...components[itemIndex], isVisible: visibleStatus };
components[itemIndex] = updatedItem;
viewportCopy[location] = components;
}
});
newState.viewports[viewportId] = viewportCopy;
return newState;
}
case 'OPEN_ITEM': {
const { viewportId, itemId } = action.payload;
const newState = { ...state };
if (!newState.viewports[viewportId]) {
return state;
}
const viewportCopy = { ...newState.viewports[viewportId] };
// Update isOpen flag for the component with matching id in any location
Object.keys(viewportCopy).forEach(locationKey => {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = [...viewportCopy[location]];
const itemIndex = components.findIndex(item => item.id === itemId);
if (itemIndex !== -1) {
const updatedItem = { ...components[itemIndex], isOpen: true };
components[itemIndex] = updatedItem;
viewportCopy[location] = components;
}
});
newState.viewports[viewportId] = viewportCopy;
return newState;
}
case 'CLOSE_ITEM': {
const { viewportId, itemId } = action.payload;
const newState = { ...state };
if (!newState.viewports[viewportId]) {
return state;
}
const viewportCopy = { ...newState.viewports[viewportId] };
// Update isOpen flag for the component with matching id in any location
Object.keys(viewportCopy).forEach(locationKey => {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = [...viewportCopy[location]];
const itemIndex = components.findIndex(item => item.id === itemId);
if (itemIndex !== -1) {
const updatedItem = { ...components[itemIndex], isOpen: false };
components[itemIndex] = updatedItem;
viewportCopy[location] = components;
}
});
newState.viewports[viewportId] = viewportCopy;
return newState;
}
case 'CLOSE_ALL_ITEMS': {
const viewportId = action.payload;
const newState = { ...state };
if (!newState.viewports[viewportId]) {
return state;
}
const viewportCopy = { ...newState.viewports[viewportId] };
// Set isOpen to false for all components in the viewport
Object.keys(viewportCopy).forEach(locationKey => {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = viewportCopy[location].map(item => ({
...item,
isOpen: false,
}));
viewportCopy[location] = components;
});
newState.viewports[viewportId] = viewportCopy;
return newState;
}
default:
return state;
}
};
const [state, dispatch] = useReducer(viewportActionCornersReducer, DEFAULT_STATE);
const getState = useCallback(() => {
return state;
}, [state]);
const addComponent = useCallback(
(component: ActionComponentInfo) => {
dispatch({
type: 'ADD_COMPONENT',
payload: component,
});
},
[dispatch]
);
const addComponents = useCallback(
(components: Array<ActionComponentInfo>) => {
dispatch({
type: 'ADD_COMPONENTS',
payload: components,
});
},
[dispatch]
);
const clear = useCallback(
(viewportId: string) => {
dispatch({
type: 'CLEAR',
payload: viewportId,
});
},
[dispatch]
);
const lockItem = useCallback(
(viewportId: string, itemId: string) => {
dispatch({
type: 'SET_LOCKED',
payload: { viewportId, itemId, lockedStatus: true },
});
},
[dispatch]
);
const unlockItem = useCallback(
(viewportId: string, itemId: string) => {
dispatch({
type: 'SET_LOCKED',
payload: { viewportId, itemId, lockedStatus: false },
});
},
[dispatch]
);
const toggleLock = useCallback(
(viewportId: string, itemId: string) => {
const currentLocked = isItemLocked(viewportId, itemId);
dispatch({
type: 'SET_LOCKED',
payload: { viewportId, itemId, lockedStatus: !currentLocked },
});
},
[dispatch]
);
const isItemLocked = useCallback(
(viewportId: string, itemId: string) => {
if (!state.viewports[viewportId]) {
return false; // Default to unlocked if viewport doesn't exist
}
for (const locationKey in state.viewports[viewportId]) {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = state.viewports[viewportId][location];
const item = components.find(item => item.id === itemId);
if (item && item.isLocked === true) {
return true;
}
}
return false; // Default to unlocked if item not found or isLocked is undefined
},
[state.viewports]
);
const showItem = useCallback(
(viewportId: string, itemId: string) => {
dispatch({
type: 'SET_VISIBLE',
payload: { viewportId, itemId, visibleStatus: true },
});
},
[dispatch]
);
const hideItem = useCallback(
(viewportId: string, itemId: string) => {
dispatch({
type: 'SET_VISIBLE',
payload: { viewportId, itemId, visibleStatus: false },
});
},
[dispatch]
);
const toggleVisibility = useCallback(
(viewportId: string, itemId: string) => {
const currentVisible = isItemVisible(viewportId, itemId);
dispatch({
type: 'SET_VISIBLE',
payload: { viewportId, itemId, visibleStatus: !currentVisible },
});
},
[dispatch]
);
const isItemVisible = useCallback(
(viewportId: string, itemId: string) => {
if (!state.viewports[viewportId]) {
return true; // Default to visible if viewport doesn't exist
}
for (const locationKey in state.viewports[viewportId]) {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = state.viewports[viewportId][location];
const item = components.find(item => item.id === itemId);
if (item && item.isVisible === false) {
return false;
}
}
return true; // Default to visible if item not found or isVisible is undefined
},
[state.viewports]
);
const openItem = useCallback(
(viewportId: string, itemId: string) => {
dispatch({
type: 'OPEN_ITEM',
payload: { viewportId, itemId },
});
},
[dispatch]
);
const closeItem = useCallback(
(viewportId: string, itemId: string) => {
dispatch({
type: 'CLOSE_ITEM',
payload: { viewportId, itemId },
});
},
[dispatch]
);
const closeAllItems = useCallback(
(viewportId: string) => {
dispatch({
type: 'CLOSE_ALL_ITEMS',
payload: viewportId,
});
},
[dispatch]
);
const isItemOpen = useCallback(
(viewportId: string, itemId: string) => {
if (!state.viewports[viewportId]) {
return false;
}
for (const locationKey in state.viewports[viewportId]) {
const location = Number(locationKey) as ViewportActionCornersLocations;
const components = state.viewports[viewportId][location];
const item = components.find(item => item.id === itemId);
if (item && item.isOpen === true) {
return true;
}
}
return false;
},
[state.viewports]
);
const getAlignAndSide = useCallback(
(location: ViewportActionCornersLocations) => {
return service.getAlignAndSide(location);
},
[service]
);
const api = useMemo(() => {
return {
getState,
addComponent,
addComponents,
clear,
getAlignAndSide,
lockItem,
unlockItem,
toggleLock,
isItemLocked,
showItem,
hideItem,
toggleVisibility,
isItemVisible,
openItem,
closeItem,
closeAllItems,
isItemOpen,
};
}, [
getState,
addComponent,
addComponents,
clear,
getAlignAndSide,
lockItem,
unlockItem,
toggleLock,
isItemLocked,
showItem,
hideItem,
toggleVisibility,
isItemVisible,
openItem,
closeItem,
closeAllItems,
isItemOpen,
]);
useEffect(() => {
if (service && service.setServiceImplementation) {
const implementation = {
getState,
addComponent,
addComponents,
clear,
lockItem,
unlockItem,
toggleLock,
isItemLocked,
showItem,
hideItem,
toggleVisibility,
isItemVisible,
openItem,
closeItem,
closeAllItems,
isItemOpen,
};
service.setServiceImplementation(implementation);
}
}, [
service,
getState,
addComponent,
addComponents,
clear,
lockItem,
unlockItem,
toggleLock,
isItemLocked,
showItem,
hideItem,
toggleVisibility,
isItemVisible,
openItem,
closeItem,
closeAllItems,
isItemOpen,
]);
return (
<ViewportActionCornersContext.Provider value={[state, api]}>
{children}
</ViewportActionCornersContext.Provider>
);
}
// Custom hook to use the ViewportActionCorners context
export const useViewportActionCorners = () => useContext(ViewportActionCornersContext);
@@ -9,6 +9,10 @@ import { UserAuthenticationProvider, useUserAuthentication } from './UserAuthent
import { ImageViewerContext, ImageViewerProvider, useImageViewer } from './ImageViewerProvider';
import DragAndDropProvider from './DragAndDropProvider';
import CineProvider, { useCine } from './CineProvider';
import {
ViewportActionCornersProvider,
useViewportActionCorners,
} from './ViewportActionCornersProvider';
export { useNotification, NotificationProvider };
export { ViewportGridContext, ViewportGridProvider, useViewportGrid };
@@ -20,3 +24,4 @@ export { UserAuthenticationProvider, useUserAuthentication };
export { ImageViewerContext, ImageViewerProvider, useImageViewer };
export { DragAndDropProvider };
export { CineProvider, useCine };
export { ViewportActionCornersProvider, useViewportActionCorners };
@@ -0,0 +1,18 @@
import { ReactNode } from 'react';
import { ViewportActionCornersLocations } from '../components/Viewport/ViewportActionCorners';
export type ActionComponentInfo = {
viewportId: string;
id: string;
component: ReactNode;
location: ViewportActionCornersLocations;
indexPriority?: number;
isLocked?: boolean;
isOpen?: boolean;
isVisible?: boolean;
};
export type AlignAndSide = {
align: 'start' | 'end' | 'center';
side: 'top' | 'bottom' | 'left' | 'right';
};
+1
View File
@@ -5,6 +5,7 @@ import ThumbnailType from './ThumbnailType';
export * from './Predicate';
export * from './ContextMenuItem';
export * from './ViewportActionCornersTypes';
export * from './ActionCorners';
/**
* StringNumber often comes back from DICOMweb for integer valued items.