refactor: 💡 React components to consume appConfig using Context (#852)

* refactor: 💡 React components to consume appConfig using Context

React components to consume app configuration using React Context and
React Hooks. Non React components to continue using global variable
window.config. Related documentation also changed.

Closes: #725

* refactor: 💡 Removing unecessary code

On current React component there is no need to import useContext method
from React, so, removing it.

* refactor: 💡 Code review

Code review. Minor changes based on review inputs and moving userManager
to an init method

BREAKING CHANGE: #725

Closes: #725

* docs: don't include implementation detail in docs

* docs: don't include implementation detail in docs

* docs: no need to specify implementation details in employment recipe

* docs: no need to specify implementation details in deployment recipe
This commit is contained in:
ladeirarodolfo 2019-09-06 15:31:26 -03:00 committed by Danny Brown
parent a187783e0d
commit 7c4ee734fa
12 changed files with 121 additions and 86 deletions

View File

@ -119,8 +119,7 @@ window.config = {
}; };
``` ```
- Install the viewer: - Install the viewer: `window.OHIFStandaloneViewer.installViewer(window.config);`
`window.OHIFStandaloneViewer.installViewer(window.config);`
This exact setup is demonstrated in this This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/ohif-viewer-script-tag-usage-c4u4t) and [CodeSandbox](https://codesandbox.io/s/ohif-viewer-script-tag-usage-c4u4t) and

View File

@ -121,11 +121,7 @@ likely want to update:
#### OHIF Viewer #### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file and made The OHIF Viewer's configuration is imported from a static `.js` file. The configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution:
available globally at `window.config`. The configuration we use is set to a
specific file when we build the viewer, and determined by the env variable:
`APP_CONFIG`. You can see where we set its value in the `dockerfile` for this
solution:
`ENV APP_CONFIG=config/docker_openresty-orthanc.js` `ENV APP_CONFIG=config/docker_openresty-orthanc.js`

View File

@ -122,11 +122,7 @@ likely want to update:
#### OHIF Viewer #### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file and made The OHIF Viewer's configuration is imported from a static `.js` file. The configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution:
available globally at `window.config`. The configuration we use is set to a
specific file when we build the viewer, and determined by the env variable:
`APP_CONFIG`. You can see where we set its value in the `dockerfile` for this
solution:
`ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js` `ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js`

View File

@ -25,12 +25,15 @@ import { OidcProvider } from 'redux-oidc';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import WhiteLabellingContext from './WhiteLabellingContext';
import { getActiveContexts } from './store/layout/selectors.js'; import { getActiveContexts } from './store/layout/selectors.js';
import i18n from '@ohif/i18n'; import i18n from '@ohif/i18n';
import setupTools from './setupTools.js'; import setupTools from './setupTools.js';
import store from './store'; import store from './store';
import UserManagerContext from './UserManagerContext';
// Contexts
import WhiteLabellingContext from './context/WhiteLabellingContext';
import UserManagerContext from './context/UserManagerContext';
import AppContext from './context/AppContext';
// ~~~~ APP SETUP // ~~~~ APP SETUP
initCornerstoneTools({ initCornerstoneTools({
@ -74,10 +77,66 @@ class App extends Component {
extensions: [], extensions: [],
}; };
_appConfig;
_userManager;
constructor(props) { constructor(props) {
super(props); super(props);
if (this.props.oidc.length) { this.appConfig = props;
const { servers, extensions, hotkeys, oidc } = props;
this.initUserManager(oidc);
_initExtensions(extensions, hotkeys);
_initServers(servers);
initWebWorkers();
}
render() {
const userManager = this._userManager;
const config = {
appConfig: this._appConfig,
};
if (userManager) {
return (
<AppContext.Provider value={config}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<OidcProvider store={store} userManager={userManager}>
<UserManagerContext.Provider value={userManager}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider
value={this.props.whiteLabelling}
>
<OHIFStandaloneViewer userManager={userManager} />
</WhiteLabellingContext.Provider>
</Router>
</UserManagerContext.Provider>
</OidcProvider>
</I18nextProvider>
</Provider>
</AppContext.Provider>
);
}
return (
<AppContext.Provider value={config}>
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<OHIFStandaloneViewer />
</WhiteLabellingContext.Provider>
</Router>
</I18nextProvider>
</Provider>
</AppContext.Provider>
);
}
initUserManager(oidc) {
if (oidc && !!oidc.length) {
const firstOpenIdClient = this.props.oidc[0]; const firstOpenIdClient = this.props.oidc[0];
const { protocol, host } = window.location; const { protocol, host } = window.location;
@ -102,58 +161,18 @@ class App extends Component {
), ),
}); });
this.userManager = getUserManagerForOpenIdConnectClient( this._userManager = getUserManagerForOpenIdConnectClient(
store, store,
openIdConnectConfiguration openIdConnectConfiguration
); );
} }
_initExtensions(this.props.extensions);
_initServers(this.props.servers);
initWebWorkers();
}
render() {
const userManager = this.userManager;
if (userManager) {
return (
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<OidcProvider store={store} userManager={userManager}>
<UserManagerContext.Provider value={userManager}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider
value={this.props.whiteLabelling}
>
<OHIFStandaloneViewer userManager={userManager} />
</WhiteLabellingContext.Provider>
</Router>
</UserManagerContext.Provider>
</OidcProvider>
</I18nextProvider>
</Provider>
);
}
return (
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<Router basename={this.props.routerBasename}>
<WhiteLabellingContext.Provider value={this.props.whiteLabelling}>
<OHIFStandaloneViewer />
</WhiteLabellingContext.Provider>
</Router>
</I18nextProvider>
</Provider>
);
} }
} }
/** /**
* @param * @param
*/ */
function _initExtensions(extensions) { function _initExtensions(extensions, hotkeys) {
const defaultExtensions = [ const defaultExtensions = [
GenericViewerCommands, GenericViewerCommands,
MeasurementsPanel, MeasurementsPanel,
@ -163,8 +182,8 @@ function _initExtensions(extensions) {
extensionManager.registerExtensions(mergedExtensions); extensionManager.registerExtensions(mergedExtensions);
// Must run after extension commands are registered // Must run after extension commands are registered
if (window.config.hotkeys) { if (hotkeys) {
hotkeysManager.setHotkeys(window.config.hotkeys, true); hotkeysManager.setHotkeys(hotkeys, true);
} }
} }

View File

@ -20,6 +20,9 @@ import './OHIFStandaloneViewer.css';
import './variables.css'; import './variables.css';
import './theme-tide.css'; import './theme-tide.css';
// Contexts
import AppContext from './context/AppContext';
// Dynamic Import Routes (CodeSplitting) // Dynamic Import Routes (CodeSplitting)
// const IHEInvokeImageDisplay = asyncComponent(() => // const IHEInvokeImageDisplay = asyncComponent(() =>
// import('./routes/IHEInvokeImageDisplay.js') // import('./routes/IHEInvokeImageDisplay.js')
@ -37,6 +40,7 @@ import './theme-tide.css';
const reload = () => window.location.reload(); const reload = () => window.location.reload();
class OHIFStandaloneViewer extends Component { class OHIFStandaloneViewer extends Component {
static contextType = AppContext;
state = { state = {
isLoading: false, isLoading: false,
}; };
@ -63,7 +67,7 @@ class OHIFStandaloneViewer extends Component {
render() { render() {
const { user, userManager } = this.props; const { user, userManager } = this.props;
const { appConfig = {} } = this.context;
const userNotLoggedIn = userManager && (!user || user.expired); const userNotLoggedIn = userManager && (!user || user.expired);
if (userNotLoggedIn) { if (userNotLoggedIn) {
const pathname = this.props.location.pathname; const pathname = this.props.location.pathname;
@ -140,9 +144,7 @@ class OHIFStandaloneViewer extends Component {
]; ];
const showStudyList = const showStudyList =
window.config && window.config.showStudyList !== undefined appConfig.showStudyList !== undefined ? appConfig.showStudyList : true;
? window.config.showStudyList
: true;
if (showStudyList) { if (showStudyList) {
routes.push({ routes.push({
path: '/studylist', path: '/studylist',

View File

@ -11,13 +11,17 @@ import { AboutModal } from '@ohif/ui';
import { hotkeysManager } from './../../App.js'; import { hotkeysManager } from './../../App.js';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
// Context
import AppContext from './../../context/AppContext';
class Header extends Component { class Header extends Component {
static contextType = AppContext;
static propTypes = { static propTypes = {
home: PropTypes.bool.isRequired, home: PropTypes.bool.isRequired,
location: PropTypes.object.isRequired, location: PropTypes.object.isRequired,
children: PropTypes.node, children: PropTypes.node,
t: PropTypes.func.isRequired, t: PropTypes.func.isRequired,
userManager: PropTypes.object userManager: PropTypes.object,
}; };
static defaultProps = { static defaultProps = {
@ -63,10 +67,10 @@ class Header extends Component {
if (this.props.user && this.props.userManager) { if (this.props.user && this.props.userManager) {
this.options.push({ this.options.push({
title: t('Logout'), title: t('Logout'),
icon: { name: 'power-off' }, icon: { name: 'power-off' },
onClick: () => { onClick: () => {
this.props.userManager.signoutRedirect(); this.props.userManager.signoutRedirect();
}, },
}); });
} }
@ -82,10 +86,9 @@ class Header extends Component {
render() { render() {
const { t } = this.props; const { t } = this.props;
const { appConfig = {} } = this.context;
const showStudyList = const showStudyList =
window.config.showStudyList !== undefined appConfig.showStudyList !== undefined ? appConfig.showStudyList : true;
? window.config.showStudyList
: true;
return ( return (
<div className={`entry-header ${this.props.home ? 'header-big' : ''}`}> <div className={`entry-header ${this.props.home ? 'header-big' : ''}`}>
<div className="header-left-box"> <div className="header-left-box">

View File

@ -5,7 +5,6 @@ import classNames from 'classnames';
import { MODULE_TYPES } from '@ohif/core'; import { MODULE_TYPES } from '@ohif/core';
import OHIF from '@ohif/core'; import OHIF from '@ohif/core';
import moment from 'moment'; import moment from 'moment';
import WhiteLabellingContext from '../WhiteLabellingContext.js';
import ConnectedHeader from './ConnectedHeader.js'; import ConnectedHeader from './ConnectedHeader.js';
import ConnectedToolbarRow from './ConnectedToolbarRow.js'; import ConnectedToolbarRow from './ConnectedToolbarRow.js';
import ConnectedLabellingOverlay from './ConnectedLabellingOverlay'; import ConnectedLabellingOverlay from './ConnectedLabellingOverlay';
@ -13,7 +12,11 @@ import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ConnectedViewerMain from './ConnectedViewerMain.js'; import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.js'; import SidePanel from './../components/SidePanel.js';
import { extensionManager } from './../App.js'; import { extensionManager } from './../App.js';
import UserManagerContext from '../UserManagerContext';
// Contexts
import WhiteLabellingContext from '../context/WhiteLabellingContext.js';
import UserManagerContext from '../context/UserManagerContext';
import './Viewer.css'; import './Viewer.css';
/** /**
* Inits OHIF Hanging Protocol's onReady. * Inits OHIF Hanging Protocol's onReady.

View File

@ -0,0 +1,5 @@
import React from 'react';
let AppContext = React.createContext({});
export default AppContext;

View File

@ -1,4 +1,4 @@
import OHIFLogo from './components/OHIFLogo/OHIFLogo.js'; import OHIFLogo from '../components/OHIFLogo/OHIFLogo.js';
import React from 'react'; import React from 'react';
const defaultContextValues = { const defaultContextValues = {

View File

@ -1,9 +1,12 @@
import React from 'react'; import React, { useContext } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import queryString from 'query-string'; import queryString from 'query-string';
import ConnectedStudyList from './ConnectedStudyList'; import ConnectedStudyList from './ConnectedStudyList';
// Contexts
import AppContext from '../context/AppContext';
// TODO: Move to @ohif/ui // TODO: Move to @ohif/ui
function toLowerCaseFirstLetter(word) { function toLowerCaseFirstLetter(word) {
@ -22,16 +25,20 @@ function getFilters({ search }) {
} }
function StudyListRouting({ location }) { function StudyListRouting({ location }) {
const { appConfig = {} } = useContext(AppContext);
const filters = location ? getFilters(location) : undefined; const filters = location ? getFilters(location) : undefined;
let studyListFunctionsEnabled = false; let studyListFunctionsEnabled = false;
if (window.config && window.config.studyListFunctionsEnabled) { if (appConfig.studyListFunctionsEnabled) {
studyListFunctionsEnabled = window.config.studyListFunctionsEnabled; studyListFunctionsEnabled = appConfig.studyListFunctionsEnabled;
} }
return <ConnectedStudyList return (
filters={filters} <ConnectedStudyList
studyListFunctionsEnabled={studyListFunctionsEnabled} filters={filters}
/>; studyListFunctionsEnabled={studyListFunctionsEnabled}
/>
);
} }
StudyListRouting.propTypes = { StudyListRouting.propTypes = {

View File

@ -10,10 +10,14 @@ import moment from 'moment';
import ConnectedDicomFilesUploader from '../googleCloud/ConnectedDicomFilesUploader'; import ConnectedDicomFilesUploader from '../googleCloud/ConnectedDicomFilesUploader';
import ConnectedDicomStorePicker from '../googleCloud/ConnectedDicomStorePicker'; import ConnectedDicomStorePicker from '../googleCloud/ConnectedDicomStorePicker';
import filesToStudies from '../lib/filesToStudies.js'; import filesToStudies from '../lib/filesToStudies.js';
import UserManagerContext from '../UserManagerContext';
import WhiteLabellingContext from '../WhiteLabellingContext'; // Contexts
import UserManagerContext from '../context/UserManagerContext';
import WhiteLabellingContext from '../context/WhiteLabellingContext';
import AppContext from '../context/AppContext';
class StudyListWithData extends Component { class StudyListWithData extends Component {
static contextType = AppContext;
state = { state = {
searchData: {}, searchData: {},
studies: [], studies: [],
@ -51,9 +55,10 @@ class StudyListWithData extends Component {
}; };
componentDidMount() { componentDidMount() {
const { appConfig = {} } = this.context;
// TODO: Avoid using timepoints here // TODO: Avoid using timepoints here
//const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} }; //const params = { studyInstanceUids, seriesInstanceUids, timepointId, timepointsFilter={} };
if (!this.props.server && window.config.enableGoogleCloudAdapter) { if (!this.props.server && appConfig.enableGoogleCloudAdapter) {
this.setState({ this.setState({
modalComponentId: 'DicomStorePicker', modalComponentId: 'DicomStorePicker',
}); });
@ -184,6 +189,7 @@ class StudyListWithData extends Component {
}; };
render() { render() {
const { appConfig = {} } = this.context;
const onDrop = async acceptedFiles => { const onDrop = async acceptedFiles => {
try { try {
const studies = await filesToStudies(acceptedFiles); const studies = await filesToStudies(acceptedFiles);
@ -203,8 +209,7 @@ class StudyListWithData extends Component {
let healthCareApiButtons = null; let healthCareApiButtons = null;
let healthCareApiWindows = null; let healthCareApiWindows = null;
// TODO: This should probably be a prop if (appConfig.enableGoogleCloudAdapter) {
if (window.config.enableGoogleCloudAdapter) {
healthCareApiWindows = ( healthCareApiWindows = (
<ConnectedDicomStorePicker <ConnectedDicomStorePicker
isOpen={this.state.modalComponentId === 'DicomStorePicker'} isOpen={this.state.modalComponentId === 'DicomStorePicker'}