Pull in master

This commit is contained in:
James A. Petts committed 2020-06-15 10:23:35 +02:00
commit e7501933dd
47 files changed
+879 -3000

No files matched your search

+108 -82
View File
@@ -1,112 +1,138 @@
import ImageSet from '@ohif/core/src/classes/ImageSet';
import { IWebApiDataSource } from '@ohif/core';
/**
*
*/
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'example-extension',
id: 'org.ohif.*',
/**
* LIFECYCLE HOOKS
*/
preRegistration({
servicesManager = {},
commandsManager = {},
appConfig = {},
configuration = {},
}) {},
preRegistration() {},
beforeExtInit() {},
beforeExtDestroy() {},
/**
* MODULE GETTERS
* MODULES
*/
getViewportModule() {
return '... react component ...';
},
getSopClassHandlerModule() {
return sopClassHandlerModule;
},
getPanelModule() {
return panelModule;
},
getToolbarModule() {
return panelModule;
},
getCommandsModule(/* store */) {
return commandsModule;
},
getCommandsModule,
getContextModule,
getDataSourcesModule,
getLayoutTemplateModule,
getPanelModule,
getSopClassHandlerModule,
getToolbarModule() {},
getViewportModule,
};
// appConfig,
// extensionConfig,
// dataSources,
// servicesManager,
// extensionManager,
// commandsManager,
/**
*
*/
const commandsModule = {
actions: {
// Store Contexts + Options
exampleAction: ({ viewports, param1 }) => {
console.log(`There are ${viewports.length} viewports`);
console.log(`param1's value is: ${param1}`);
},
},
const getCommandsModule = () => ({
definitions: {
exampleActionDef: {
commandFn: this.actions.exampleAction,
storeContexts: ['viewports'],
commandFn: ({ param1 }) => {
console.log(`param1's value is: ${param1}`);
},
// storeContexts: ['viewports'],
options: { param1: 'hello world' },
context: 'VIEWER', // optional
},
},
};
defaultContext: 'ACTIVE_VIEWPORT::DICOMSR',
});
/**
*
*/
const sopClassHandlerModule = {
id: 'OHIFDicomHtmlSopClassHandler',
sopClassUIDs: Object.values({
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
PROCEDURE_LOG_STORAGE: '1.2.840.10008.5.1.4.1.1.88.40',
MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50',
CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65',
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67',
}),
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
const ExampleContext = React.createContext();
return {
plugin: 'html',
displaySetInstanceUID: 0, //utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
SOPInstanceUID: instance.getSOPInstanceUID(),
SeriesInstanceUID: series.getSeriesInstanceUID(),
StudyInstanceUID: study.getStudyInstanceUID(),
authorizationHeaders,
};
function ExampleContextProvider({ children }) {
return (
<ExampleContext.Provider value={{ example: 'value' }}>
{children}
</TrackedMeasurementsContext.Provider>
);
}
const getContextModule = () => [
{
name: 'ExampleContext',
context: ExampleContext,
provider: ExampleContextProvider,
},
];
const getDataSourcesModule = () => [
{
name: 'exampleDataSource',
type: 'webApi', // 'webApi' | 'local' | 'other'
createDataSource: dataSourceConfig => {
return IWebApiDataSource.create(/* */);
},
},
];
const getLayoutTemplateModule = (/* ... */) => [
{
id: 'exampleLayout',
name: 'exampleLayout',
component: ExampleLayoutComponent,
},
];
const getPanelModule = () => {
return [
{
name: 'exampleSidePanel',
iconName: 'info-circle-o',
iconLabel: 'Example',
label: 'Hello World',
isDisabled: studies => {}, // optional
component: ExamplePanelContentComponent,
},
];
};
/**
*
*/
const panelModule = {
menuOptions: [
const getSopClassHandlerModule = (/* ... */) => {
const BASIC_TEXT_SR = '1.2.840.10008.5.1.4.1.1.88.11';
return [
{
icon: 'th-list',
label: 'Segments',
target: 'segment-panel',
isDisabled: studies => {
return false;
name: 'ExampleSopClassHandle',
sopClassUids: [BASIC_TEXT_SR],
getDisplaySetsFromSeries: instances => {
const imageSet = new ImageSet(instances);
imageSet.setAttributes(/** */);
imageSet.sortBy((a, b) => 0);
return imageSet;
},
},
],
components: [
{
id: 'segment-panel',
component: '... react component ...',
},
],
defaultContext: ['VIEWER'],
];
};
const getToolbarModule = () => {};
// displaySet, viewportIndex, dataSource
const getViewportModule = () => {
const wrappedViewport = props => {
return (
<ExampleViewport
{...props}
onEvent={data => {
commandsManager.runCommand('commandName', data);
}}
/>
);
};
return [{ name: 'example', component: wrappedViewport }];
};
@@ -145,4 +145,8 @@ CornerstoneViewportDownloadForm.propTypes = {
activeViewportIndex: PropTypes.number.isRequired,
};
export default CornerstoneViewportDownloadForm;
// export default CornerstoneViewportDownloadForm;
export default function HelloWorld() {
return <div>Hello World</div>;
}
+4 -4
View File
@@ -148,13 +148,13 @@ const commandsModule = ({ servicesManager }) => {
const enabledElement = getEnabledElement(viewports.activeViewportIndex);
return enabledElement;
},
showDownloadViewportModal: ({ title, viewports }) => {
const activeViewportIndex = viewports.activeViewportIndex;
showDownloadViewportModal: () => {
const activeViewportIndex = 1; // viewports.activeViewportIndex;
const { UIModalService } = servicesManager.services;
if (UIModalService) {
UIModalService.show({
content: CornerstoneViewportDownloadForm,
title,
title: 'Download High Quality Image',
contentProps: {
activeViewportIndex,
onClose: UIModalService.hide,
@@ -318,7 +318,7 @@ const commandsModule = ({ servicesManager }) => {
},
showDownloadViewportModal: {
commandFn: actions.showDownloadViewportModal,
storeContexts: ['viewports'],
storeContexts: [],
options: {},
},
getActiveViewportEnabledElement: {
-4
View File
@@ -1,7 +1,6 @@
import React from 'react';
import init from './init.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
const Component = React.lazy(() => {
@@ -48,9 +47,6 @@ export default {
{ name: 'cornerstone', component: ExtendedOHIFCornerstoneViewport },
];
},
getToolbarModule() {
return toolbarModule;
},
getCommandsModule({ servicesManager }) {
return commandsModule({ servicesManager });
},
+114 -248
View File
@@ -1,14 +1,3 @@
// TODO: A way to add Icons that don't already exist?
// - Register them and add
// - Include SVG Source/Inline?
// - By URL, or own component?
// What KINDS of toolbar buttons do we have...
// - One's that dispatch commands
// - One's that set tool's active
// - More custom, like CINE
// - Built in for one's like this, or custom components?
// Visible?
// Disabled?
// Based on contexts or misc. criteria?
@@ -17,241 +6,118 @@
// setToolActive commands should receive the button event that triggered
// so we can do the "bind to this button" magic
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
SET_TOOL_ACTIVE: 'setToolActive',
BUILT_IN: 'builtIn',
};
// const definitions = [
// // OLD
// {
// id: 'StackScroll',
// label: 'Stack Scroll',
// icon: 'bars',
// //
// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
// commandName: 'setToolActive',
// commandOptions: { toolName: 'StackScroll' },
// },
// {
// id: 'Reset',
// label: 'Reset',
// icon: 'reset',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'resetViewport',
// },
// {
// id: 'Cine',
// label: 'CINE',
// icon: 'youtube',
// //
// type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
// options: {
// behavior: TOOLBAR_BUTTON_BEHAVIORS.CINE,
// },
// },
// {
// id: 'More',
// label: 'More',
// icon: 'ellipse-circle',
// buttons: [
// {
// id: 'Magnify',
// label: 'Magnify',
// icon: 'circle',
// //
// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
// commandName: 'setToolActive',
// commandOptions: { toolName: 'Magnify' },
// },
// {
// id: 'WwwcRegion',
// label: 'ROI Window',
// icon: 'stop',
// //
// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
// commandName: 'setToolActive',
// commandOptions: { toolName: 'WwwcRegion' },
// },
// {
// id: 'Invert',
// label: 'Invert',
// icon: 'adjust',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'invertViewport',
// },
// {
// id: 'RotateRight',
// label: 'Rotate Right',
// icon: 'rotate-right',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'rotateViewportCW',
// },
// {
// id: 'FlipH',
// label: 'Flip H',
// icon: 'ellipse-h',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'flipViewportHorizontal',
// },
// {
// id: 'FlipV',
// label: 'Flip V',
// icon: 'ellipse-v',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'flipViewportVertical',
// },
// {
// id: 'Clear',
// label: 'Clear',
// icon: 'trash',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'clearAnnotations',
// },
// {
// id: 'Eraser',
// label: 'Eraser',
// icon: 'eraser',
// //
// type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
// commandName: 'setToolActive',
// commandOptions: { toolName: 'Eraser' },
// },
// ],
// },
// {
// id: 'Exit2DMPR',
// label: 'Exit 2D MPR',
// icon: 'times',
// //
// type: TOOLBAR_BUTTON_TYPES.COMMAND,
// commandName: 'setCornerstoneLayout',
// context: 'ACTIVE_VIEWPORT::VTK',
// },
// ];
const TOOLBAR_BUTTON_BEHAVIORS = {
CINE: 'CINE',
DOWNLOAD_SCREEN_SHOT: 'DOWNLOAD_SCREEN_SHOT',
};
/* TODO: Export enums through a extension manager. */
const enums = {
TOOLBAR_BUTTON_TYPES,
TOOLBAR_BUTTON_BEHAVIORS,
};
const definitions = [
// UPDATED
{
id: 'Zoom',
label: 'Zoom',
icon: 'tool-zoom',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
},
{
id: 'Wwwc',
label: 'Levels',
icon: 'tool-window-level',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Wwwc' },
},
{
id: 'Pan',
label: 'Pan',
icon: 'tool-move',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Pan' },
},
{
id: 'Capture',
label: 'Capture',
icon: 'tool-capture',
//
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
options: {
behavior: TOOLBAR_BUTTON_BEHAVIORS.DOWNLOAD_SCREEN_SHOT,
togglable: true,
},
},
{
id: 'Annotate',
label: 'Annotate',
icon: 'tool-annotate',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'ArrowAnnotate' },
},
{
id: 'Bidirectional',
label: 'Bidirectional',
icon: 'tool-bidirectional',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Bidirectional' },
},
{
id: 'Ellipse',
label: 'Ellipse',
icon: 'tool-elipse',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'EllipticalRoi' },
},
{
id: 'Length',
label: 'Length',
icon: 'tool-length',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Length' },
},
// OLD
{
id: 'StackScroll',
label: 'Stack Scroll',
icon: 'bars',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'StackScroll' },
},
{
id: 'Angle',
label: 'Angle',
icon: 'angle-left',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Angle' },
},
{
id: 'Reset',
label: 'Reset',
icon: 'reset',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'resetViewport',
},
{
id: 'Cine',
label: 'CINE',
icon: 'youtube',
//
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
options: {
behavior: TOOLBAR_BUTTON_BEHAVIORS.CINE,
},
},
{
id: 'More',
label: 'More',
icon: 'ellipse-circle',
buttons: [
{
id: 'Magnify',
label: 'Magnify',
icon: 'circle',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Magnify' },
},
{
id: 'WwwcRegion',
label: 'ROI Window',
icon: 'stop',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'WwwcRegion' },
},
{
id: 'DragProbe',
label: 'Probe',
icon: 'dot-circle',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'DragProbe' },
},
{
id: 'RectangleRoi',
label: 'Rectangle',
icon: 'square-o',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'RectangleRoi' },
},
{
id: 'Invert',
label: 'Invert',
icon: 'adjust',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'invertViewport',
},
{
id: 'RotateRight',
label: 'Rotate Right',
icon: 'rotate-right',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'rotateViewportCW',
},
{
id: 'FlipH',
label: 'Flip H',
icon: 'ellipse-h',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'flipViewportHorizontal',
},
{
id: 'FlipV',
label: 'Flip V',
icon: 'ellipse-v',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'flipViewportVertical',
},
{
id: 'Clear',
label: 'Clear',
icon: 'trash',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'clearAnnotations',
},
{
id: 'Eraser',
label: 'Eraser',
icon: 'eraser',
//
type: TOOLBAR_BUTTON_TYPES.SET_TOOL_ACTIVE,
commandName: 'setToolActive',
commandOptions: { toolName: 'Eraser' },
},
],
},
{
id: 'Exit2DMPR',
label: 'Exit 2D MPR',
icon: 'times',
//
type: TOOLBAR_BUTTON_TYPES.COMMAND,
commandName: 'setCornerstoneLayout',
context: 'ACTIVE_VIEWPORT::VTK',
},
];
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE',
};
export default [];
@@ -0,0 +1,7 @@
import React from 'react';
export default function ToolbarDivider() {
return (
<span className="self-center w-1 h-8 mx-2 border-l border-common-dark" />
);
}
@@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
function LayoutSelector() {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
function LayoutSelector() {
if (isOpen) {
setIsOpen(false);
}
}
window.addEventListener('click', LayoutSelector);
return () => {
window.removeEventListener('click', LayoutSelector);
};
}, [isOpen]);
const dropdownContent = isOpen ? OHIFLayoutSelector : undefined;
return (
<ToolbarButton
id="Layout"
label="Grid Layout"
icon="tool-layout"
onClick={() => {
setIsOpen(!isOpen);
}}
dropdownContent={dropdownContent}
isActive={isOpen}
type="primary"
/>
);
}
LayoutSelector.propTypes = {
children: PropTypes.any.isRequired,
};
export default LayoutSelector;
+10 -82
View File
@@ -1,10 +1,9 @@
import React, { useState } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
//
import { NavBar, Svg, Icon, IconButton, Toolbar } from '@ohif/ui';
import { NavBar, Svg, Icon, IconButton } from '@ohif/ui';
function Header({ tools, moreTools }) {
const [activeTool, setActiveTool] = useState('Zoom');
function Header({ children }) {
// const dropdownContent = [
// {
// name: 'Soft tissue',
@@ -14,80 +13,16 @@ function Header({ tools, moreTools }) {
// { name: 'Liver', value: '150 / 90' },
// { name: 'Bone', value: '2500 / 480' },
// { name: 'Brain', value: '80 / 40' },
// ];
// ]
// TODO -> In ToolBarManager => Consume commandName and commandOptions and create onClick?
/*
const tools = [
{
id: 'Zoom',
label: 'Zoom',
icon: 'tool-zoom',
commandName: 'setToolActive',
commandOptions: { toolName: 'Zoom' },
onClick: () => setActiveTool('Zoom'),
},
{
id: 'Wwwc',
label: 'Levels',
icon: 'tool-window-level',
commandName: 'setToolActive',
commandOptions: { toolName: 'Wwwc' },
onClick: () => setActiveTool('Wwwc'),
dropdownContent: (
<div>
{dropdownContent.map((row, i) => (
<div
key={i}
className="flex justify-between py-2 px-3 hover:bg-secondary-dark cursor-pointer"
>
<div>
<span className="text-base text-white">{row.name}</span>
<span className="text-base text-primary-light ml-3">
{row.value}
</span>
</div>
<span className="text-base text-primary-active ml-4">{i}</span>
</div>
))}
</div>
),
},
{
id: 'Pan',
label: 'Pan',
icon: 'tool-move',
commandName: 'setToolActive',
commandOptions: { toolName: 'Pan' },
onClick: () => setActiveTool('Pan'),
},
{
id: 'Capture',
label: 'Capture',
icon: 'tool-capture',
commandName: 'setToolActive',
commandOptions: { toolName: 'Capture' },
onClick: () => setActiveTool('Capture'),
},
{
id: 'Layout',
label: 'Layout',
icon: 'tool-layout',
commandName: 'setToolActive',
commandOptions: { toolName: 'Layout' },
onClick: () => setActiveTool('Layout'),
},
];
*/
return (
<NavBar className="justify-between border-b-4 border-black">
<div className="flex flex-1 justify-between">
<div className="flex justify-between flex-1">
<div className="flex items-center">
<div className="mr-3 inline-flex items-center">
<div className="inline-flex items-center mr-3">
<Icon
name="chevron-left"
className="text-primary-active w-8 cursor-pointer"
className="w-8 cursor-pointer text-primary-active"
onClick={() => alert('Navigate to previous page')}
/>
<a href="#" className="ml-4">
@@ -95,15 +30,9 @@ function Header({ tools, moreTools }) {
</a>
</div>
</div>
<div className="flex items-center">{children}</div>
<div className="flex items-center">
<Toolbar
tools={tools}
activeTool={activeTool}
moreTools={moreTools}
/>
</div>
<div className="flex items-center">
<span className="mr-3 text-common-light text-lg">
<span className="mr-3 text-lg text-common-light">
FOR INVESTIGATIONAL USE ONLY
</span>
<IconButton
@@ -123,8 +52,7 @@ function Header({ tools, moreTools }) {
}
Header.propTypes = {
tools: PropTypes.array.isRequired,
moreTools: PropTypes.array.isRequired,
children: PropTypes.any.isRequired,
};
export default Header;
@@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ToolbarButton } from '@ohif/ui';
function NestedMenu({ children }) {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
function closeNestedMenu() {
if (isOpen) {
setIsOpen(false);
}
}
window.addEventListener('click', closeNestedMenu);
return () => {
window.removeEventListener('click', closeNestedMenu);
};
}, [isOpen]);
const dropdownContent = isOpen ? children : undefined;
return (
<ToolbarButton
id="More"
label="More"
icon="tool-more-menu"
onClick={() => {
setIsOpen(!isOpen);
}}
dropdownContent={dropdownContent}
isActive={isOpen}
type="primary"
/>
);
}
NestedMenu.propTypes = {
children: PropTypes.any.isRequired,
};
export default NestedMenu;
+43 -128
View File
@@ -1,8 +1,9 @@
import React, { useEffect, useState, useCallback } from 'react';
import React, { useEffect, useState } from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { SidePanel, Toolbar } from '@ohif/ui';
//
import { SidePanel } from '@ohif/ui';
import Header from './Header.jsx';
import NestedMenu from './ToolbarButtonNestedMenu.jsx';
function ViewerLayout({
// From Extension Module Params
@@ -54,71 +55,23 @@ function ViewerLayout({
};
};
const handleToolBarSubscription = newToolBarLayout => {
// Get buttons to pass to toolbars.
console.log(commandsManager);
const firstTool = newToolBarLayout[0].tools[0];
const toolBarLayout = [];
newToolBarLayout.forEach(newToolBar => {
const toolBar = { tools: [], moreTools: [] };
Object.keys(newToolBar).forEach(key => {
if (newToolBar[key].length) {
newToolBar[key].forEach(tool => {
const commandOptions = tool.commandOptions || {};
toolBar[key].push({
context: tool.context,
icon: tool.icon,
id: tool.id,
label: tool.label,
type: 'setToolActive',
onClick: () => {
commandsManager.runCommand(tool.commandName, commandOptions);
},
});
});
}
});
toolBarLayout.push(toolBar);
// if (newToolBar.moreTools && newToolBar.moreTools.length) {
// newToolBar.moreTools.forEach(tool => {
// const commandOptions = tool.commandOptions || {};
// toolBar.push({
// context: tool.context,
// icon: tool.icon,
// id: tool.id,
// label: tool.label,
// type: 'setToolActive',
// command: () =>
// commandsManager.runCommand(tool.commandName, commandOptions),
// });
// });
// }
});
setToolBarLayout(toolBarLayout);
};
const [toolBarLayout, setToolBarLayout] = useState([
{ tools: [], moreTools: [] },
{ tools: [] },
]);
const [toolbars, setToolbars] = useState({ primary: [], secondary: [] });
useEffect(() => {
const { unsubscribe } = ToolBarService.subscribe(
ToolBarService.EVENTS.TOOL_BAR_MODIFIED,
handleToolBarSubscription
() => {
console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT');
const updatedToolbars = {
primary: ToolBarService.getButtonSection('primary'),
secondary: ToolBarService.getButtonSection('secondary'),
};
setToolbars(updatedToolbars);
}
);
return unsubscribe;
}, []);
}, [ToolBarService]);
const leftPanelComponents = leftPanels.map(getPanelData);
const rightPanelComponents = rightPanels.map(getPanelData);
@@ -126,10 +79,30 @@ function ViewerLayout({
return (
<div>
<Header
tools={toolBarLayout[0].tools}
moreTools={toolBarLayout[0].moreTools}
/>
<Header>
<div className="relative flex justify-center">
{toolbars.primary.map(toolDef => {
const isNested = Array.isArray(toolDef);
if (!isNested) {
const { id, Component, componentProps } = toolDef;
return <Component key={id} id={id} {...componentProps} />;
} else {
return (
<NestedMenu>
<div className="flex">
{toolDef.map(x => {
const { id, Component, componentProps } = x;
return <Component key={id} id={id} {...componentProps} />;
})}
</div>
</NestedMenu>
);
}
})}
</div>
</Header>
<div
className="flex flex-row flex-no-wrap items-stretch w-full overflow-hidden"
style={{ height: 'calc(100vh - 57px' }}
@@ -145,67 +118,17 @@ function ViewerLayout({
{/* TOOLBAR + GRID */}
<div className="flex flex-col flex-1 h-full">
<div className="flex h-12 border-b border-transparent flex-2 w-100">
<Toolbar type="secondary" tools={toolBarLayout[1].tools} />
{toolbars.secondary.map(toolDef => {
const { id, Component, componentProps } = toolDef;
return <Component key={id} id={id} {...componentProps} />;
})}
</div>
<div className="flex items-center justify-center flex-1 h-full pt-1 pb-2 overflow-hidden bg-black">
<ViewportGridComp
servicesManager={servicesManager}
viewportComponents={viewportComponents}
/>
{/*
viewportContents={[
<Viewport
viewportIndex={0}
onSeriesChange={direction => alert(`Series ${direction}`)}
studyData={{
label: 'A',
isTracked: true,
isLocked: false,
studyDate: '07-Sep-2011',
currentSeries: 1,
seriesDescription:
'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ',
modality: 'CT',
patientInformation: {
patientName: 'Smith, Jane',
patientSex: 'F',
patientAge: '59',
MRN: '10000001',
thickness: '5.0mm',
spacing: '1.25mm',
scanner: 'Aquilion',
},
}}
>
</Viewport>,
<Viewport
viewportIndex={1}
onSeriesChange={direction => alert(`Series ${direction}`)}
studyData={{
label: 'A',
isTracked: false,
isLocked: true,
studyDate: '07-Sep-2010',
currentSeries: 2,
seriesDescription:
'Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit Series description lorem ipsum dolor sit ',
modality: 'SR',
patientInformation: {
patientName: 'Smith, Jane',
patientSex: 'F',
patientAge: '59',
MRN: '10000001',
thickness: '2.0mm',
spacing: '1.25mm',
scanner: 'Aquilion',
},
}}
>
</Viewport>,
]}
setActiveViewportIndex={setActiveViewportIndex}
activeViewportIndex={activeViewportIndex}
/>*/}
</div>
</div>
{rightPanelComponents.length && (
@@ -227,14 +150,6 @@ ViewerLayout.propTypes = {
}).isRequired,
commandsManager: PropTypes.object,
// From modes
// TODO: Not in love with this shape,
toolBarLayout: PropTypes.arrayOf(
PropTypes.shape({
tools: PropTypes.array,
moreTools: PropTypes.array,
})
).isRequired,
//displaySetInstanceUids: PropTypes.any.isRequired,
leftPanels: PropTypes.array,
rightPanels: PropTypes.array,
/** Responsible for rendering our grid of viewports; provided by consuming application */
@@ -1,68 +0,0 @@
// SEE:
// https://github.com/OHIF/Viewers/blob/b58aa4575ab72fe3f493cc5a4261b4f8256516ab/platform/viewer/src/appExtensions/MeasurementsPanel/index.js#L18-L49
import React from 'react';
import { useViewportGrid } from '@ohif/ui';
function getCommandsModule({ servicesManager }) {
const { UIDialogService } = servicesManager.services;
const definitions = {
toggleLayoutSelectionDialog: {
commandFn: () => {
if (!UIDialogService) {
window.alert(
'Unable to show dialog; no UI Dialog Service available.'
);
return;
}
// TODO: use SimpleDialog component
// TODO: update position on window resize
// TODO: Expand service API to check if dialog w/ ID is already open
// TODO: Import and call `useViewportGrid`
UIDialogService.dismiss({ id: 'layoutSelection' });
UIDialogService.create({
id: 'layoutSelection',
centralize: true,
isDraggable: false,
showOverlay: true,
content: Test,
});
},
storeContexts: [],
options: {},
context: 'VIEWER',
},
};
return {
definitions,
defaultContext: 'VIEWER',
};
}
function Test() {
const [
{ numCols, numRows, activeViewportIndex, viewports },
dispatch,
] = useViewportGrid();
return (
<div
onClick={() => {
dispatch({
type: 'SET_LAYOUT',
payload: {
numCols: 2,
numRows: 2,
},
});
}}
style={{ color: 'white' }}
>
Hello World!
</div>
);
}
export default getCommandsModule;
+95 -16
View File
@@ -1,19 +1,98 @@
const TOOLBAR_BUTTON_TYPES = {
COMMAND: 'command',
SET_TOOL_ACTIVE: 'setToolActive',
BUILT_IN: 'builtIn',
};
import { ToolbarButton } from '@ohif/ui';
import ToolbarDivider from './Toolbar/ToolbarDivider.jsx';
import ToolbarLayoutSelector from './Toolbar/ToolbarLayoutSelector.jsx';
const definitions = [
{
id: 'Layout',
label: 'Layout',
icon: 'tool-layout',
commandName: 'toggleLayoutSelectionDialog',
commandOptions: { },
},
];
export default function getToolbarModule({ commandsManager, servicesManager }) {
const toolbarService = servicesManager.services.ToolBarService;
export default function getToolbarModule() {
return { definitions, defaultContext: 'ACTIVE_VIEWPORT::CORNERSTONE' };
return [
{
name: 'ohif.divider',
defaultComponent: ToolbarDivider,
clickHandler: () => {},
},
{
name: 'ohif.action',
defaultComponent: ToolbarButton,
requiredConfig: [],
optionalConfig: [],
requiredProps: [],
optionalProps: [],
clickHandler: (evt, btn, btnSectionName) => {
const { props } = btn;
commandsManager.runCommand(props.commandName, props.commandOptions);
},
},
{
name: 'ohif.radioGroup',
defaultComponent: ToolbarButton,
requiredConfig: ['groupName'],
optionalConfig: [],
requiredProps: [],
optionalProps: [],
clickHandler: (evt, clickedBtn, btnSectionName) => {
const { props } = clickedBtn;
const allButtons = toolbarService.getButtons();
// Set all buttons in same group to inactive
Object.keys(allButtons).forEach(btnName => {
const btn = allButtons[btnName];
const isRadioGroupBtn =
btn.config &&
btn.config.groupName &&
btn.type === 'ohif.radioGroup';
if (
isRadioGroupBtn &&
clickedBtn.config.groupName === btn.config.groupName
) {
btn.props.isActive = false;
}
});
// Set our clicked button to active
allButtons[clickedBtn.id].props.isActive = true;
// Run button logic/command
commandsManager.runCommand(props.commandName, props.commandOptions);
// Set buttons & trigger notification
toolbarService.setButtons(allButtons);
},
},
{
name: 'ohif.layoutSelector',
defaultComponent: ToolbarLayoutSelector,
requiredConfig: [],
optionalConfig: [],
requiredProps: [],
optionalProps: [],
clickHandler: (evt, clickedBtn, btnSectionName) => {},
},
{
name: 'ohif.toggle',
defaultComponent: ToolbarButton,
requiredConfig: [],
optionalConfig: [],
requiredProps: [],
optionalProps: [],
clickHandler: (evt, clickedBtn, btnSectionName) => {
const { props } = clickedBtn;
const allButtons = toolbarService.getButtons();
const thisButton = allButtons[clickedBtn.id];
// Set our clicked button to active
thisButton.props.isActive = !thisButton.props.isActive;
// Run button logic/command
// MAKE SURE THIS SUPPORTS TOGGLE!
// commandsManager.runCommand(props.commandName, props.commandOptions);
// What if just toggled "content"?
// commandName OR content?
// Set buttons & trigger notification
toolbarService.setButtons(allButtons);
},
},
];
}
-2
View File
@@ -1,4 +1,3 @@
import getCommandsModule from './getCommandsModule.js';
import getContextModule from './getContextModule.js';
import getDataSourcesModule from './getDataSourcesModule.js';
import getLayoutTemplateModule from './getLayoutTemplateModule.js';
@@ -12,7 +11,6 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id,
getCommandsModule,
getContextModule,
getDataSourcesModule,
getLayoutTemplateModule,
@@ -1,68 +0,0 @@
// SEE:
// https://github.com/OHIF/Viewers/blob/b58aa4575ab72fe3f493cc5a4261b4f8256516ab/platform/viewer/src/appExtensions/MeasurementsPanel/index.js#L18-L49
import React from 'react';
import { useViewportGrid } from '@ohif/ui';
function getCommandsModule({ servicesManager }) {
const { UIDialogService } = servicesManager.services;
const definitions = {
toggleLayoutSelectionDialog: {
commandFn: () => {
if (!UIDialogService) {
window.alert(
'Unable to show dialog; no UI Dialog Service available.'
);
return;
}
// TODO: use SimpleDialog component
// TODO: update position on window resize
// TODO: Expand service API to check if dialog w/ ID is already open
// TODO: Import and call `useViewportGrid`
UIDialogService.dismiss({ id: 'layoutSelection' });
UIDialogService.create({
id: 'layoutSelection',
centralize: true,
isDraggable: false,
showOverlay: true,
content: Test,
});
},
storeContexts: [],
options: {},
context: 'VIEWER',
},
};
return {
definitions,
defaultContext: 'VIEWER',
};
}
function Test() {
const [
{ numCols, numRows, activeViewportIndex, viewports },
dispatch,
] = useViewportGrid();
return (
<div
onClick={() => {
dispatch({
type: 'SET_LAYOUT',
payload: {
numCols: 2,
numRows: 2,
},
});
}}
style={{ color: 'white' }}
>
Hello World!
</div>
);
}
export default getCommandsModule;
@@ -1,4 +1,3 @@
import getCommandsModule from './getCommandsModule.js';
import getContextModule from './getContextModule.js';
import getPanelModule from './getPanelModule.js';
import getViewportModule from './getViewportModule.js';
@@ -8,7 +7,6 @@ export default {
* Only required property. Should be a unique value across all extensions.
*/
id: 'org.ohif.measurement-tracking',
getCommandsModule,
getContextModule,
getPanelModule,
getViewportModule,
+6 -4
View File
@@ -128,7 +128,9 @@ const definitions = [
},
];
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
};
export default [];
// export default {
// definitions,
// defaultContext: 'ACTIVE_VIEWPORT::VTK',
// };