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:
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
@@ -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))
|
||||
);
|
||||
Reference in new issue
Block a user