ohif-viewer/extensions/default/src/ViewerLayout/index.jsx

298 lines
9.0 KiB
React
Raw Normal View History

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { SidePanel, ErrorBoundary, UserPreferences, AboutModal, Header, useModal } from '@ohif/ui';
import NestedMenu from './ToolbarButtonNestedMenu.jsx';
// TODO: Having ToolbarPrimary and ToolbarSecondary is ugly, but
// these are going to be unified shortly so this is good enough for now.
function ToolbarPrimary({ servicesManager }) {
2020-05-19 11:01:44 +02:00
const { ToolBarService } = servicesManager.services;
const defaultTool = {
icon: 'tool-more-menu',
label: 'More',
isActive: false,
};
const [toolbars, setToolbars] = useState({ primary: [], secondary: [] });
const [activeTool, setActiveTool] = useState(defaultTool);
2020-05-19 11:01:44 +02:00
const setActiveToolHandler = (tool, isNested) => {
setActiveTool(isNested ? tool : defaultTool);
};
const onPrimaryClickHandler = (evt, btn) => {
if (
btn.props &&
btn.props.commands &&
evt.value &&
btn.props.commands[evt.value]
) {
const { commandName, commandOptions } = btn.props.commands[evt.value];
commandsManager.runCommand(commandName, commandOptions);
}
};
useEffect(() => {
const { unsubscribe } = ToolBarService.subscribe(
ToolBarService.EVENTS.TOOL_BAR_MODIFIED,
() => {
console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT');
const updatedToolbars = {
primary: ToolBarService.getButtonSection('primary', {
onClick: onPrimaryClickHandler,
setActiveTool: setActiveToolHandler,
}),
secondary: ToolBarService.getButtonSection('secondary', {
setActiveTool: setActiveToolHandler,
}),
};
setToolbars(updatedToolbars);
}
);
2020-05-15 19:07:21 +02:00
return unsubscribe;
}, [ToolBarService]);
2020-05-15 19:07:21 +02:00
return <>
{toolbars.primary.map((toolDef, index) => {
const isNested = Array.isArray(toolDef);
if (!isNested) {
const { id, Component, componentProps } = toolDef;
return <Component key={id} id={id} {...componentProps} />;
} else {
return (
<NestedMenu
key={index}
isActive={activeTool.isActive}
icon={activeTool.icon}
label={activeTool.label}
>
<div className="flex">
{toolDef.map(x => {
const { id, Component, componentProps } = x;
return (
<Component key={id} id={id} {...componentProps} />
);
})}
</div>
</NestedMenu>
);
}
})}
</>
}
function ToolbarSecondary({ servicesManager }) {
const { ToolBarService } = servicesManager.services;
const defaultTool = {
icon: 'tool-more-menu',
label: 'More',
isActive: false,
};
const [toolbars, setToolbars] = useState({ primary: [], secondary: [] });
const [nestedActiveTool, setNestedActiveTool] = useState(defaultTool);
const setActiveToolHandler = (tool, isNested) => {
setNestedActiveTool(isNested ? tool : defaultTool);
};
2020-05-18 17:21:57 +02:00
2020-06-25 20:27:42 +02:00
const onPrimaryClickHandler = (evt, btn) => {
if (
btn.props &&
btn.props.commands &&
evt.item && evt.item.value &&
btn.props.commands[evt.item.value]
) {
const { commandName, commandOptions } = btn.props.commands[evt.item.value];
2020-06-25 20:27:42 +02:00
commandsManager.runCommand(commandName, commandOptions);
}
};
2020-05-19 11:01:44 +02:00
useEffect(() => {
const { unsubscribe } = ToolBarService.subscribe(
ToolBarService.EVENTS.TOOL_BAR_MODIFIED,
() => {
console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT');
const updatedToolbars = {
primary: ToolBarService.getButtonSection('primary', {
onClick: onPrimaryClickHandler,
setActiveTool: setActiveToolHandler,
}),
secondary: ToolBarService.getButtonSection('secondary', {
setActiveTool: setActiveToolHandler,
}),
};
setToolbars(updatedToolbars);
}
2020-05-19 11:01:44 +02:00
);
return unsubscribe;
}, [ToolBarService]);
return <>
{toolbars.secondary.map(toolDef => {
const { id, Component, componentProps } = toolDef;
return <Component key={id} id={id} {...componentProps} />;
})}
</>
}
function ViewerLayout({
// From Extension Module Params
extensionManager,
servicesManager,
hotkeysManager,
// From Modes
leftPanels,
rightPanels,
viewports,
ViewportGridComp,
}) {
const { t } = useTranslation();
const { show, hide } = useModal();
const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager;
const menuOptions = [
{
title: t('Header:About'),
icon: 'info',
onClick: () => show({ content: AboutModal, title: 'About OHIF Viewer' })
},
{
title: t('Header:Preferences'),
icon: 'settings',
onClick: () => show({
title: t('UserPreferencesModal:User Preferences'),
content: UserPreferences,
contentProps: {
hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults),
hotkeyDefinitions,
onCancel: hide,
onSubmit: ({ hotkeyDefinitions }) => {
hotkeysManager.setHotkeys(hotkeyDefinitions);
hide();
},
onReset: () => hotkeysManager.restoreDefaultBindings()
}
})
},
];
/**
* 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 getPanelData = id => {
const entry = extensionManager.getModuleEntry(id);
// TODO, not sure why sidepanel content has to be JSX, and not a children prop?
const content = entry.component;
return {
iconName: entry.iconName,
iconLabel: entry.iconLabel,
label: entry.label,
name: entry.name,
content,
};
};
const getViewportComponentData = viewportComponent => {
const entry = extensionManager.getModuleEntry(viewportComponent.namespace);
return {
component: entry.component,
displaySetsToDisplay: viewportComponent.displaySetsToDisplay,
};
};
2020-05-19 11:01:44 +02:00
const leftPanelComponents = leftPanels.map(getPanelData);
const rightPanelComponents = rightPanels.map(getPanelData);
const viewportComponents = viewports.map(getViewportComponentData);
return (
<div>
<Header menuOptions={menuOptions}>
2020-06-26 19:08:29 +02:00
<ErrorBoundary context="Primary Toolbar">
<div className="relative flex justify-center">
<ToolbarPrimary servicesManager={servicesManager} />
2020-06-26 19:08:29 +02:00
</div>
</ErrorBoundary>
</Header>
<div
className="flex flex-row flex-no-wrap items-stretch w-full overflow-hidden"
style={{ height: 'calc(100vh - 57px' }}
>
2020-05-15 04:55:17 +02:00
{/* LEFT SIDEPANELS */}
{leftPanelComponents.length && (
2020-06-26 19:08:29 +02:00
<ErrorBoundary context="Left Panel">
<SidePanel
side="left"
defaultComponentOpen={leftPanelComponents[0].name}
childComponents={leftPanelComponents}
/>
</ErrorBoundary>
)}
2020-05-15 04:55:17 +02:00
{/* TOOLBAR + GRID */}
<div className="flex flex-col flex-1 h-full">
<div className="flex h-12 border-b border-transparent flex-2 w-100">
2020-06-26 19:08:29 +02:00
<ErrorBoundary context="Secondary Toolbar">
<div className="flex items-center w-full px-3 bg-primary-dark">
<ToolbarSecondary servicesManager={servicesManager} />
2020-06-26 19:08:29 +02:00
</div>
</ErrorBoundary>
</div>
<div className="flex items-center justify-center flex-1 h-full pt-1 pb-2 overflow-hidden bg-black">
2020-06-26 19:08:29 +02:00
<ErrorBoundary context="Grid">
<ViewportGridComp
servicesManager={servicesManager}
viewportComponents={viewportComponents}
commandsManager={commandsManager}
2020-06-26 19:08:29 +02:00
/>
</ErrorBoundary>
</div>
</div>
{rightPanelComponents.length && (
2020-06-26 19:08:29 +02:00
<ErrorBoundary context="Right Panel">
<SidePanel
side="right"
defaultComponentOpen={rightPanelComponents[0].name}
childComponents={rightPanelComponents}
/>
</ErrorBoundary>
)}
</div>
</div>
);
}
ViewerLayout.propTypes = {
// From extension module params
extensionManager: PropTypes.shape({
getModuleEntry: PropTypes.func.isRequired,
}).isRequired,
commandsManager: PropTypes.object,
// From modes
leftPanels: PropTypes.array,
rightPanels: PropTypes.array,
/** Responsible for rendering our grid of viewports; provided by consuming application */
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
};
ViewerLayout.defaultProps = {
leftPanels: [],
rightPanels: [],
};
export default ViewerLayout;