ohif-viewer/extensions/default/src/ViewerLayout/index.tsx
Leonardo Campos d57e8bc157
feat(4D): Add 4D dynamic volume rendering and new pre-clinical 4d pt/ct mode (#3664)
Co-authored-by: Alireza <ar.sedghi@gmail.com>
Co-authored-by: Neil <neil.a.macphee@gmail.com>
2024-04-09 23:57:34 -04:00

182 lines
6.3 KiB
TypeScript

import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { ErrorBoundary, LoadingIndicatorProgress, InvestigationalUseDialog } from '@ohif/ui';
import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core';
import { useAppConfig } from '@state';
import ViewerHeader from './ViewerHeader';
import SidePanelWithServices from '../Components/SidePanelWithServices';
function ViewerLayout({
// From Extension Module Params
extensionManager,
servicesManager,
hotkeysManager,
commandsManager,
// From Modes
viewports,
ViewportGridComp,
leftPanelClosed = false,
rightPanelClosed = false,
}): React.FunctionComponent {
const [appConfig] = useAppConfig();
const { panelService, hangingProtocolService } = servicesManager.services;
const [showLoadingIndicator, setShowLoadingIndicator] = useState(appConfig.showLoadingIndicator);
const hasPanels = useCallback(
(side): boolean => !!panelService.getPanels(side).length,
[panelService]
);
const [hasRightPanels, setHasRightPanels] = useState(hasPanels('right'));
const [hasLeftPanels, setHasLeftPanels] = useState(hasPanels('left'));
const [leftPanelClosedState, setLeftPanelClosed] = useState(leftPanelClosed);
const [rightPanelClosedState, setRightPanelClosed] = useState(rightPanelClosed);
/**
* Set body classes (tailwindcss) that don't allow vertical
* or horizontal overflow (no scrolling). Also guarantee window
* is sized to our viewport.
*/
useEffect(() => {
document.body.classList.add('bg-black');
document.body.classList.add('overflow-hidden');
return () => {
document.body.classList.remove('bg-black');
document.body.classList.remove('overflow-hidden');
};
}, []);
const getComponent = id => {
const entry = extensionManager.getModuleEntry(id);
if (!entry || !entry.component) {
throw new Error(
`${id} is not valid for an extension module or no component found from extension ${id}. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file). Check the reference string to the extension in your Mode configuration`
);
}
return { entry, content: entry.component };
};
useEffect(() => {
const { unsubscribe } = hangingProtocolService.subscribe(
HangingProtocolService.EVENTS.PROTOCOL_CHANGED,
// Todo: right now to set the loading indicator to false, we need to wait for the
// hangingProtocolService to finish applying the viewport matching to each viewport,
// however, this might not be the only approach to set the loading indicator to false. we need to explore this further.
() => {
setShowLoadingIndicator(false);
}
);
return () => {
unsubscribe();
};
}, [hangingProtocolService]);
const getViewportComponentData = viewportComponent => {
const { entry } = getComponent(viewportComponent.namespace);
return {
component: entry.component,
displaySetsToDisplay: viewportComponent.displaySetsToDisplay,
};
};
useEffect(() => {
const { unsubscribe } = panelService.subscribe(
panelService.EVENTS.PANELS_CHANGED,
({ options }) => {
setHasLeftPanels(hasPanels('left'));
setHasRightPanels(hasPanels('right'));
if (options?.leftPanelClosed !== undefined) {
setLeftPanelClosed(options.leftPanelClosed);
}
if (options?.rightPanelClosed !== undefined) {
setRightPanelClosed(options.rightPanelClosed);
}
}
);
return () => {
unsubscribe();
};
}, [panelService, hasPanels]);
const viewportComponents = viewports.map(getViewportComponentData);
return (
<div>
<ViewerHeader
hotkeysManager={hotkeysManager}
extensionManager={extensionManager}
servicesManager={servicesManager}
appConfig={appConfig}
/>
<div
className="relative flex w-full flex-row flex-nowrap items-stretch overflow-hidden bg-black"
style={{ height: 'calc(100vh - 52px' }}
>
<React.Fragment>
{showLoadingIndicator && <LoadingIndicatorProgress className="h-full w-full bg-black" />}
{/* LEFT SIDEPANELS */}
{hasLeftPanels ? (
<ErrorBoundary context="Left Panel">
<SidePanelWithServices
side="left"
activeTabIndex={leftPanelClosedState ? null : 0}
servicesManager={servicesManager}
/>
</ErrorBoundary>
) : null}
{/* TOOLBAR + GRID */}
<div className="flex h-full flex-1 flex-col">
<div className="relative flex h-full flex-1 items-center justify-center overflow-hidden bg-black">
<ErrorBoundary context="Grid">
<ViewportGridComp
servicesManager={servicesManager}
viewportComponents={viewportComponents}
commandsManager={commandsManager}
/>
</ErrorBoundary>
</div>
</div>
{hasRightPanels ? (
<ErrorBoundary context="Right Panel">
<SidePanelWithServices
side="right"
activeTabIndex={rightPanelClosedState ? null : 0}
servicesManager={servicesManager}
/>
</ErrorBoundary>
) : null}
</React.Fragment>
</div>
<InvestigationalUseDialog dialogConfiguration={appConfig?.investigationalUseDialog} />
</div>
);
}
ViewerLayout.propTypes = {
// From extension module params
extensionManager: PropTypes.shape({
getModuleEntry: PropTypes.func.isRequired,
}).isRequired,
commandsManager: PropTypes.instanceOf(CommandsManager),
servicesManager: PropTypes.instanceOf(ServicesManager),
// From modes
leftPanels: PropTypes.array,
rightPanels: PropTypes.array,
leftPanelClosed: PropTypes.bool.isRequired,
rightPanelClosed: PropTypes.bool.isRequired,
/** Responsible for rendering our grid of viewports; provided by consuming application */
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
viewports: PropTypes.array,
};
export default ViewerLayout;