feat: New dialog service (#1202)

* Add dialog provider boilerplate and improve provider imports

* Add react-draggable dependency

* Add dialog service boilerplate

* Refactor modal to use react-modal and remove dependency on modal themed styles

* Update cinedialog to use new dialog provider

* Add multiple dialogs and refactor provider (dialogs)

* Set app root (modal)

* Add modal zindex bigger than tooltip

* Block drag on buttons/svgs/paths/input and improve jsdoc

* Use guid util to generate dialog ids

* Explicit props in dialog provider

* Improve jsdocs

* Fix tests broken

* Fix boundaries calculation

* Remember last dialog position

* Update providers location

* Add scroll to modal

* Add toggable button toolbar and fix css modal
This commit is contained in:
Igor Octaviano authored and Danny Brown committed 2019-11-19 14:17:33 -05:00
1 parent cdd75bda2a
commit f65639c2b0
39 files changed
+741 -193

No files matched your search

@@ -273,7 +273,7 @@ describe('OHIF Study Viewer Page', function() {
cy.get('[data-cy="about-item-menu"]')
.first()
.click();
cy.get('.modal-content')
cy.get('[data-cy="about-modal"]')
.as('aboutOverlay')
.should('be.visible');
@@ -296,7 +296,7 @@ describe('OHIF Study Viewer Page', function() {
cy.percyCanvasSnapshot('About modal - Should display modal');
//close modal
cy.get('.close').click();
cy.get('[data-cy="close-button"]').click();
cy.get('@aboutOverlay').should('not.be.enabled');
});
});
+50 -30
View File
@@ -1,7 +1,18 @@
import React, { Component } from 'react';
import { OidcProvider } from 'redux-oidc';
import { I18nextProvider } from 'react-i18next';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import { hot } from 'react-hot-loader/root';
// TODO: This should not be here
import './config';
import {
SnackbarProvider,
ModalProvider,
DialogProvider,
OHIFModal,
} from '@ohif/ui';
import {
CommandsManager,
@@ -10,44 +21,48 @@ import {
HotkeysManager,
createUINotificationService,
createUIModalService,
createUIDialogService,
utils,
} from '@ohif/core';
import React, { Component } from 'react';
import i18n from '@ohif/i18n';
// TODO: This should not be here
import './config';
/** Utils */
import {
getUserManagerForOpenIdConnectClient,
initWebWorkers,
} from './utils/index.js';
import { I18nextProvider } from 'react-i18next';
// ~~ EXTENSIONS
/** Extensions */
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
import OHIFCornerstoneExtension from '@ohif/extension-cornerstone';
import OHIFStandaloneViewer from './OHIFStandaloneViewer';
import { OidcProvider } from 'redux-oidc';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { getActiveContexts } from './store/layout/selectors.js';
import i18n from '@ohif/i18n';
import store from './store';
import { SnackbarProvider, ModalProvider, OHIFModal } from '@ohif/ui';
// Contexts
/** Viewer */
import OHIFStandaloneViewer from './OHIFStandaloneViewer';
/** Store */
import { getActiveContexts } from './store/layout/selectors.js';
import store from './store';
/** Contexts */
import WhiteLabellingContext from './context/WhiteLabellingContext';
import UserManagerContext from './context/UserManagerContext';
import AppContext from './context/AppContext';
// ~~~~ APP SETUP
/** ~~~~~~~~~~~~~ Application Setup */
const commandsManagerConfig = {
getAppState: () => store.getState(),
getActiveContexts: () => getActiveContexts(store.getState()),
};
// Services
/** Services */
const UINotificationService = createUINotificationService();
const UIModalService = createUIModalService();
const UIDialogService = createUIDialogService();
/** Managers */
const commandsManager = new CommandsManager(commandsManagerConfig);
const hotkeysManager = new HotkeysManager(commandsManager);
const servicesManager = new ServicesManager();
@@ -55,7 +70,7 @@ const extensionManager = new ExtensionManager({
commandsManager,
servicesManager,
});
// ~~~~ END APP SETUP
/** ~~~~~~~~~~~~~ End Application Setup */
// TODO[react] Use a provider when the whole tree is React
window.store = store;
@@ -72,6 +87,7 @@ class App extends Component {
id: PropTypes.string.isRequired,
})
),
hotkeys: PropTypes.array,
};
static defaultProps = {
@@ -91,7 +107,7 @@ class App extends Component {
const { servers, extensions, hotkeys, oidc } = props;
this.initUserManager(oidc);
_initServices([UINotificationService, UIModalService]);
_initServices([UINotificationService, UIModalService, UIDialogService]);
_initExtensions(extensions, hotkeys);
_initServers(servers);
initWebWorkers();
@@ -114,12 +130,14 @@ class App extends Component {
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider service={UINotificationService}>
<ModalProvider
modal={OHIFModal}
service={UIModalService}
>
<OHIFStandaloneViewer userManager={userManager} />
</ModalProvider>
<DialogProvider service={UIDialogService}>
<ModalProvider
modal={OHIFModal}
service={UIModalService}
>
<OHIFStandaloneViewer userManager={userManager} />
</ModalProvider>
</DialogProvider>
</SnackbarProvider>
</WhiteLabellingContext.Provider>
</Router>
@@ -138,9 +156,11 @@ class App extends Component {
<Router basename={routerBasename}>
<WhiteLabellingContext.Provider value={whiteLabelling}>
<SnackbarProvider service={UINotificationService}>
<ModalProvider modal={OHIFModal} service={UIModalService}>
<OHIFStandaloneViewer />
</ModalProvider>
<DialogProvider service={UIDialogService}>
<ModalProvider modal={OHIFModal} service={UIModalService}>
<OHIFStandaloneViewer />
</ModalProvider>
</DialogProvider>
</SnackbarProvider>
</WhiteLabellingContext.Provider>
</Router>
@@ -49,7 +49,6 @@ class Header extends Component {
onClick: () =>
show(AboutContent, {
title: t('OHIF Viewer - About'),
customClassName: 'AboutContent',
}),
},
{
@@ -7,6 +7,7 @@ import {
RoundedButtonGroup,
ToolbarButton,
withModal,
withDialog,
} from '@ohif/ui';
import './ToolbarRow.css';
@@ -45,7 +46,6 @@ class ToolbarRow extends Component {
this.state = {
toolbarButtons: toolbarButtonDefinitions,
activeButtons: [],
isCineDialogOpen: false,
};
this._handleBuiltIn = _handleBuiltIn.bind(this);
@@ -106,13 +106,6 @@ class ToolbarRow extends Component {
this.state.activeButtons
);
const cineDialogContainerStyle = {
display: this.state.isCineDialogOpen ? 'block' : 'none',
position: 'absolute',
top: '82px',
zIndex: 999,
};
const onPress = (side, value) => {
this.props.handleSidePanelChange(side, value);
};
@@ -145,9 +138,6 @@ class ToolbarRow extends Component {
)}
</div>
</div>
<div className="CineDialogContainer" style={cineDialogContainerStyle}>
<ConnectedCineDialog />
</div>
</>
);
}
@@ -160,7 +150,8 @@ function _getCustomButtonComponent(button, activeButtons) {
// Check if its a valid customComponent. Later on an CustomToolbarComponent interface could be implemented.
if (isValidComponent) {
const parentContext = this;
const isActive = activeButtons.includes(button.id);
const activeButtonsIds = activeButtons.map(button => button.id);
const isActive = activeButtonsIds.includes(button.id);
return (
<CustomComponent
@@ -168,7 +159,7 @@ function _getCustomButtonComponent(button, activeButtons) {
toolbarClickCallback={_handleToolbarButtonClick.bind(this)}
button={button}
key={button.id}
activeButtons={activeButtons}
activeButtons={activeButtonsIds}
isActive={isActive}
/>
);
@@ -181,7 +172,7 @@ function _getExpandableButtonComponent(button, activeButtons) {
const childButtons = button.buttons.map(childButton => {
childButton.onClick = _handleToolbarButtonClick.bind(this, childButton);
if (activeButtons.indexOf(childButton.id) > -1) {
if (activeButtons.map(button => button.id).indexOf(childButton.id) > -1) {
activeCommand = childButton.id;
}
@@ -206,7 +197,7 @@ function _getDefaultButtonComponent(button, activeButtons) {
label={button.label}
icon={button.icon}
onClick={_handleToolbarButtonClick.bind(this, button)}
isActive={activeButtons.includes(button.id)}
isActive={activeButtons.map(button => button.id).includes(button.id)}
/>
);
}
@@ -241,6 +232,8 @@ function _getButtonComponents(toolbarButtons, activeButtons) {
* @param {*} props
*/
function _handleToolbarButtonClick(button, evt, props) {
const { activeButtons } = this.state;
if (button.commandName) {
const options = Object.assign({ evt }, button.commandOptions);
commandsManager.runCommand(button.commandName, options);
@@ -250,11 +243,12 @@ function _handleToolbarButtonClick(button, evt, props) {
// TODO: We can update this to be a `getter` on the extension to query
// For the active tools after we apply our updates?
if (button.type === 'setToolActive') {
this.setState({
activeButtons: [button.id],
});
const toggables = activeButtons.filter(
({ options }) => options && !options.togglable
);
this.setState({ activeButtons: [...toggables, button] });
} else if (button.type === 'builtIn') {
this._handleBuiltIn(button.options);
this._handleBuiltIn(button);
}
}
@@ -279,21 +273,47 @@ function _getVisibleToolbarButtons() {
return toolbarButtonDefinitions;
}
function _handleBuiltIn({ behavior } = {}) {
if (behavior === 'CINE') {
this.setState({
isCineDialogOpen: !this.state.isCineDialogOpen,
});
function _handleBuiltIn(button) {
/* TODO: Keep cine button active until its unselected. */
const { dialog, modal, t } = this.props;
const { dialogId } = this.state;
const { id, options } = button;
if (options.behavior === 'CINE') {
if (dialogId) {
dialog.dismiss({ id: dialogId });
this.setState(state => ({
dialogId: null,
activeButtons: [
...state.activeButtons.filter(button => button.id !== id),
],
}));
} else {
const spacing = 20;
const { x, y } = document
.querySelector(`.ViewerMain`)
.getBoundingClientRect();
const newDialogId = dialog.create({
content: ConnectedCineDialog,
defaultPosition: {
x: x + spacing || 0,
y: y + spacing || 0,
},
});
this.setState(state => ({
dialogId: newDialogId,
activeButtons: [...state.activeButtons, button],
}));
}
}
if (behavior === 'DOWNLOAD_SCREEN_SHOT') {
this.props.modal.show(ConnectedViewportDownloadForm, {
title: this.props.t('Download High Quality Image'),
customClassName: 'ViewportDownloadForm',
if (options.behavior === 'DOWNLOAD_SCREEN_SHOT') {
modal.show(ConnectedViewportDownloadForm, {
title: t('Download High Quality Image'),
});
}
}
export default withTranslation(['Common', 'ViewportDownloadForm'])(
withModal(ToolbarRow)
withModal(withDialog(ToolbarRow))
);