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:
`window.OHIFStandaloneViewer.installViewer(window.config);`
- Install the viewer: `window.OHIFStandaloneViewer.installViewer(window.config);`
This exact setup is demonstrated in this
[CodeSandbox](https://codesandbox.io/s/ohif-viewer-script-tag-usage-c4u4t) and

View File

@ -121,11 +121,7 @@ likely want to update:
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file and made
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:
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:
`ENV APP_CONFIG=config/docker_openresty-orthanc.js`

View File

@ -122,11 +122,7 @@ likely want to update:
#### OHIF Viewer
The OHIF Viewer's configuration is imported from a static `.js` file and made
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:
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:
`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 { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import WhiteLabellingContext from './WhiteLabellingContext';
import { getActiveContexts } from './store/layout/selectors.js';
import i18n from '@ohif/i18n';
import setupTools from './setupTools.js';
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
initCornerstoneTools({
@ -74,10 +77,66 @@ class App extends Component {
extensions: [],
};
_appConfig;
_userManager;
constructor(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 { protocol, host } = window.location;
@ -102,58 +161,18 @@ class App extends Component {
),
});
this.userManager = getUserManagerForOpenIdConnectClient(
this._userManager = getUserManagerForOpenIdConnectClient(
store,
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
*/
function _initExtensions(extensions) {
function _initExtensions(extensions, hotkeys) {
const defaultExtensions = [
GenericViewerCommands,
MeasurementsPanel,
@ -163,8 +182,8 @@ function _initExtensions(extensions) {
extensionManager.registerExtensions(mergedExtensions);
// Must run after extension commands are registered
if (window.config.hotkeys) {
hotkeysManager.setHotkeys(window.config.hotkeys, true);
if (hotkeys) {
hotkeysManager.setHotkeys(hotkeys, true);
}
}

View File

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

View File

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

View File

@ -5,7 +5,6 @@ import classNames from 'classnames';
import { MODULE_TYPES } from '@ohif/core';
import OHIF from '@ohif/core';
import moment from 'moment';
import WhiteLabellingContext from '../WhiteLabellingContext.js';
import ConnectedHeader from './ConnectedHeader.js';
import ConnectedToolbarRow from './ConnectedToolbarRow.js';
import ConnectedLabellingOverlay from './ConnectedLabellingOverlay';
@ -13,7 +12,11 @@ import ConnectedStudyBrowser from './ConnectedStudyBrowser.js';
import ConnectedViewerMain from './ConnectedViewerMain.js';
import SidePanel from './../components/SidePanel.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';
/**
* 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';
const defaultContextValues = {

View File

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

View File

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