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 (
-
+
);
};
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 && (
-
-
-
- )}
-
-);
+}) => {
+ const renderHeader = () => {
+ return (
+ title && (
+
+
{title}
+ {closeButton && (
+
+ )}
+
+ )
+ );
+ };
+
+ return (
+
+ <>
+ {renderHeader()}
+ {children}
+ >
+
+ );
+};
OHIFModal.propTypes = {
className: PropTypes.string,
closeButton: PropTypes.bool,
- backdrop: PropTypes.bool,
- keyboard: PropTypes.bool,
- show: PropTypes.bool,
+ shouldCloseOnEsc: PropTypes.bool,
+ isOpen: PropTypes.bool,
title: PropTypes.string,
- onHide: PropTypes.func,
- footer: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- PropTypes.func,
- ]),
- header: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- PropTypes.func,
- ]),
+ onClose: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
diff --git a/platform/ui/src/components/ohifModal/OHIFModal.styl b/platform/ui/src/components/ohifModal/OHIFModal.styl
new file mode 100644
index 000000000..0118e7809
--- /dev/null
+++ b/platform/ui/src/components/ohifModal/OHIFModal.styl
@@ -0,0 +1,67 @@
+.OHIFModal
+ background-color: var(--ui-gray-darker)
+ border-color: var(--ui-border-color)
+ color: var(--text-secondary-color)
+ border-radius: 6px
+ border: 0
+ color: var(--text-primary-color)
+ position: relative
+ -webkit-box-shadow: 0 3px 9px rgba(0,0,0,.5)
+ box-shadow: 0 3px 9px rgba(0,0,0,.5);
+ background-clip: padding-box
+ outline: 0
+
+ @media (min-width: 320px)
+ width: 78%
+ min-width: 300px
+
+ @media (min-width: 768px)
+ width: 600px
+
+ @media (min-width: 992px)
+ width: 900px
+
+ &__content
+ padding: 20px
+ max-height: 90vh;
+ overflow-x: hidden;
+ overflow-y: auto;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+ &::-webkit-scrollbar
+ display: none;
+
+ &__header
+ display: flex
+ justify-content: space-between
+ align-items: center
+ border-bottom-width: 3px
+ border-bottom-style: solid
+ border-bottom-color: #000000
+ padding: 20px
+ position: relative
+
+ h4
+ color: var(--text-secondary-color)
+ font-size: 20px
+ font-weight: 500
+ line-height: 24px
+ padding-right: 24px
+ margin: 0
+
+ button
+ cursor: pointer
+ margin: -10px 0 0 0
+ padding: 0
+ background-color: transparent
+ border: none
+ color: var(--text-secondary-color)
+ font-size: 25px
+ font-weight: lighter
+
+ &:active,
+ &:focus,
+ &:focus:active
+ background-image: none
+ outline: 0
+ box-shadow: none
diff --git a/platform/ui/src/components/overlayTrigger/Overlay.js b/platform/ui/src/components/overlayTrigger/Overlay.js
index e824045d5..4e59ab9be 100644
--- a/platform/ui/src/components/overlayTrigger/Overlay.js
+++ b/platform/ui/src/components/overlayTrigger/Overlay.js
@@ -3,7 +3,7 @@ import React, { cloneElement } from 'react';
import PropTypes from 'prop-types';
import { Overlay as BaseOverlay } from 'react-overlays';
import elementType from 'prop-types-extra/lib/elementType';
-import { withTranslation } from '../../utils/LanguageProvider';
+import { withTranslation } from '../../contextProviders';
import Fade from './Fade';
diff --git a/platform/ui/src/components/snackbar/SnackbarContainer.js b/platform/ui/src/components/snackbar/SnackbarContainer.js
index 8a1d804ed..e49d1c73e 100644
--- a/platform/ui/src/components/snackbar/SnackbarContainer.js
+++ b/platform/ui/src/components/snackbar/SnackbarContainer.js
@@ -1,7 +1,7 @@
import React from 'react';
import SnackbarItem from './SnackbarItem';
import './Snackbar.css';
-import { useSnackbarContext } from '../../utils/SnackbarProvider';
+import { useSnackbarContext } from '../../contextProviders';
const SnackbarContainer = () => {
const { snackbarItems, hide } = useSnackbarContext();
diff --git a/platform/ui/src/components/studyList/StudyList.js b/platform/ui/src/components/studyList/StudyList.js
index 76e4c1e56..5ba9d7ee0 100644
--- a/platform/ui/src/components/studyList/StudyList.js
+++ b/platform/ui/src/components/studyList/StudyList.js
@@ -6,7 +6,7 @@ import TableSearchFilter from './TableSearchFilter.js';
import useMedia from '../../hooks/useMedia.js';
import PropTypes from 'prop-types';
import { StudyListLoadingText } from './StudyListLoadingText.js';
-import { withTranslation } from '../../utils/LanguageProvider';
+import { withTranslation } from '../../contextProviders';
/**
*
diff --git a/platform/ui/src/components/studyList/StudyListLoadingText.js b/platform/ui/src/components/studyList/StudyListLoadingText.js
index 9afa23770..b1c404d3c 100644
--- a/platform/ui/src/components/studyList/StudyListLoadingText.js
+++ b/platform/ui/src/components/studyList/StudyListLoadingText.js
@@ -1,7 +1,7 @@
import React from 'react';
import { Icon } from './../../elements/Icon';
// TODO: useTranslation
-import { withTranslation } from '../../utils/LanguageProvider';
+import { withTranslation } from '../../contextProviders';
function StudyListLoadingText({ t: translate }) {
return (
diff --git a/platform/ui/src/components/studyList/TablePagination.js b/platform/ui/src/components/studyList/TablePagination.js
index 9fa4452a4..1a55b9481 100644
--- a/platform/ui/src/components/studyList/TablePagination.js
+++ b/platform/ui/src/components/studyList/TablePagination.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './PaginationArea.styl';
-import { withTranslation } from '../../utils/LanguageProvider';
+import { withTranslation } from '../../contextProviders';
class TablePagination extends PureComponent {
static defaultProps = {
diff --git a/platform/ui/src/components/userPreferencesForm/HotKeysPreferences.styl b/platform/ui/src/components/userPreferencesForm/HotKeysPreferences.styl
index 98db2e52e..d34cbbbaa 100644
--- a/platform/ui/src/components/userPreferencesForm/HotKeysPreferences.styl
+++ b/platform/ui/src/components/userPreferencesForm/HotKeysPreferences.styl
@@ -1,7 +1,5 @@
.HotKeysPreferences
display: flex;
- margin-right: -15px;
- margin-left: -15px;
.column
width: 50%;
diff --git a/platform/ui/src/components/userPreferencesForm/UserPreferences.js b/platform/ui/src/components/userPreferencesForm/UserPreferences.js
index 7e8d462a5..7c6f6ad94 100644
--- a/platform/ui/src/components/userPreferencesForm/UserPreferences.js
+++ b/platform/ui/src/components/userPreferencesForm/UserPreferences.js
@@ -90,8 +90,8 @@ export class UserPreferences extends Component {
render() {
return (
-
-
+
+
- {
diff --git a/platform/ui/src/components/userPreferencesForm/UserPreferences.styl b/platform/ui/src/components/userPreferencesForm/UserPreferences.styl
index f62578e88..d3e3049c0 100644
--- a/platform/ui/src/components/userPreferencesForm/UserPreferences.styl
+++ b/platform/ui/src/components/userPreferencesForm/UserPreferences.styl
@@ -3,23 +3,27 @@
@import './../../design/styles/common/state.styl'
@import './../../design/styles/common/global.styl'
-.modal-body
- overflow: hidden
+.UserPreferences
+ display: flex
+ flex-direction: column
-.errorMessage
- color: var(--state-error-text)
- font-size: 10px
- text-transform: uppercase;
+ &__selector
+ border-bottom: 3px solid black
-.form-content
- border-bottom: 3px solid var(--primary-background-color)
- margin-bottom: 20px
- margin-left: -22px
- margin-right: -22px
- max-height: 70vh
- overflow-y: auto
- padding: 22px
- min-height: 500px
+ .errorMessage
+ color: var(--state-error-text)
+ font-size: 10px
+ text-transform: uppercase;
-.popover
- width: 300px
+ .form-content
+ border-bottom: 3px solid var(--primary-background-color)
+ margin-bottom: 20px
+ margin-left: -20px
+ margin-right: -20px
+ max-height: 70vh
+ overflow-y: auto
+ padding: 20px
+ min-height: 500px
+
+ .popover
+ width: 300px
diff --git a/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.js b/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.js
index 12044c50d..19b6e6161 100644
--- a/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.js
+++ b/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.js
@@ -2,7 +2,7 @@ import './UserPreferencesForm.styl';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { withTranslation } from '../../utils/LanguageProvider';
+import { withTranslation } from '../../contextProviders';
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
diff --git a/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.styl b/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.styl
index 6a62b1b08..4d6c60d42 100644
--- a/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.styl
+++ b/platform/ui/src/components/userPreferencesForm/UserPreferencesForm.styl
@@ -16,7 +16,6 @@
.footer
display: flex
flex-direction: row
- padding-bottom: 20px
justify-content: space-between
div
diff --git a/platform/ui/src/contextProviders/DialogProvider.js b/platform/ui/src/contextProviders/DialogProvider.js
new file mode 100644
index 000000000..ac1273d0b
--- /dev/null
+++ b/platform/ui/src/contextProviders/DialogProvider.js
@@ -0,0 +1,208 @@
+import React, {
+ useState,
+ createContext,
+ useContext,
+ useCallback,
+ useEffect,
+} from 'react';
+import PropTypes from 'prop-types';
+import Draggable from 'react-draggable';
+import classNames from 'classnames';
+
+import { utils } from '@ohif/core';
+
+import './DialogProvider.styl';
+
+const DialogContext = createContext(null);
+
+export const useDialog = () => useContext(DialogContext);
+
+const DialogProvider = ({ children, service }) => {
+ const [isDragging, setIsDragging] = useState(false);
+ const [dialogs, setDialogs] = useState([]);
+ const [lastDialogPosition, setLastDialogPosition] = useState(null);
+
+ /**
+ * Sets the implementation of a dialog service that can be used by extensions.
+ *
+ * @returns void
+ */
+ useEffect(() => {
+ if (service) {
+ service.setServiceImplementation({ create, dismiss, dismissAll });
+ }
+ }, [create, dismiss, service]);
+
+ /**
+ * Creates a dialog and return its id.
+ *
+ * @returns id
+ */
+ const create = useCallback(
+ ({
+ id,
+ content,
+ onSubmit,
+ onClose,
+ onDrag,
+ onStop,
+ isDraggable,
+ defaultPosition,
+ position,
+ }) => {
+ let dialogId = id;
+ if (!dialogId) {
+ dialogId = utils.guid();
+ }
+
+ const newDialog = {
+ id: dialogId,
+ content,
+ onSubmit,
+ onClose,
+ onDrag,
+ onStop,
+ isDraggable,
+ defaultPosition,
+ position,
+ };
+
+ setDialogs(dialogs => [...dialogs, newDialog]);
+
+ return dialogId;
+ },
+ []
+ );
+
+ /**
+ * Dismisses the dialog with a given id.
+ *
+ * @returns void
+ */
+ const dismiss = useCallback(({ id }) => {
+ setDialogs(dialogs => dialogs.filter(dialog => dialog.id !== id));
+ }, []);
+
+ /**
+ * Dismisses all dialogs.
+ *
+ * @returns void
+ */
+ const dismissAll = () => {
+ setDialogs([]);
+ };
+
+ /**
+ * Moves the dialog to the foreground if clicked.
+ *
+ * @returns void
+ */
+ const _reorder = id => {
+ setDialogs(dialogs => [
+ ...dialogs.filter(dialog => dialog.id !== id),
+ dialogs.find(dialog => dialog.id === id),
+ ]);
+ };
+
+ const _updateLastDialogPosition = dialogId => {
+ const draggableItemBounds = document
+ .querySelector(`#draggableItem-${dialogId}`)
+ .getBoundingClientRect();
+ setLastDialogPosition({
+ x: draggableItemBounds.x,
+ y: draggableItemBounds.y,
+ });
+ };
+
+ return (
+
+
+ {dialogs.map(dialog => {
+ const {
+ id,
+ content: Dialog,
+ position /* Position of the dialog. {{x: 0, y: 0}} */,
+ defaultPosition,
+ isDraggable = true,
+ onStart = () => {},
+ onStop = () => {},
+ onDrag = () => {},
+ } = dialog;
+
+ return (
+
{
+ const e = event || window.event;
+ const target = e.target || e.srcElement;
+ const BLACKLIST = ['SVG', 'BUTTON', 'PATH', 'INPUT'];
+ if (BLACKLIST.includes(target.tagName.toUpperCase())) {
+ return false;
+ }
+
+ onStart(event);
+ }}
+ onStop={event => {
+ onStop(event);
+ setIsDragging(false);
+ return;
+ }}
+ onDrag={event => {
+ setIsDragging(true);
+ _reorder(id);
+ _updateLastDialogPosition(id);
+ onDrag(event);
+ }}
+ >
+ _reorder(id)}
+ >
+
+
+
+ );
+ })}
+
+ {children}
+
+ );
+};
+
+DialogProvider.defaultProps = {
+ service: null,
+};
+
+DialogProvider.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.func,
+ ]).isRequired,
+ service: PropTypes.shape({
+ setServiceImplementation: PropTypes.func,
+ }),
+};
+
+/**
+ *
+ * High Order Component to use the dialog methods through a Class Component
+ *
+ */
+export const withDialog = Component => {
+ return function WrappedComponent(props) {
+ const { create, dismiss, dismissAll } = useDialog();
+ return ;
+ };
+};
+
+export default DialogProvider;
diff --git a/platform/ui/src/contextProviders/DialogProvider.styl b/platform/ui/src/contextProviders/DialogProvider.styl
new file mode 100644
index 000000000..de697d3bd
--- /dev/null
+++ b/platform/ui/src/contextProviders/DialogProvider.styl
@@ -0,0 +1,12 @@
+.DraggableItem
+ div
+ cursor: grab !important
+
+.DraggableItem.dragging
+ div
+ cursor: grabbing !important
+
+.DraggableArea
+ width: 100vw
+ height: 100vh
+ position: absolute
diff --git a/platform/ui/src/utils/LanguageProvider.js b/platform/ui/src/contextProviders/LanguageProvider.js
similarity index 100%
rename from platform/ui/src/utils/LanguageProvider.js
rename to platform/ui/src/contextProviders/LanguageProvider.js
diff --git a/platform/ui/src/utils/ModalProvider.js b/platform/ui/src/contextProviders/ModalProvider.js
similarity index 81%
rename from platform/ui/src/utils/ModalProvider.js
rename to platform/ui/src/contextProviders/ModalProvider.js
index 0b752cfaf..3446a75db 100644
--- a/platform/ui/src/utils/ModalProvider.js
+++ b/platform/ui/src/contextProviders/ModalProvider.js
@@ -16,14 +16,11 @@ export const useModal = () => useContext(ModalContext);
const ModalProvider = ({ children, modal: Modal, service }) => {
const DEFAULT_OPTIONS = {
component: null /* The component instance inside the modal. */,
- header: null /* The content inside the modal header. */,
- footer: null /* The content inside the modal footer. */,
- backdrop: false /* Should the modal render a backdrop overlay. */,
- keyboard: false /* Modal is dismissible via the esc key. */,
- show: true /* Make the Modal visible or hidden. */,
+ shouldCloseOnEsc: false /* Modal is dismissible via the esc key. */,
+ isOpen: true /* Make the Modal visible or hidden. */,
closeButton: true /* Should the modal body render the close button. */,
title: null /* Should the modal render the title independently of the body content. */,
- customClassName: null /* The custom class to style the modal. */,
+ customClassName: '' /* The custom class to style the modal. */,
};
const [options, setOptions] = useState(DEFAULT_OPTIONS);
@@ -69,14 +66,11 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
options.customClassName,
options.component.className
)}
- backdrop={options.backdrop}
- keyboard={options.keyboard}
- show={options.show}
+ shouldCloseOnEsc={options.keyboard}
+ isOpen={options.isOpen}
title={options.title}
closeButton={options.closeButton}
- footer={options.footer}
- header={options.header}
- onHide={hide}
+ onClose={hide}
>
diff --git a/platform/ui/src/utils/SnackbarProvider.js b/platform/ui/src/contextProviders/SnackbarProvider.js
similarity index 100%
rename from platform/ui/src/utils/SnackbarProvider.js
rename to platform/ui/src/contextProviders/SnackbarProvider.js
diff --git a/platform/ui/src/contextProviders/index.js b/platform/ui/src/contextProviders/index.js
new file mode 100644
index 000000000..e4ca06143
--- /dev/null
+++ b/platform/ui/src/contextProviders/index.js
@@ -0,0 +1,20 @@
+export {
+ default as ModalProvider,
+ useModal,
+ withModal,
+ ModalConsumer,
+} from './ModalProvider.js';
+export {
+ default as SnackbarProvider,
+ useSnackbarContext,
+ withSnackbar,
+} from './SnackbarProvider.js';
+export {
+ default as LanguageProvider,
+ withTranslation,
+} from './LanguageProvider.js';
+export {
+ default as DialogProvider,
+ withDialog,
+ useDialog,
+} from './DialogProvider.js';
diff --git a/platform/ui/src/index.js b/platform/ui/src/index.js
index 191620d71..6af64c47d 100644
--- a/platform/ui/src/index.js
+++ b/platform/ui/src/index.js
@@ -49,15 +49,18 @@ import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
import Toolbar from './viewer/Toolbar.js';
import ToolbarButton from './viewer/ToolbarButton.js';
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
-import SnackbarProvider, {
+import {
+ SnackbarProvider,
useSnackbarContext,
withSnackbar,
-} from './utils/SnackbarProvider';
-import ModalProvider, {
+ DialogProvider,
+ useDialog,
+ withDialog,
+ ModalProvider,
+ ModalConsumer,
useModal,
withModal,
- ModalConsumer,
-} from './utils/ModalProvider';
+} from './contextProviders';
export {
// Elements
@@ -111,6 +114,9 @@ export {
ModalConsumer,
withModal,
OHIFModal,
+ DialogProvider,
+ withDialog,
+ useDialog,
// Hooks
useDebounce,
useMedia,
diff --git a/platform/ui/src/viewer/ToolbarButton.js b/platform/ui/src/viewer/ToolbarButton.js
index 38b4f0ed9..2959059b3 100644
--- a/platform/ui/src/viewer/ToolbarButton.js
+++ b/platform/ui/src/viewer/ToolbarButton.js
@@ -4,7 +4,7 @@ import { Icon } from './../elements/Icon';
import PropTypes from 'prop-types';
import React from 'react';
import classnames from 'classnames';
-import { withTranslation } from '../utils/LanguageProvider';
+import { withTranslation } from '../contextProviders';
export function ToolbarButton(props) {
const { isActive, icon, labelWhenActive, onClick, t } = props;
diff --git a/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js b/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js
index d1163452e..ad7bdd6dd 100644
--- a/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js
+++ b/platform/viewer/cypress/integration/common/OHIFStudyViewer.spec.js
@@ -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');
});
});
diff --git a/platform/viewer/src/App.js b/platform/viewer/src/App.js
index 7dc1f0bcf..0f797b039 100644
--- a/platform/viewer/src/App.js
+++ b/platform/viewer/src/App.js
@@ -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 {
-
-
-
+
+
+
+
+
@@ -138,9 +156,11 @@ class App extends Component {
-
-
-
+
+
+
+
+
diff --git a/platform/viewer/src/components/Header/Header.js b/platform/viewer/src/components/Header/Header.js
index 9da9f401f..fc8074078 100644
--- a/platform/viewer/src/components/Header/Header.js
+++ b/platform/viewer/src/components/Header/Header.js
@@ -49,7 +49,6 @@ class Header extends Component {
onClick: () =>
show(AboutContent, {
title: t('OHIF Viewer - About'),
- customClassName: 'AboutContent',
}),
},
{
diff --git a/platform/viewer/src/connectedComponents/ToolbarRow.js b/platform/viewer/src/connectedComponents/ToolbarRow.js
index 007da4335..975b4d0e6 100644
--- a/platform/viewer/src/connectedComponents/ToolbarRow.js
+++ b/platform/viewer/src/connectedComponents/ToolbarRow.js
@@ -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 {
)}
-
-
-
>
);
}
@@ -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 (
);
@@ -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))
);
diff --git a/yarn.lock b/yarn.lock
index b7513f5b6..6f07a1f74 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1100,13 +1100,34 @@
pirates "^4.0.0"
source-map-support "^0.5.9"
-"@babel/runtime@7.1.2", "@babel/runtime@7.5.5", "@babel/runtime@7.6.0", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3":
+"@babel/runtime@7.1.2":
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3"
+ integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==
+ dependencies:
+ regenerator-runtime "^0.12.0"
+
+"@babel/runtime@7.6.0":
+ version "7.6.0"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.6.0.tgz#4fc1d642a9fd0299754e8b5de62c631cf5568205"
+ integrity sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ==
+ dependencies:
+ regenerator-runtime "^0.13.2"
+
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
dependencies:
regenerator-runtime "^0.13.2"
+"@babel/runtime@^7.6.0", "@babel/runtime@^7.6.3":
+ version "7.7.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a"
+ integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw==
+ dependencies:
+ regenerator-runtime "^0.13.2"
+
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
@@ -8074,6 +8095,11 @@ executable@4.1.1:
dependencies:
pify "^2.2.0"
+exenv@^1.2.0:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d"
+ integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=
+
exif-parser@^0.1.12, exif-parser@^0.1.9:
version "0.1.12"
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
@@ -16019,6 +16045,14 @@ react-dom@^16.8.6:
prop-types "^15.6.2"
scheduler "^0.17.0"
+react-draggable@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.1.0.tgz#e1c5b774001e32f0bff397254e1e9d5448ac92a4"
+ integrity sha512-Or/qe70cfymshqoC8Lsp0ukTzijJObehb7Vfl7tb5JRxoV+b6PDkOGoqYaWBzZ59k9dH/bwraLGsnlW78/3vrA==
+ dependencies:
+ classnames "^2.2.5"
+ prop-types "^15.6.0"
+
react-dropzone@^10.1.7:
version "10.1.10"
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.10.tgz#f340290dfc26ac09ad68abc020ab6232c23d6cf3"
@@ -16082,7 +16116,7 @@ react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==
-react-lifecycles-compat@^3.0.4:
+react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
@@ -16101,6 +16135,16 @@ react-live@2.0.1:
react-simple-code-editor "^0.9.0"
unescape "^0.2.0"
+react-modal@^3.11.1:
+ version "3.11.1"
+ resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.1.tgz#2a0d6877c9e98f123939ea92d2bb4ad7fa5a17f9"
+ integrity sha512-8uN744Yq0X2lbfSLxsEEc2UV3RjSRb4yDVxRQ1aGzPo86QjNOwhQSukDb8U8kR+636TRTvfMren10fgOjAy9eA==
+ dependencies:
+ exenv "^1.2.0"
+ prop-types "^15.5.10"
+ react-lifecycles-compat "^3.0.0"
+ warning "^4.0.3"
+
react-moment-proptypes@^1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/react-moment-proptypes/-/react-moment-proptypes-1.7.0.tgz#89881479840a76c13574a86e3bb214c4ba564e7a"
@@ -16613,6 +16657,11 @@ regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
+regenerator-runtime@^0.12.0:
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
+ integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
+
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2:
version "0.13.3"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
@@ -20041,6 +20090,13 @@ warning@^3.0.0:
dependencies:
loose-envify "^1.0.0"
+warning@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
+ integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==
+ dependencies:
+ loose-envify "^1.0.0"
+
watchpack@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"