Working cornerstone tools buttons, needs UI updates + onClicks wrapped in toolbarService

This commit is contained in:
James A. Petts 2020-05-20 17:49:17 +01:00
commit a3e1c216c4
13 changed files with 179 additions and 219 deletions

View File

@ -1,39 +1,22 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { SidePanel, Toolbar } from '@ohif/ui'; import { SidePanel, Toolbar } from '@ohif/ui';
import { useToolbarLayout } from '@ohif/core';
// //
import Header from './Header.jsx'; import Header from './Header.jsx';
import { displaySetManager } from '@ohif/core';
// function ViewportDataCreator({ setViewportData }) {
// const { displaySetInstanceUIDs } = useViewModel();
// console.log(displaySetInstanceUIDs);
// useEffect(() => {
// setViewportData([
// displaySetManager.getDisplaySetByUID(displaySetInstanceUIDs[0]),
// ]);
// }, [displaySetInstanceUIDs, setViewportData]);
// return null;
// }
function ViewerLayout({ function ViewerLayout({
// From Extension Module Params // From Extension Module Params
extensionManager, extensionManager,
servicesManager, servicesManager,
commandsManager,
// From Modes // From Modes
leftPanels, leftPanels,
rightPanels, rightPanels,
viewports, viewports,
children, children,
ViewportGridComp ViewportGridComp,
}) { }) {
const [displaySets, setDisplaySets] = useState({}); const { ToolBarService } = servicesManager.services;
//const ViewportGrid = React.Children.only(children);
/** /**
* Set body classes (tailwindcss) that don't allow vertical * Set body classes (tailwindcss) that don't allow vertical
* or horizontal overflow (no scrolling). Also guarantee window * or horizontal overflow (no scrolling). Also guarantee window
@ -47,6 +30,7 @@ function ViewerLayout({
document.body.classList.remove('overflow-hidden'); document.body.classList.remove('overflow-hidden');
}; };
}, []); }, []);
const getPanelData = id => { const getPanelData = id => {
const entry = extensionManager.getModuleEntry(id); const entry = extensionManager.getModuleEntry(id);
// TODO, not sure why sidepanel content has to be JSX, and not a children prop? // TODO, not sure why sidepanel content has to be JSX, and not a children prop?
@ -70,27 +54,84 @@ 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: () => {
debugger;
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),
// });
// });
// }
});
debugger;
setToolBarLayout(toolBarLayout);
};
const [toolBarLayout, setToolBarLayout] = useState([
{ tools: [], moreTools: [] },
{ tools: [] },
]);
useEffect(() => {
const { unsubscribe } = ToolBarService.subscribe(
ToolBarService.EVENTS.TOOL_BAR_MODIFIED,
handleToolBarSubscription
);
return unsubscribe;
}, []);
const leftPanelComponents = leftPanels.map(getPanelData); const leftPanelComponents = leftPanels.map(getPanelData);
const rightPanelComponents = rightPanels.map(getPanelData); const rightPanelComponents = rightPanels.map(getPanelData);
const viewportComponents = viewports.map(getViewportComponentData); const viewportComponents = viewports.map(getViewportComponentData);
// let { toolBarLayout } = useToolbarLayout();
// if (!toolBarLayout.length) {
// return null;
// }
// TODO -> make toolbar service
const toolBarLayout = { tools: [], moreTools: [] };
//const [primaryToolBarLayout, secondaryToolBarLayout] = toolBarLayout;
const primaryToolBarLayout = toolBarLayout;
const secondaryToolBarLayout = toolBarLayout;
return ( return (
<div> <div>
<Header <Header
tools={primaryToolBarLayout.tools} tools={toolBarLayout[0].tools}
moreTools={primaryToolBarLayout.moreTools} moreTools={toolBarLayout[0].moreTools}
/> />
<div <div
className="flex flex-row flex-no-wrap items-stretch overflow-hidden w-full" className="flex flex-row flex-no-wrap items-stretch overflow-hidden w-full"
@ -105,7 +146,7 @@ function ViewerLayout({
{/* TOOLBAR + GRID */} {/* TOOLBAR + GRID */}
<div className="flex flex-col flex-1 h-full"> <div className="flex flex-col flex-1 h-full">
<div className="flex flex-2 w-100 border-b border-transparent h-12"> <div className="flex flex-2 w-100 border-b border-transparent h-12">
<Toolbar type="secondary" tools={secondaryToolBarLayout.tools} /> <Toolbar type="secondary" tools={toolBarLayout[1].tools} />
</div> </div>
<div className="flex flex-1 h-full overflow-hidden bg-black items-center justify-center pb-2 pt-1"> <div className="flex flex-1 h-full overflow-hidden bg-black items-center justify-center pb-2 pt-1">
<ViewportGridComp <ViewportGridComp
@ -183,6 +224,7 @@ ViewerLayout.propTypes = {
extensionManager: PropTypes.shape({ extensionManager: PropTypes.shape({
getModuleEntry: PropTypes.func.isRequired, getModuleEntry: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
commandsManager: PropTypes.object,
// From modes // From modes
// TODO: Not in love with this shape, // TODO: Not in love with this shape,
toolBarLayout: PropTypes.arrayOf( toolBarLayout: PropTypes.arrayOf(
@ -198,11 +240,4 @@ ViewerLayout.propTypes = {
children: PropTypes.oneOfType(PropTypes.node, PropTypes.func).isRequired, children: PropTypes.oneOfType(PropTypes.node, PropTypes.func).isRequired,
}; };
ViewerLayout.defaultProps = {
toolBarLayout: [
{ tools: [], moreTools: [] },
{ tools: [], moreTools: [] },
],
};
export default ViewerLayout; export default ViewerLayout;

View File

@ -5,9 +5,18 @@ import ViewerLayout from './ViewerLayout';
- Init layout based on the displaySets and the objects. - Init layout based on the displaySets and the objects.
*/ */
export default function({ servicesManager, extensionManager }) { export default function({
servicesManager,
extensionManager,
commandsManager,
}) {
function ViewerLayoutWithServices(props) { function ViewerLayoutWithServices(props) {
return ViewerLayout({ servicesManager, extensionManager, ...props }); return ViewerLayout({
servicesManager,
extensionManager,
commandsManager,
...props,
});
} }
return [ return [

View File

@ -12,8 +12,10 @@ export default function mode({ modeConfiguration }) {
routes: [ routes: [
{ {
path: 'viewer', path: 'viewer',
init: ({ toolBarManager }) => { init: ({ servicesManager, extensionManager }) => {
toolBarManager.addButtons([ const { ToolBarService } = servicesManager.services;
ToolBarService.init(extensionManager);
ToolBarService.addButtons([
{ {
id: 'Zoom', id: 'Zoom',
namespace: 'org.ohif.cornerstone.toolbarModule.Zoom', namespace: 'org.ohif.cornerstone.toolbarModule.Zoom',
@ -53,7 +55,7 @@ export default function mode({ modeConfiguration }) {
]); ]);
// Could import layout selector here from org.ohif.default (when it exists!) // Could import layout selector here from org.ohif.default (when it exists!)
toolBarManager.setToolBarLayout([ ToolBarService.setToolBarLayout([
// Primary // Primary
{ {
tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'], tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'],

View File

@ -1,46 +0,0 @@
export default class toolBarManager {
constructor(extensionManager) {
this.buttons = {};
this.extensionManager = extensionManager;
}
addButtons(buttons) {
buttons.forEach(button => {
const buttonDefinition = this.extensionManager.getModuleEntry(
button.namespace
);
const id = button.id || buttonDefinition.id;
this.buttons[id] = buttonDefinition;
});
}
setToolBarLayout(layouts) {
const toolBarLayout = [];
layouts.forEach(layout => {
const toolBarDefinitions = { tools: [], moreTools: [] };
const { tools, moreTools } = layout;
tools &&
tools.forEach(element => {
const button = this.buttons[element];
toolBarDefinitions.tools.push(button);
});
moreTools &&
moreTools.forEach(element => {
const button = this.buttons[element];
toolBarDefinitions.moreTools.push(button);
});
toolBarLayout.push(toolBarDefinitions);
});
// TODO -> Change this to a service. => emit an event to subscribers to update the toolbar layout.
}
}

View File

@ -1,39 +0,0 @@
import React, { Component, useContext } from 'react';
/// TODO MAKE THIS PRETTY DANNY
const ToolbarLayoutContext = React.createContext({
toolBarLayout: [],
setToolBarLayout: () => {},
});
ToolbarLayoutContext.displayName = 'ToolbarLayoutContext';
class ToolbarLayoutProvider extends Component {
state = {
toolBarLayout: [],
};
render() {
const setToolBarLayout = toolBarLayout => {
this.setState({ toolBarLayout });
};
return (
<ToolbarLayoutContext.Provider
value={{
toolBarLayout: this.state.toolBarLayout,
setToolBarLayout,
}}
>
{this.props.children}
</ToolbarLayoutContext.Provider>
);
}
}
const useToolbarLayout = () => useContext(ToolbarLayoutContext);
export default ToolbarLayoutContext;
export { ToolbarLayoutProvider, useToolbarLayout };

View File

@ -20,12 +20,7 @@ import studies from './studies/';
import ui from './ui'; import ui from './ui';
import user from './user.js'; import user from './user.js';
import dicomMetadataStore from './dicomMetadataStore'; import dicomMetadataStore from './dicomMetadataStore';
import ToolBarManager from './ToolBarManager';
import { ViewModelProvider, useViewModel } from './ViewModelContext'; import { ViewModelProvider, useViewModel } from './ViewModelContext';
import {
ToolbarLayoutProvider,
useToolbarLayout,
} from './ToolbarLayoutContext';
import utils, { hotkeys } from './utils/'; import utils, { hotkeys } from './utils/';
import { import {
@ -35,6 +30,7 @@ import {
UINotificationService, UINotificationService,
UIViewportDialogService, UIViewportDialogService,
DisplaySetService, DisplaySetService,
ToolBarSerivce,
} from './services'; } from './services';
import IWebApiDataSource from './DataSources/IWebApiDataSource'; import IWebApiDataSource from './DataSources/IWebApiDataSource';
@ -73,10 +69,10 @@ const OHIF = {
UIViewportDialogService, UIViewportDialogService,
DisplaySetService, DisplaySetService,
MeasurementService, MeasurementService,
ToolBarSerivce,
IWebApiDataSource, IWebApiDataSource,
dicomMetadataStore, dicomMetadataStore,
// //
ToolBarManager,
ViewModelProvider, ViewModelProvider,
useViewModel, useViewModel,
}; };
@ -114,13 +110,11 @@ export {
UIViewportDialogService, UIViewportDialogService,
DisplaySetService, DisplaySetService,
MeasurementService, MeasurementService,
ToolBarSerivce,
IWebApiDataSource, IWebApiDataSource,
dicomMetadataStore, dicomMetadataStore,
ToolBarManager,
ViewModelProvider, ViewModelProvider,
useViewModel, useViewModel,
ToolbarLayoutProvider,
useToolbarLayout,
}; };
export { OHIF }; export { OHIF };

View File

@ -1,5 +1,9 @@
import pubSubServiceInterface from '../pubSubServiceInterface'; import pubSubServiceInterface from '../pubSubServiceInterface';
const EVENTS = {
TOOL_BAR_MODIFIED: 'event::toolBarService:toolBarModified',
};
export default class ToolBarService { export default class ToolBarService {
constructor() { constructor() {
this.displaySets = {}; this.displaySets = {};
@ -26,6 +30,23 @@ export default class ToolBarService {
}); });
} }
/**
* Broadcasts displaySetService changes.
*
* @param {string} eventName The event name
* @return void
*/
_broadcastChange = (eventName, callbackProps) => {
const hasListeners = Object.keys(this.listeners).length > 0;
const hasCallbacks = Array.isArray(this.listeners[eventName]);
if (hasListeners && hasCallbacks) {
this.listeners[eventName].forEach(listener => {
listener.callback(callbackProps);
});
}
};
setToolBarLayout(layouts) { setToolBarLayout(layouts) {
const toolBarLayout = []; const toolBarLayout = [];
@ -51,6 +72,8 @@ export default class ToolBarService {
toolBarLayout.push(toolBarDefinitions); toolBarLayout.push(toolBarDefinitions);
}); });
// TODO -> Change this to a service. => emit an event to subscribers to update the toolbar layout. this.toolBarLayout = toolBarLayout;
this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, toolBarLayout);
} }
} }

View File

@ -0,0 +1,8 @@
import ToolBarService from './ToolBarService';
export default {
name: 'ToolBarService',
create: ({ configuration = {} }) => {
return new ToolBarService();
},
};

View File

@ -5,6 +5,7 @@ import UIModalService from './UIModalService';
import UINotificationService from './UINotificationService'; import UINotificationService from './UINotificationService';
import UIViewportDialogService from './UIViewportDialogService'; import UIViewportDialogService from './UIViewportDialogService';
import DisplaySetService from './DisplaySetService'; import DisplaySetService from './DisplaySetService';
import ToolBarSerivce from './ToolBarService';
export { export {
MeasurementService, MeasurementService,
@ -14,4 +15,5 @@ export {
UINotificationService, UINotificationService,
UIViewportDialogService, UIViewportDialogService,
DisplaySetService, DisplaySetService,
ToolBarSerivce,
}; };

View File

@ -1,19 +1,48 @@
window.config = function(props) { window.config = {
var servicesManager = props.servicesManager; routerBasename: '/',
enableGoogleCloudAdapter: true,
return { servers: {
routerBasename: '/', // This is an array, but we'll only use the first entry for now
enableGoogleCloudAdapter: true, dicomWeb: [],
enableGoogleCloudAdapterUI: false, },
showStudyList: true, // This is an array, but we'll only use the first entry for now
httpErrorHandler: error => { oidc: [
// This is 429 when rejected from the public idc sandbox too often. {
console.warn(error.status); // ~ REQUIRED
// Authorization Server URL
// Could use services manager here to bring up a dialog/modal if needed. authority: 'https://accounts.google.com',
console.warn('test, navigate to https://ohif.org/'); client_id:
window.location = 'https://ohif.org/'; '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com',
redirect_uri: '/callback', // `OHIFStandaloneViewer.js`
response_type: 'id_token token',
scope:
'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid
// ~ OPTIONAL
post_logout_redirect_uri: '/logout-redirect.html',
revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=',
automaticSilentRenew: true,
revokeAccessTokenOnSignout: true,
}, },
healthcareApiEndpoint: 'https://idc-sandbox-002.appspot.com/v1beta1', ],
}; studyListFunctionsEnabled: true,
}; };
// window.config = function(props) {
// var servicesManager = props.servicesManager;
// return {
// routerBasename: '/',
// enableGoogleCloudAdapter: true,
// enableGoogleCloudAdapterUI: false,
// showStudyList: true,
// httpErrorHandler: error => {
// // This is 429 when rejected from the public idc sandbox too often.
// console.warn(error.status);
// // Could use services manager here to bring up a dialog/modal if needed.
// console.warn('test, navigate to https://ohif.org/');
// window.location = 'https://ohif.org/';
// },
// healthcareApiEndpoint: 'https://idc-sandbox-002.appspot.com/v1beta1',
// };
// };

View File

@ -8,6 +8,7 @@ import {
UIDialogService, UIDialogService,
MeasurementService, MeasurementService,
DisplaySetService, DisplaySetService,
ToolBarSerivce,
// utils, // utils,
// redux as reduxOHIF, // redux as reduxOHIF,
} from '@ohif/core'; } from '@ohif/core';
@ -45,6 +46,7 @@ function appInit(appConfigOrFunc, defaultExtensions) {
UIDialogService, UIDialogService,
MeasurementService, MeasurementService,
DisplaySetService, DisplaySetService,
ToolBarSerivce,
]); ]);
/** /**

View File

@ -1,55 +0,0 @@
import { useEffect, useCallback } from 'react';
import {
displaySetManager,
ToolBarManager,
useViewModel,
useToolbarLayout,
} from '@ohif/core';
export default function DisplaySetCreator({
location,
mode,
dataSourceName,
extensionManager,
DisplaySetService,
}) {
console.warn('DisplaySetCreator rerendering');
const { routes, sopClassHandlers } = mode;
const dataSources = extensionManager.getDataSources(dataSourceName);
// TODO: For now assume one unique datasource.
const dataSource = dataSources[0];
const route = routes[0];
// Add toolbar state to the view model context?
const { displaySetInstanceUIDs, setDisplaySetInstanceUIDs } = useViewModel();
const { toolBarLayout, setToolBarLayout } = useToolbarLayout();
useEffect(() => {
let toolBarManager = new ToolBarManager(extensionManager, setToolBarLayout);
route.init({ toolBarManager });
}, [mode, dataSourceName, location]);
const createDisplaySets = useCallback(() => {
// Add SOPClassHandlers to a new SOPClassManager.
displaySetManager.init(extensionManager, sopClassHandlers, {
displaySetInstanceUIDs,
setDisplaySetInstanceUIDs,
});
const queryParams = location.search;
// Call the data source to start building the view model?
dataSource.retrieve.series.metadata(
queryParams,
displaySetManager.makeDisplaySets
);
}, [displaySetInstanceUIDs, location]);
useEffect(() => {
createDisplaySets();
}, [mode, dataSourceName, location]);
return null;
}

View File

@ -2,13 +2,11 @@ import React, { useEffect, useCallback } from 'react';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
// //
import { ToolBarManager } from '@ohif/core';
import { DragAndDropProvider } from '@ohif/ui'; import { DragAndDropProvider } from '@ohif/ui';
// //
import { useQuery } from '@hooks'; import { useQuery } from '@hooks';
import ViewportGrid from '@components/ViewportGrid'; import ViewportGrid from '@components/ViewportGrid';
import Compose from './Compose'; import Compose from './Compose';
//import DisplaySetCreator from './DisplaySetCreator';
export default function ModeRoute({ export default function ModeRoute({
location, location,
@ -69,9 +67,7 @@ export default function ModeRoute({
} }
useEffect(() => { useEffect(() => {
// TODO -> Make this into a service route.init({ servicesManager, extensionManager });
let toolBarManager = new ToolBarManager(extensionManager); //, setToolBarLayout);
route.init({ toolBarManager });
}, [mode, dataSourceName, location]); }, [mode, dataSourceName, location]);
// This queries for series, but... What does it do with them? // This queries for series, but... What does it do with them?