diff --git a/platform/ui/index.js b/platform/ui/index.js
index 827147317..4dd7ddb85 100644
--- a/platform/ui/index.js
+++ b/platform/ui/index.js
@@ -4,16 +4,22 @@ export { utils };
/** CONTEXT/HOOKS */
export {
+ DialogProvider,
+ useDialog,
+ withDialog,
DragAndDropProvider,
ModalProvider,
ModalConsumer,
useModal,
withModal,
- ViewportDialogProvider,
- useViewportDialog,
ImageViewerContext,
ImageViewerProvider,
useImageViewer,
+ SnackbarProvider,
+ useSnackbar,
+ withSnackbar,
+ ViewportDialogProvider,
+ useViewportDialog,
ViewportGridContext,
ViewportGridProvider,
useViewportGrid,
@@ -37,6 +43,7 @@ export {
Label,
MeasurementsPanel,
MeasurementTable,
+ Modal,
NavBar,
Notification,
Select,
diff --git a/platform/ui/package.json b/platform/ui/package.json
index 81bc781aa..add38db86 100644
--- a/platform/ui/package.json
+++ b/platform/ui/package.json
@@ -43,6 +43,7 @@
"react-dnd-html5-backend": "^10.0.2",
"react-dnd-touch-backend": "^10.0.2",
"react-dom": "16.11.0",
+ "react-modal": "^3.11.2",
"react-powerplug": "1.0.0",
"react-select": "^3.0.8",
"theme-ui": "^0.2.38"
diff --git a/platform/ui/src/components/Modal/Modal.jsx b/platform/ui/src/components/Modal/Modal.jsx
new file mode 100644
index 000000000..4ef669cbf
--- /dev/null
+++ b/platform/ui/src/components/Modal/Modal.jsx
@@ -0,0 +1,72 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ReactModal from 'react-modal';
+import classNames from 'classnames';
+
+const customStyle = {
+ overlay: {
+ zIndex: 1071,
+ backgroundColor: 'rgb(0, 0, 0, 0.5)',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+};
+
+ReactModal.setAppElement(document.getElementById('root'));
+
+const Modal = ({
+ className,
+ closeButton,
+ shouldCloseOnEsc,
+ isOpen,
+ title,
+ onClose,
+ children,
+}) => {
+ const renderHeader = () => {
+ return (
+ title && (
+
+ {title}
+ {closeButton && (
+
+ )}
+
+ )
+ );
+ };
+
+ return (
+
+ <>
+ {renderHeader()}
+
+ >
+
+ );
+};
+
+Modal.propTypes = {
+ className: PropTypes.string,
+ closeButton: PropTypes.bool,
+ shouldCloseOnEsc: PropTypes.bool,
+ isOpen: PropTypes.bool,
+ title: PropTypes.string,
+ onClose: PropTypes.func,
+ /** The modal's content */
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ ]).isRequired,
+};
+
+export default Modal;
diff --git a/platform/ui/src/components/Modal/index.js b/platform/ui/src/components/Modal/index.js
new file mode 100644
index 000000000..f44559f49
--- /dev/null
+++ b/platform/ui/src/components/Modal/index.js
@@ -0,0 +1,2 @@
+import Modal from './Modal';
+export default Modal;
diff --git a/platform/ui/src/components/Snackbar/SnackbarContainer.jsx b/platform/ui/src/components/Snackbar/SnackbarContainer.jsx
new file mode 100644
index 000000000..eb0e83fb2
--- /dev/null
+++ b/platform/ui/src/components/Snackbar/SnackbarContainer.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import SnackbarItem from './SnackbarItem';
+import { useSnackbar } from '../../contextProviders';
+
+const SnackbarContainer = () => {
+ const { snackbarItems, hide } = useSnackbar();
+
+ const renderItem = item => {
+ return ;
+ };
+
+ if (!snackbarItems) {
+ return null;
+ }
+
+ const renderItems = () => {
+ const items = {
+ topLeft: [],
+ topCenter: [],
+ topRight: [],
+ bottomLeft: [],
+ bottomCenter: [],
+ bottomRight: [],
+ };
+
+ snackbarItems.map(item => {
+ items[item.position].push(item);
+ });
+
+ return (
+
+ {Object.keys(items).map(pos => {
+ if (!items[pos].length) {
+ return null;
+ }
+
+ return (
+
+ {items[pos].map((item, index) => (
+
{renderItem(item)}
+ ))}
+
+ );
+ })}
+
+ );
+ };
+
+ return <>{renderItems()}>;
+};
+
+export default SnackbarContainer;
diff --git a/platform/ui/src/components/Snackbar/SnackbarItem.jsx b/platform/ui/src/components/Snackbar/SnackbarItem.jsx
new file mode 100644
index 000000000..abeef2ccf
--- /dev/null
+++ b/platform/ui/src/components/Snackbar/SnackbarItem.jsx
@@ -0,0 +1,27 @@
+import React, { useEffect } from 'react';
+
+const SnackbarItem = ({ options, onClose }) => {
+ const handleClose = () => {
+ onClose(options.id);
+ };
+
+ useEffect(() => {
+ if (options.autoClose) {
+ setTimeout(() => {
+ handleClose();
+ }, options.duration);
+ }
+ }, []);
+
+ return (
+
+
+ x
+
+ {options.title &&
{options.title}
}
+ {options.message &&
{options.message}
}
+
+ );
+};
+
+export default SnackbarItem;
diff --git a/platform/ui/src/components/Snackbar/SnackbarTypes.js b/platform/ui/src/components/Snackbar/SnackbarTypes.js
new file mode 100644
index 000000000..3dffcb5c9
--- /dev/null
+++ b/platform/ui/src/components/Snackbar/SnackbarTypes.js
@@ -0,0 +1,6 @@
+export default {
+ INFO: 'info',
+ WARNING: 'warning',
+ SUCCESS: 'success',
+ ERROR: 'error',
+};
diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js
index 9a5589f90..cc224c2bd 100644
--- a/platform/ui/src/components/index.js
+++ b/platform/ui/src/components/index.js
@@ -14,6 +14,7 @@ import InputText from './InputText';
import Label from './Label';
import MeasurementsPanel from './MeasurementsPanel';
import MeasurementTable from './MeasurementTable';
+import Modal from './Modal';
import NavBar from './NavBar';
import Notification from './Notification';
import Select from './Select';
@@ -62,6 +63,7 @@ export {
Label,
MeasurementsPanel,
MeasurementTable,
+ Modal,
NavBar,
Notification,
Select,
diff --git a/platform/ui/src/contextProviders/DialogProvider.jsx b/platform/ui/src/contextProviders/DialogProvider.jsx
new file mode 100644
index 000000000..eb3c207cb
--- /dev/null
+++ b/platform/ui/src/contextProviders/DialogProvider.jsx
@@ -0,0 +1,280 @@
+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';
+
+
+const DialogContext = createContext(null);
+
+export const useDialog = () => useContext(DialogContext);
+
+const DialogProvider = ({ children, service }) => {
+ const [isDragging, setIsDragging] = useState(false);
+ const [dialogs, setDialogs] = useState([]);
+ const [lastDialogId, setLastDialogId] = useState(null);
+ const [lastDialogPosition, setLastDialogPosition] = useState(null);
+ const [centerPositions, setCenterPositions] = useState([]);
+
+ useEffect(() => {
+ setCenterPositions(
+ dialogs.map(dialog => ({
+ id: dialog.id,
+ ...getCenterPosition(dialog.id),
+ }))
+ );
+ }, [dialogs]);
+
+ const getCenterPosition = id => {
+ const root = document.querySelector('#root');
+ const centerX = root.offsetLeft + root.offsetWidth / 2;
+ const centerY = root.offsetTop + root.offsetHeight / 2;
+ const item = document.querySelector(`#draggableItem-${id}`);
+ const itemBounds = item.getBoundingClientRect();
+ return {
+ x: centerX - itemBounds.width / 2,
+ y: centerY - itemBounds.height / 2,
+ };
+ };
+
+ /**
+ * 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]);
+
+ /**
+ * UI Dialog
+ *
+ * @typedef {Object} DialogProps
+ * @property {string} id The dialog id.
+ * @property {DialogContent} content The dialog content.
+ * @property {Object} contentProps The dialog content props.
+ * @property {boolean} isDraggable Controls if dialog content is draggable or not.
+ * @property {boolean} showOverlay Controls dialog overlay.
+ * @property {boolean} centralize Center the dialog on the screen.
+ * @property {boolean} preservePosition Use last position instead of default.
+ * @property {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at.
+ * @property {Function} onStart Called when dragging starts. If `false` is returned any handler, the action will cancel.
+ * @property {Function} onStop Called when dragging stops.
+ * @property {Function} onDrag Called while dragging.
+ */
+
+ useEffect(() => _bringToFront(lastDialogId), [_bringToFront, lastDialogId]);
+
+ /**
+ * Creates a new dialog and return its id.
+ *
+ * @param {DialogProps} props The dialog props.
+ * @returns The new dialog id.
+ */
+ const create = useCallback(props => {
+ const { id } = props;
+
+ let dialogId = id;
+ if (!dialogId) {
+ dialogId = utils.guid();
+ }
+
+ setDialogs(dialogs => [...dialogs, { ...props, id: dialogId }]);
+ setLastDialogId(dialogId);
+
+ return dialogId;
+ }, []);
+
+ /**
+ * Dismisses the dialog with a given id.
+ *
+ * @param {Object} props -
+ * @property {string} props.id The dialog id.
+ * @returns void
+ */
+ const dismiss = useCallback(
+ ({ id }) =>
+ setDialogs(dialogs => dialogs.filter(dialog => dialog.id !== id)),
+ []
+ );
+
+ /**
+ * Dismisses all dialogs.
+ *
+ * @returns void
+ */
+ const dismissAll = () => {
+ setDialogs([]);
+ };
+
+ /**
+ * Indicate if there are no dialogs present.
+ *
+ * @returns True if no dialogs are present.
+ */
+ const isEmpty = () => dialogs && dialogs.length < 1;
+
+ /**
+ * Moves the dialog to the foreground if clicked.
+ *
+ * @param {string} id The dialog id.
+ * @returns void
+ */
+ const _bringToFront = useCallback(id => {
+ setDialogs(dialogs => {
+ const topDialog = dialogs.find(dialog => dialog.id === id);
+ return topDialog
+ ? [...dialogs.filter(dialog => dialog.id !== id), topDialog]
+ : dialogs;
+ });
+ }, []);
+
+ const renderDialogs = () =>
+ dialogs.map(dialog => {
+ const {
+ id,
+ content: DialogContent,
+ contentProps,
+ defaultPosition,
+ centralize = false,
+ preservePosition = true,
+ isDraggable = true,
+ onStart,
+ onStop,
+ onDrag,
+ } = dialog;
+
+ let position =
+ (preservePosition && lastDialogPosition) || defaultPosition;
+ if (centralize) {
+ position = centerPositions.find(position => position.id === id);
+ }
+
+ return (
+ {
+ const e = event || window.event;
+ const target = e.target || e.srcElement;
+ const BLACKLIST = [
+ 'SVG',
+ 'BUTTON',
+ 'PATH',
+ 'INPUT',
+ 'SPAN',
+ 'LABEL',
+ ];
+ if (BLACKLIST.includes(target.tagName.toUpperCase())) {
+ return false;
+ }
+
+ if (validCallback(onStart)) {
+ return onStart(event);
+ }
+ }}
+ onStop={event => {
+ setIsDragging(false);
+
+ if (validCallback(onStop)) {
+ return onStop(event);
+ }
+ }}
+ onDrag={event => {
+ setIsDragging(true);
+ _bringToFront(id);
+ _updateLastDialogPosition(id);
+
+ if (validCallback(onDrag)) {
+ return onDrag(event);
+ }
+ }}
+ >
+ _bringToFront(id)}
+ >
+
+
+
+ );
+ });
+
+ /**
+ * Update the last dialog position to be used as the new default position.
+ *
+ * @returns void
+ */
+ const _updateLastDialogPosition = dialogId => {
+ const draggableItemBounds = document
+ .querySelector(`#draggableItem-${dialogId}`)
+ .getBoundingClientRect();
+ setLastDialogPosition({
+ x: draggableItemBounds.x,
+ y: draggableItemBounds.y,
+ });
+ };
+
+ const validCallback = callback => callback && typeof callback === 'function';
+
+ return (
+
+
+ {dialogs.some(dialog => dialog.showOverlay) ? (
+
{renderDialogs()}
+ ) : (
+ renderDialogs()
+ )}
+
+ {children}
+
+ );
+};
+
+/**
+ *
+ * High Order Component to use the dialog methods through a Class Component
+ *
+ */
+export const withDialog = Component => {
+ return function WrappedComponent(props) {
+ const { create, dismiss, dismissAll, isEmpty } = useDialog();
+ return (
+
+ );
+ };
+};
+
+DialogProvider.defaultProps = {
+ service: null,
+};
+
+DialogProvider.propTypes = {
+ children: PropTypes.oneOfType([
+ PropTypes.arrayOf(PropTypes.node),
+ PropTypes.node,
+ PropTypes.func,
+ ]).isRequired,
+ service: PropTypes.shape({
+ setServiceImplementation: PropTypes.func,
+ }),
+};
+
+export default DialogProvider;
diff --git a/platform/ui/src/contextProviders/ModalProvider.js b/platform/ui/src/contextProviders/ModalProvider.jsx
similarity index 95%
rename from platform/ui/src/contextProviders/ModalProvider.js
rename to platform/ui/src/contextProviders/ModalProvider.jsx
index 74a53c02f..e8d65635d 100644
--- a/platform/ui/src/contextProviders/ModalProvider.js
+++ b/platform/ui/src/contextProviders/ModalProvider.jsx
@@ -39,6 +39,17 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
const [options, setOptions] = useState(DEFAULT_OPTIONS);
+ /**
+ * Sets the implementation of a modal service that can be used by extensions.
+ *
+ * @returns void
+ */
+ useEffect(() => {
+ if (service) {
+ service.setServiceImplementation({ hide, show });
+ }
+ }, [hide, service, show]);
+
/**
* Show the modal and override its configuration props.
*
@@ -58,17 +69,6 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
DEFAULT_OPTIONS,
]);
- /**
- * Sets the implementation of a modal service that can be used by extensions.
- *
- * @returns void
- */
- useEffect(() => {
- if (service) {
- service.setServiceImplementation({ hide, show });
- }
- }, [hide, service, show]);
-
const {
content: ModalContent,
contentProps,
@@ -115,18 +115,15 @@ ModalProvider.defaultProps = {
};
ModalProvider.propTypes = {
- /** Children that will be wrapped with Modal Context */
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
- /** Modal component */
modal: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
- /** service to be update once modal provider is instanciated */
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
diff --git a/platform/ui/src/contextProviders/SnackbarProvider.jsx b/platform/ui/src/contextProviders/SnackbarProvider.jsx
new file mode 100644
index 000000000..33d93b749
--- /dev/null
+++ b/platform/ui/src/contextProviders/SnackbarProvider.jsx
@@ -0,0 +1,142 @@
+import React, {
+ useState,
+ createContext,
+ useContext,
+ useCallback,
+ useEffect,
+} from 'react';
+import PropTypes from 'prop-types';
+
+import SnackbarContainer from '../components/Snackbar/SnackbarContainer';
+import SnackbarTypes from '../components/Snackbar/SnackbarTypes';
+
+const SnackbarContext = createContext(null);
+
+export const useSnackbar = () => useContext(SnackbarContext);
+
+const SnackbarProvider = ({ children, service }) => {
+ const DEFAULT_OPTIONS = {
+ title: '',
+ message: '',
+ duration: 5000,
+ autoClose: true,
+ position: 'bottomRight',
+ type: SnackbarTypes.INFO,
+ };
+
+ const [count, setCount] = useState(1);
+ const [snackbarItems, setSnackbarItems] = useState([]);
+
+ /**
+ * Sets the implementation of a notification service that can be used by extensions.
+ *
+ * @returns void
+ */
+ useEffect(() => {
+ if (service) {
+ service.setServiceImplementation({ hide, show });
+ }
+ }, [service, hide, show]);
+
+ const show = useCallback(
+ options => {
+ if (!options || (!options.title && !options.message)) {
+ console.warn(
+ 'Snackbar cannot be rendered without required parameters: title | message'
+ );
+
+ return null;
+ }
+
+ const newItem = {
+ ...DEFAULT_OPTIONS,
+ ...options,
+ id: count,
+ visible: true,
+ };
+
+ setSnackbarItems(state => [...state, newItem]);
+ setCount(count + 1);
+ },
+ [count, DEFAULT_OPTIONS]
+ );
+
+ const hide = useCallback(
+ id => {
+ const hideItem = items => {
+ const newItems = items.map(item => {
+ if (item.id === id) {
+ item.visible = false;
+ }
+
+ return item;
+ });
+
+ return newItems;
+ };
+
+ setSnackbarItems(state => hideItem(state));
+
+ setTimeout(() => {
+ setSnackbarItems(state => [...state.filter(item => item.id !== id)]);
+ }, 1000);
+ },
+ [setSnackbarItems]
+ );
+
+ const hideAll = () => {
+ // reset count
+ setCount(1);
+
+ // remove all items from array
+ setSnackbarItems(() => []);
+ };
+
+ /**
+ * expose snackbar methods to window for debug purposes
+ * TODO: Check if it's really necessary
+ */
+ window.snackbar = {
+ show,
+ hide,
+ hideAll,
+ };
+
+ return (
+
+ {!!snackbarItems && }
+ {children}
+
+ );
+};
+
+SnackbarProvider.defaultProps = {
+ service: null,
+};
+
+SnackbarProvider.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 snackbar methods through a Class Component
+ *
+ */
+export const withSnackbar = Component => {
+ return function WrappedComponent(props) {
+ const snackbarContext = {
+ ...useSnackbarContext(),
+ };
+ return ;
+ };
+};
+
+export default SnackbarProvider;
diff --git a/platform/ui/src/contextProviders/index.js b/platform/ui/src/contextProviders/index.js
index 571ce4294..6874f1a07 100644
--- a/platform/ui/src/contextProviders/index.js
+++ b/platform/ui/src/contextProviders/index.js
@@ -1,3 +1,11 @@
+export {
+ default as DialogProvider,
+ useDialog,
+ withDialog,
+} from './DialogProvider'
+
+export { default as DragAndDropProvider } from './DragAndDropProvider';
+
export {
default as ModalProvider,
useModal,
@@ -5,21 +13,25 @@ export {
ModalConsumer,
} from './ModalProvider';
-export {
- default as ViewportDialogProvider,
- useViewportDialog,
-} from './ViewportDialogProvider';
-
export {
ImageViewerContext,
ImageViewerProvider,
useImageViewer,
} from './ImageViewerProvider';
+export {
+ default as SnackbarProvider,
+ useSnackbar,
+ withSnackbar,
+} from './SnackbarProvider'
+
+export {
+ default as ViewportDialogProvider,
+ useViewportDialog,
+} from './ViewportDialogProvider';
+
export {
ViewportGridContext,
ViewportGridProvider,
useViewportGrid,
} from './ViewportGridProvider';
-
-export { default as DragAndDropProvider } from './DragAndDropProvider';
diff --git a/platform/viewer/src/App.jsx b/platform/viewer/src/App.jsx
index 28ab3a4e9..46014c33b 100644
--- a/platform/viewer/src/App.jsx
+++ b/platform/viewer/src/App.jsx
@@ -2,7 +2,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { BrowserRouter, HashRouter } from 'react-router-dom';
-import { ThemeWrapper } from '@ohif/ui';
+import {
+ DialogProvider,
+ Modal,
+ ModalProvider,
+ SnackbarProvider,
+ ThemeWrapper,
+} from '@ohif/ui';
// Viewer Project
// TODO: Should this influence study list?
import { appConfigContext } from '@state/appConfig.context';
@@ -10,7 +16,7 @@ import { useAppConfig } from '@hooks/useAppConfig';
import createRoutes from './routes';
import appInit from './appInit.js';
-// Temporarily for testing
+// TODO: Temporarily for testing
import '@ohif/mode-example';
/**
@@ -40,11 +46,20 @@ function App({ config, defaultExtensions }) {
extensionManager,
servicesManager
);
+ const { UIDialogService, UIModalService, UINotificationService } = servicesManager.services;
return (
- {appRoutes}
+
+
+
+
+ {appRoutes}
+
+
+
+
);
diff --git a/yarn.lock b/yarn.lock
index b05065346..bcd4cdce0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -868,13 +868,27 @@
pirates "^4.0.0"
source-map-support "^0.5.16"
-"@babel/runtime@7.1.2", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4":
+"@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.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6":
version "7.7.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
dependencies:
regenerator-runtime "^0.13.2"
+"@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4":
+ version "7.9.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f"
+ integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/standalone@^7.4.5":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.8.6.tgz#1364534775c83bf7b7988e4ca98823bef56a0a53"
@@ -16720,7 +16734,7 @@ react-live@^2.2.1:
react-simple-code-editor "^0.10.0"
unescape "^1.0.1"
-react-modal@^3.11.1:
+react-modal@^3.11.1, react-modal@^3.11.2:
version "3.11.2"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.11.2.tgz#bad911976d4add31aa30dba8a41d11e21c4ac8a4"
integrity sha512-o8gvvCOFaG1T7W6JUvsYjRjMVToLZgLIsi5kdhFIQCtHxDkA47LznX62j+l6YQkpXDbvQegsDyxe/+JJsFQN7w==
@@ -17282,6 +17296,11 @@ regenerator-runtime@^0.11.0:
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"