From f65639c2b0dab01decd20cab2cef4263cb4fab37 Mon Sep 17 00:00:00 2001 From: Igor Octaviano Date: Tue, 19 Nov 2019 16:17:33 -0300 Subject: [PATCH] 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 --- extensions/cornerstone/src/toolbarModule.js | 16 +- platform/core/src/index.js | 8 +- platform/core/src/index.test.js | 1 + .../src/services/UIDialogService/index.js | 125 +++++++++++ .../core/src/services/UIModalService/index.js | 20 +- platform/core/src/services/index.js | 8 +- platform/ui/package.json | 2 + .../src/components/cineDialog/CineDialog.js | 2 +- .../content/aboutContent/AboutContent.js | 2 +- .../ViewportDownloadForm.js | 8 +- .../ViewportDownloadForm.styl | 14 +- .../languageSwitcher/LanguageSwitcher.js | 2 +- .../measurementTable/MeasurementTable.js | 2 +- .../measurementTable/MeasurementTableItem.js | 2 +- .../ui/src/components/ohifModal/OHIFModal.js | 99 +++++---- .../src/components/ohifModal/OHIFModal.styl | 67 ++++++ .../src/components/overlayTrigger/Overlay.js | 2 +- .../components/snackbar/SnackbarContainer.js | 2 +- .../ui/src/components/studyList/StudyList.js | 2 +- .../studyList/StudyListLoadingText.js | 2 +- .../components/studyList/TablePagination.js | 2 +- .../HotKeysPreferences.styl | 2 - .../userPreferencesForm/UserPreferences.js | 4 +- .../userPreferencesForm/UserPreferences.styl | 38 ++-- .../UserPreferencesForm.js | 2 +- .../UserPreferencesForm.styl | 1 - .../ui/src/contextProviders/DialogProvider.js | 208 ++++++++++++++++++ .../src/contextProviders/DialogProvider.styl | 12 + .../LanguageProvider.js | 0 .../ModalProvider.js | 18 +- .../SnackbarProvider.js | 0 platform/ui/src/contextProviders/index.js | 20 ++ platform/ui/src/index.js | 16 +- platform/ui/src/viewer/ToolbarButton.js | 2 +- .../common/OHIFStudyViewer.spec.js | 4 +- platform/viewer/src/App.js | 80 ++++--- .../viewer/src/components/Header/Header.js | 1 - .../src/connectedComponents/ToolbarRow.js | 78 ++++--- yarn.lock | 60 ++++- 39 files changed, 741 insertions(+), 193 deletions(-) create mode 100644 platform/core/src/services/UIDialogService/index.js create mode 100644 platform/ui/src/components/ohifModal/OHIFModal.styl create mode 100644 platform/ui/src/contextProviders/DialogProvider.js create mode 100644 platform/ui/src/contextProviders/DialogProvider.styl rename platform/ui/src/{utils => contextProviders}/LanguageProvider.js (100%) rename platform/ui/src/{utils => contextProviders}/ModalProvider.js (81%) rename platform/ui/src/{utils => contextProviders}/SnackbarProvider.js (100%) create mode 100644 platform/ui/src/contextProviders/index.js diff --git a/extensions/cornerstone/src/toolbarModule.js b/extensions/cornerstone/src/toolbarModule.js index 18104ee86..f32cdfbf1 100644 --- a/extensions/cornerstone/src/toolbarModule.js +++ b/extensions/cornerstone/src/toolbarModule.js @@ -23,6 +23,17 @@ const TOOLBAR_BUTTON_TYPES = { BUILT_IN: 'builtIn', }; +const TOOLBAR_BUTTON_BEHAVIORS = { + CINE: 'CINE', + DOWNLOAD_SCREEN_SHOT: 'DOWNLOAD_SCREEN_SHOT', +}; + +/* TODO: Export enums through a extension manager. */ +const enums = { + TOOLBAR_BUTTON_TYPES, + TOOLBAR_BUTTON_BEHAVIORS, +}; + const definitions = [ { id: 'StackScroll', @@ -102,7 +113,7 @@ const definitions = [ // type: TOOLBAR_BUTTON_TYPES.BUILT_IN, options: { - behavior: 'CINE', + behavior: TOOLBAR_BUTTON_BEHAVIORS.CINE, }, }, { @@ -220,7 +231,8 @@ const definitions = [ // type: TOOLBAR_BUTTON_TYPES.BUILT_IN, options: { - behavior: 'DOWNLOAD_SCREEN_SHOT', + behavior: TOOLBAR_BUTTON_BEHAVIORS.DOWNLOAD_SCREEN_SHOT, + togglable: true, }, }, ], diff --git a/platform/core/src/index.js b/platform/core/src/index.js index cb482e505..678758eda 100644 --- a/platform/core/src/index.js +++ b/platform/core/src/index.js @@ -19,7 +19,11 @@ import ui from './ui'; import user from './user.js'; import utils from './utils/'; -import { createUINotificationService, createUIModalService } from './services'; +import { + createUINotificationService, + createUIModalService, + createUIDialogService, +} from './services'; const OHIF = { MODULE_TYPES, @@ -48,6 +52,7 @@ const OHIF = { // createUINotificationService, createUIModalService, + createUIDialogService, }; export { @@ -76,6 +81,7 @@ export { // createUINotificationService, createUIModalService, + createUIDialogService, }; export { OHIF }; diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js index b06073e12..c67e27a5e 100644 --- a/platform/core/src/index.test.js +++ b/platform/core/src/index.test.js @@ -12,6 +12,7 @@ describe('Top level exports', () => { // 'createUINotificationService', 'createUIModalService', + 'createUIDialogService', // 'utils', 'studies', diff --git a/platform/core/src/services/UIDialogService/index.js b/platform/core/src/services/UIDialogService/index.js new file mode 100644 index 000000000..dc27939f6 --- /dev/null +++ b/platform/core/src/services/UIDialogService/index.js @@ -0,0 +1,125 @@ +/** + * A UI Element + * + * @typedef {ReactElement|HTMLElement} DialogContent + */ + +/** + * A UI Position + * + * @typedef {Object} ElementPosition + * @property {number} top - + * @property {number} left - + * @property {number} right - + * @property {number} bottom - + */ + +/** + * UI Dialog + * + * @typedef {Object} DialogProps + * @property {string} id - + * @property {DialogContent} content - + * @property {boolean} isDraggable - + * @property {ElementPosition} defaultPosition - + * @property {ElementPosition} position - + * @property {Function} onSubmit - + * @property {Function} onClose - + * @property {Function} onStart - + * @property {Function} onStop - + * @property {Function} onDrag - + */ + +const uiDialogServicePublicAPI = { + name: 'UIDialogService', + dismiss, + dismissAll, + create, + setServiceImplementation, +}; + +const uiDialogServiceImplementation = { + _dismiss: () => console.warn('dismiss() NOT IMPLEMENTED'), + _dismissAll: () => console.warn('dismissAll() NOT IMPLEMENTED'), + _create: () => console.warn('create() NOT IMPLEMENTED'), +}; + +function createUIDialogService() { + return uiDialogServicePublicAPI; +} + +/** + * Show a new UI dialog; + * + * @param {DialogProps} props { id, content, onSubmit, onClose, onStart, onDrag, onStop, isDraggable, defaultPosition, position } + */ +function create({ + id, + content, + onSubmit, + onClose, + onStart, + onDrag, + onStop, + isDraggable, + defaultPosition, + position, +}) { + return uiDialogServiceImplementation._create({ + id, + content, + onSubmit, + onClose, + onStart, + onDrag, + onStop, + isDraggable, + defaultPosition, + position, + }); +} + +/** + * Destroys all dialogs, if any + * + * @returns void + */ +function dismissAll() { + return uiDialogServiceImplementation._dismissAll(); +} + +/** + * Destroy the dialog, if currently created + * + * @returns void + */ +function dismiss({ id }) { + return uiDialogServiceImplementation._dismiss({ id }); +} + +/** + * + * + * @param {*} { + * dismiss: dismissImplementation, + * dismissAll: dismissAllImplementation, + * create: createImplementation, + * } + */ +function setServiceImplementation({ + dismiss: dismissImplementation, + dismissAll: dismissAllImplementation, + create: createImplementation, +}) { + if (dismissImplementation) { + uiDialogServiceImplementation._dismiss = dismissImplementation; + } + if (dismissAllImplementation) { + uiDialogServiceImplementation._dismissAll = dismissAllImplementation; + } + if (createImplementation) { + uiDialogServiceImplementation._create = createImplementation; + } +} + +export default createUIDialogService; diff --git a/platform/core/src/services/UIModalService/index.js b/platform/core/src/services/UIModalService/index.js index b93bccdb8..3531e5d37 100644 --- a/platform/core/src/services/UIModalService/index.js +++ b/platform/core/src/services/UIModalService/index.js @@ -8,14 +8,11 @@ * UI Modal * * @typedef {Object} ModalProps - * @property {string} [header=null] - - * @property {string} [footer=null] - - * @property {string} [backdrop=false] - - * @property {string} [keyboard=false] - - * @property {number} [show=true] - - * @property {string} [closeButton=true] - + * @property {boolean} [shouldCloseOnEsc=false] - + * @property {boolean} [isOpen=true] - + * @property {boolean} [closeButton=true] - * @property {string} [title=null] - 'Modal Title' - * @property {boolean} [customClassName=null] - '.ModalClass' + * @property {string} [customClassName=null] - '.ModalClass' */ const uiModalServicePublicAPI = { @@ -38,16 +35,13 @@ function createUIModalService() { * Show a new UI modal; * * @param {Modal} component React component - * @param {ModalProps} props { header, footer, backdrop, keyboard, show, closeButton, title, customClassName } + * @param {ModalProps} props { shouldCloseOnEsc, isOpen, closeButton, title, customClassName } */ function show( component, props = { - header: null, - footer: null, - backdrop: false, - keyboard: false, - show: true, + shouldCloseOnEsc: false, + isOpen: true, closeButton: true, title: null, customClassName: null, diff --git a/platform/core/src/services/index.js b/platform/core/src/services/index.js index 033dee202..0d2f5a525 100644 --- a/platform/core/src/services/index.js +++ b/platform/core/src/services/index.js @@ -1,5 +1,11 @@ import ServicesManager from './ServicesManager.js'; import createUINotificationService from './UINotificationService'; import createUIModalService from './UIModalService'; +import createUIDialogService from './UIDialogService'; -export { createUINotificationService, createUIModalService, ServicesManager }; +export { + createUINotificationService, + createUIModalService, + createUIDialogService, + ServicesManager, +}; diff --git a/platform/ui/package.json b/platform/ui/package.json index 8609a810b..4ec081423 100644 --- a/platform/ui/package.json +++ b/platform/ui/package.json @@ -52,7 +52,9 @@ "react-dnd": "9.4.0", "react-dnd-html5-backend": "^9.4.0", "react-dnd-touch-backend": "^9.4.0", + "react-draggable": "^4.1.0", "react-i18next": "^10.11.0", + "react-modal": "^3.11.1", "react-with-direction": "1.3.0" }, "devDependencies": { diff --git a/platform/ui/src/components/cineDialog/CineDialog.js b/platform/ui/src/components/cineDialog/CineDialog.js index d74592977..e64d94aa7 100644 --- a/platform/ui/src/components/cineDialog/CineDialog.js +++ b/platform/ui/src/components/cineDialog/CineDialog.js @@ -1,7 +1,7 @@ import './CineDialog.styl'; import React, { PureComponent } from 'react'; -import { withTranslation } from '../../utils/LanguageProvider'; +import { withTranslation } from '../../contextProviders'; import { Icon } from './../../elements/Icon'; import PropTypes from 'prop-types'; diff --git a/platform/ui/src/components/content/aboutContent/AboutContent.js b/platform/ui/src/components/content/aboutContent/AboutContent.js index 138219a83..9f38e8974 100644 --- a/platform/ui/src/components/content/aboutContent/AboutContent.js +++ b/platform/ui/src/components/content/aboutContent/AboutContent.js @@ -58,7 +58,7 @@ const AboutContent = () => { ); return ( -
+
+
{t( 'Please specify the dimensions, filename, and desired type for the output image.' @@ -284,10 +284,6 @@ const ViewportDownloadForm = ({ className="viewport-preview" src={viewportPreview.src} alt="Viewport Preview" - style={{ - height: viewportPreview.height, - width: viewportPreview.width, - }} />
@@ -303,7 +299,7 @@ const ViewportDownloadForm = ({
- +
); }; diff --git a/platform/ui/src/components/content/viewportDownloadForm/ViewportDownloadForm.styl b/platform/ui/src/components/content/viewportDownloadForm/ViewportDownloadForm.styl index a1eb621cf..514443a60 100644 --- a/platform/ui/src/components/content/viewportDownloadForm/ViewportDownloadForm.styl +++ b/platform/ui/src/components/content/viewportDownloadForm/ViewportDownloadForm.styl @@ -3,15 +3,13 @@ @import '../../../design/styles/common/button.styl' .ViewportDownloadForm - color: var(--text-secondary-color); - filter: drop-shadow(0 0 3px var(--ui-gray-darkest)); - border: none; - border-radius: 8px; - width: inherit; - padding: 15px; - background: transparent; + display: flex; + flex-direction: column; z-index: 1080 !important; + input, select + max-height: 30px; + .title margin: 0; font-weight: bold; @@ -67,6 +65,7 @@ padding: 10px; border-radius: 5px; align-self: center; + margin-bottom: 20px; @media screen and (max-width: 1023px) width: 100%; justify-content: center; @@ -90,7 +89,6 @@ .actions display: flex; - height: 60px; flex-wrap: nowrap; justify-content: flex-end; align-items: center; diff --git a/platform/ui/src/components/languageSwitcher/LanguageSwitcher.js b/platform/ui/src/components/languageSwitcher/LanguageSwitcher.js index 2d17305bd..f531b6e62 100644 --- a/platform/ui/src/components/languageSwitcher/LanguageSwitcher.js +++ b/platform/ui/src/components/languageSwitcher/LanguageSwitcher.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import i18n from '@ohif/i18n'; import './LanguageSwitcher.styl'; -import { withTranslation } from '../../utils/LanguageProvider'; +import { withTranslation } from '../../contextProviders'; const LanguageSwitcher = () => { const getCurrentLanguage = (language = i18n.language) => diff --git a/platform/ui/src/components/measurementTable/MeasurementTable.js b/platform/ui/src/components/measurementTable/MeasurementTable.js index 7b78dfdce..9ea4aa86b 100644 --- a/platform/ui/src/components/measurementTable/MeasurementTable.js +++ b/platform/ui/src/components/measurementTable/MeasurementTable.js @@ -1,7 +1,7 @@ import './MeasurementTable.styl'; import React, { Component } from 'react'; -import { withTranslation } from '../../utils/LanguageProvider'; +import { withTranslation } from '../../contextProviders'; import { Icon } from './../../elements/Icon'; import { MeasurementTableItem } from './MeasurementTableItem.js'; diff --git a/platform/ui/src/components/measurementTable/MeasurementTableItem.js b/platform/ui/src/components/measurementTable/MeasurementTableItem.js index a09820cb2..203c93ec6 100644 --- a/platform/ui/src/components/measurementTable/MeasurementTableItem.js +++ b/platform/ui/src/components/measurementTable/MeasurementTableItem.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withTranslation } from '../../utils/LanguageProvider'; +import { withTranslation } from '../../contextProviders'; import { Icon } from './../../elements/Icon'; import { OverlayTrigger } from './../overlayTrigger'; diff --git a/platform/ui/src/components/ohifModal/OHIFModal.js b/platform/ui/src/components/ohifModal/OHIFModal.js index 483c650f9..5c6b18633 100644 --- a/platform/ui/src/components/ohifModal/OHIFModal.js +++ b/platform/ui/src/components/ohifModal/OHIFModal.js @@ -1,64 +1,69 @@ import React from 'react'; import PropTypes from 'prop-types'; -import ReactBootstrapModal from 'react-bootstrap-modal'; +import Modal from 'react-modal'; import classNames from 'classnames'; +import './OHIFModal.styl'; + +const customStyle = { + overlay: { + zIndex: 1071, + backgroundColor: 'rgb(0, 0, 0, 0.5)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, +}; + +Modal.setAppElement(document.getElementById('root')); + const OHIFModal = ({ className, closeButton, - backdrop, - keyboard, - show, + shouldCloseOnEsc, + isOpen, title, - onHide, - footer: Footer, - header: Header, + onClose, children, -}) => ( - - {(Header || title) && ( - - {title && ( - {title} - )} - {Header &&
} - - )} - {children} - {Footer && ( - -