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:
parent
cdd75bda2a
commit
f65639c2b0
@ -23,6 +23,17 @@ const TOOLBAR_BUTTON_TYPES = {
|
|||||||
BUILT_IN: 'builtIn',
|
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 = [
|
const definitions = [
|
||||||
{
|
{
|
||||||
id: 'StackScroll',
|
id: 'StackScroll',
|
||||||
@ -102,7 +113,7 @@ const definitions = [
|
|||||||
//
|
//
|
||||||
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
|
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
|
||||||
options: {
|
options: {
|
||||||
behavior: 'CINE',
|
behavior: TOOLBAR_BUTTON_BEHAVIORS.CINE,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -220,7 +231,8 @@ const definitions = [
|
|||||||
//
|
//
|
||||||
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
|
type: TOOLBAR_BUTTON_TYPES.BUILT_IN,
|
||||||
options: {
|
options: {
|
||||||
behavior: 'DOWNLOAD_SCREEN_SHOT',
|
behavior: TOOLBAR_BUTTON_BEHAVIORS.DOWNLOAD_SCREEN_SHOT,
|
||||||
|
togglable: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -19,7 +19,11 @@ import ui from './ui';
|
|||||||
import user from './user.js';
|
import user from './user.js';
|
||||||
import utils from './utils/';
|
import utils from './utils/';
|
||||||
|
|
||||||
import { createUINotificationService, createUIModalService } from './services';
|
import {
|
||||||
|
createUINotificationService,
|
||||||
|
createUIModalService,
|
||||||
|
createUIDialogService,
|
||||||
|
} from './services';
|
||||||
|
|
||||||
const OHIF = {
|
const OHIF = {
|
||||||
MODULE_TYPES,
|
MODULE_TYPES,
|
||||||
@ -48,6 +52,7 @@ const OHIF = {
|
|||||||
//
|
//
|
||||||
createUINotificationService,
|
createUINotificationService,
|
||||||
createUIModalService,
|
createUIModalService,
|
||||||
|
createUIDialogService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -76,6 +81,7 @@ export {
|
|||||||
//
|
//
|
||||||
createUINotificationService,
|
createUINotificationService,
|
||||||
createUIModalService,
|
createUIModalService,
|
||||||
|
createUIDialogService,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OHIF };
|
export { OHIF };
|
||||||
|
|||||||
@ -12,6 +12,7 @@ describe('Top level exports', () => {
|
|||||||
//
|
//
|
||||||
'createUINotificationService',
|
'createUINotificationService',
|
||||||
'createUIModalService',
|
'createUIModalService',
|
||||||
|
'createUIDialogService',
|
||||||
//
|
//
|
||||||
'utils',
|
'utils',
|
||||||
'studies',
|
'studies',
|
||||||
|
|||||||
125
platform/core/src/services/UIDialogService/index.js
Normal file
125
platform/core/src/services/UIDialogService/index.js
Normal file
@ -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;
|
||||||
@ -8,14 +8,11 @@
|
|||||||
* UI Modal
|
* UI Modal
|
||||||
*
|
*
|
||||||
* @typedef {Object} ModalProps
|
* @typedef {Object} ModalProps
|
||||||
* @property {string} [header=null] -
|
* @property {boolean} [shouldCloseOnEsc=false] -
|
||||||
* @property {string} [footer=null] -
|
* @property {boolean} [isOpen=true] -
|
||||||
* @property {string} [backdrop=false] -
|
* @property {boolean} [closeButton=true] -
|
||||||
* @property {string} [keyboard=false] -
|
|
||||||
* @property {number} [show=true] -
|
|
||||||
* @property {string} [closeButton=true] -
|
|
||||||
* @property {string} [title=null] - 'Modal Title'
|
* @property {string} [title=null] - 'Modal Title'
|
||||||
* @property {boolean} [customClassName=null] - '.ModalClass'
|
* @property {string} [customClassName=null] - '.ModalClass'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const uiModalServicePublicAPI = {
|
const uiModalServicePublicAPI = {
|
||||||
@ -38,16 +35,13 @@ function createUIModalService() {
|
|||||||
* Show a new UI modal;
|
* Show a new UI modal;
|
||||||
*
|
*
|
||||||
* @param {Modal} component React component
|
* @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(
|
function show(
|
||||||
component,
|
component,
|
||||||
props = {
|
props = {
|
||||||
header: null,
|
shouldCloseOnEsc: false,
|
||||||
footer: null,
|
isOpen: true,
|
||||||
backdrop: false,
|
|
||||||
keyboard: false,
|
|
||||||
show: true,
|
|
||||||
closeButton: true,
|
closeButton: true,
|
||||||
title: null,
|
title: null,
|
||||||
customClassName: null,
|
customClassName: null,
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
import ServicesManager from './ServicesManager.js';
|
import ServicesManager from './ServicesManager.js';
|
||||||
import createUINotificationService from './UINotificationService';
|
import createUINotificationService from './UINotificationService';
|
||||||
import createUIModalService from './UIModalService';
|
import createUIModalService from './UIModalService';
|
||||||
|
import createUIDialogService from './UIDialogService';
|
||||||
|
|
||||||
export { createUINotificationService, createUIModalService, ServicesManager };
|
export {
|
||||||
|
createUINotificationService,
|
||||||
|
createUIModalService,
|
||||||
|
createUIDialogService,
|
||||||
|
ServicesManager,
|
||||||
|
};
|
||||||
|
|||||||
@ -52,7 +52,9 @@
|
|||||||
"react-dnd": "9.4.0",
|
"react-dnd": "9.4.0",
|
||||||
"react-dnd-html5-backend": "^9.4.0",
|
"react-dnd-html5-backend": "^9.4.0",
|
||||||
"react-dnd-touch-backend": "^9.4.0",
|
"react-dnd-touch-backend": "^9.4.0",
|
||||||
|
"react-draggable": "^4.1.0",
|
||||||
"react-i18next": "^10.11.0",
|
"react-i18next": "^10.11.0",
|
||||||
|
"react-modal": "^3.11.1",
|
||||||
"react-with-direction": "1.3.0"
|
"react-with-direction": "1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import './CineDialog.styl';
|
import './CineDialog.styl';
|
||||||
|
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
import { Icon } from './../../elements/Icon';
|
import { Icon } from './../../elements/Icon';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|||||||
@ -58,7 +58,7 @@ const AboutContent = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-cy="about-modal">
|
<div className="AboutContent" data-cy="about-modal">
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
<a
|
<a
|
||||||
className="btn btn-default"
|
className="btn btn-default"
|
||||||
|
|||||||
@ -189,7 +189,7 @@ const ViewportDownloadForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="ViewportDownloadForm">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
{t(
|
{t(
|
||||||
'Please specify the dimensions, filename, and desired type for the output image.'
|
'Please specify the dimensions, filename, and desired type for the output image.'
|
||||||
@ -284,10 +284,6 @@ const ViewportDownloadForm = ({
|
|||||||
className="viewport-preview"
|
className="viewport-preview"
|
||||||
src={viewportPreview.src}
|
src={viewportPreview.src}
|
||||||
alt="Viewport Preview"
|
alt="Viewport Preview"
|
||||||
style={{
|
|
||||||
height: viewportPreview.height,
|
|
||||||
width: viewportPreview.width,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -303,7 +299,7 @@ const ViewportDownloadForm = ({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,13 @@
|
|||||||
@import '../../../design/styles/common/button.styl'
|
@import '../../../design/styles/common/button.styl'
|
||||||
|
|
||||||
.ViewportDownloadForm
|
.ViewportDownloadForm
|
||||||
color: var(--text-secondary-color);
|
display: flex;
|
||||||
filter: drop-shadow(0 0 3px var(--ui-gray-darkest));
|
flex-direction: column;
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: inherit;
|
|
||||||
padding: 15px;
|
|
||||||
background: transparent;
|
|
||||||
z-index: 1080 !important;
|
z-index: 1080 !important;
|
||||||
|
|
||||||
|
input, select
|
||||||
|
max-height: 30px;
|
||||||
|
|
||||||
.title
|
.title
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
@ -67,6 +65,7 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
@media screen and (max-width: 1023px)
|
@media screen and (max-width: 1023px)
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -90,7 +89,6 @@
|
|||||||
|
|
||||||
.actions
|
.actions
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 60px;
|
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import i18n from '@ohif/i18n';
|
import i18n from '@ohif/i18n';
|
||||||
|
|
||||||
import './LanguageSwitcher.styl';
|
import './LanguageSwitcher.styl';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
const LanguageSwitcher = () => {
|
const LanguageSwitcher = () => {
|
||||||
const getCurrentLanguage = (language = i18n.language) =>
|
const getCurrentLanguage = (language = i18n.language) =>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import './MeasurementTable.styl';
|
import './MeasurementTable.styl';
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
import { Icon } from './../../elements/Icon';
|
import { Icon } from './../../elements/Icon';
|
||||||
import { MeasurementTableItem } from './MeasurementTableItem.js';
|
import { MeasurementTableItem } from './MeasurementTableItem.js';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
import { Icon } from './../../elements/Icon';
|
import { Icon } from './../../elements/Icon';
|
||||||
import { OverlayTrigger } from './../overlayTrigger';
|
import { OverlayTrigger } from './../overlayTrigger';
|
||||||
|
|||||||
@ -1,64 +1,69 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ReactBootstrapModal from 'react-bootstrap-modal';
|
import Modal from 'react-modal';
|
||||||
import classNames from 'classnames';
|
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 = ({
|
const OHIFModal = ({
|
||||||
className,
|
className,
|
||||||
closeButton,
|
closeButton,
|
||||||
backdrop,
|
shouldCloseOnEsc,
|
||||||
keyboard,
|
isOpen,
|
||||||
show,
|
|
||||||
title,
|
title,
|
||||||
onHide,
|
onClose,
|
||||||
footer: Footer,
|
|
||||||
header: Header,
|
|
||||||
children,
|
children,
|
||||||
}) => (
|
}) => {
|
||||||
<ReactBootstrapModal
|
const renderHeader = () => {
|
||||||
className={classNames('modal fade themed in', className)}
|
return (
|
||||||
backdrop={backdrop}
|
title && (
|
||||||
keyboard={keyboard}
|
<div className="OHIFModal__header">
|
||||||
show={show}
|
<h4>{title}</h4>
|
||||||
large={true}
|
{closeButton && (
|
||||||
|
<button data-cy="close-button" onClick={onClose}>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className={classNames('OHIFModal', className)}
|
||||||
|
shouldCloseOnEsc={shouldCloseOnEsc}
|
||||||
|
isOpen={isOpen}
|
||||||
title={title}
|
title={title}
|
||||||
onHide={onHide}
|
style={customStyle}
|
||||||
>
|
>
|
||||||
{(Header || title) && (
|
<>
|
||||||
<ReactBootstrapModal.Header closeButton={closeButton}>
|
{renderHeader()}
|
||||||
{title && (
|
<div className="OHIFModal__content">{children}</div>
|
||||||
<ReactBootstrapModal.Title>{title}</ReactBootstrapModal.Title>
|
</>
|
||||||
)}
|
</Modal>
|
||||||
{Header && <Header hide={onHide} />}
|
);
|
||||||
</ReactBootstrapModal.Header>
|
};
|
||||||
)}
|
|
||||||
<ReactBootstrapModal.Body>{children}</ReactBootstrapModal.Body>
|
|
||||||
{Footer && (
|
|
||||||
<ReactBootstrapModal.Footer>
|
|
||||||
<Footer hide={onHide} />
|
|
||||||
</ReactBootstrapModal.Footer>
|
|
||||||
)}
|
|
||||||
</ReactBootstrapModal>
|
|
||||||
);
|
|
||||||
|
|
||||||
OHIFModal.propTypes = {
|
OHIFModal.propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
closeButton: PropTypes.bool,
|
closeButton: PropTypes.bool,
|
||||||
backdrop: PropTypes.bool,
|
shouldCloseOnEsc: PropTypes.bool,
|
||||||
keyboard: PropTypes.bool,
|
isOpen: PropTypes.bool,
|
||||||
show: PropTypes.bool,
|
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
onHide: PropTypes.func,
|
onClose: PropTypes.func,
|
||||||
footer: PropTypes.oneOfType([
|
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
|
||||||
PropTypes.node,
|
|
||||||
PropTypes.func,
|
|
||||||
]),
|
|
||||||
header: PropTypes.oneOfType([
|
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
|
||||||
PropTypes.node,
|
|
||||||
PropTypes.func,
|
|
||||||
]),
|
|
||||||
children: PropTypes.oneOfType([
|
children: PropTypes.oneOfType([
|
||||||
PropTypes.arrayOf(PropTypes.node),
|
PropTypes.arrayOf(PropTypes.node),
|
||||||
PropTypes.node,
|
PropTypes.node,
|
||||||
|
|||||||
67
platform/ui/src/components/ohifModal/OHIFModal.styl
Normal file
67
platform/ui/src/components/ohifModal/OHIFModal.styl
Normal file
@ -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
|
||||||
@ -3,7 +3,7 @@ import React, { cloneElement } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Overlay as BaseOverlay } from 'react-overlays';
|
import { Overlay as BaseOverlay } from 'react-overlays';
|
||||||
import elementType from 'prop-types-extra/lib/elementType';
|
import elementType from 'prop-types-extra/lib/elementType';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
import Fade from './Fade';
|
import Fade from './Fade';
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SnackbarItem from './SnackbarItem';
|
import SnackbarItem from './SnackbarItem';
|
||||||
import './Snackbar.css';
|
import './Snackbar.css';
|
||||||
import { useSnackbarContext } from '../../utils/SnackbarProvider';
|
import { useSnackbarContext } from '../../contextProviders';
|
||||||
|
|
||||||
const SnackbarContainer = () => {
|
const SnackbarContainer = () => {
|
||||||
const { snackbarItems, hide } = useSnackbarContext();
|
const { snackbarItems, hide } = useSnackbarContext();
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import TableSearchFilter from './TableSearchFilter.js';
|
|||||||
import useMedia from '../../hooks/useMedia.js';
|
import useMedia from '../../hooks/useMedia.js';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { StudyListLoadingText } from './StudyListLoadingText.js';
|
import { StudyListLoadingText } from './StudyListLoadingText.js';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon } from './../../elements/Icon';
|
import { Icon } from './../../elements/Icon';
|
||||||
// TODO: useTranslation
|
// TODO: useTranslation
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
function StudyListLoadingText({ t: translate }) {
|
function StudyListLoadingText({ t: translate }) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import './PaginationArea.styl';
|
import './PaginationArea.styl';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
class TablePagination extends PureComponent {
|
class TablePagination extends PureComponent {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
.HotKeysPreferences
|
.HotKeysPreferences
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: -15px;
|
|
||||||
margin-left: -15px;
|
|
||||||
|
|
||||||
.column
|
.column
|
||||||
width: 50%;
|
width: 50%;
|
||||||
|
|||||||
@ -90,8 +90,8 @@ export class UserPreferences extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="UserPreferences">
|
||||||
<div className="dialog-separator-after">
|
<div className="UserPreferences__selector">
|
||||||
<ul className="nav nav-tabs">
|
<ul className="nav nav-tabs">
|
||||||
<li
|
<li
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@ -3,23 +3,27 @@
|
|||||||
@import './../../design/styles/common/state.styl'
|
@import './../../design/styles/common/state.styl'
|
||||||
@import './../../design/styles/common/global.styl'
|
@import './../../design/styles/common/global.styl'
|
||||||
|
|
||||||
.modal-body
|
.UserPreferences
|
||||||
overflow: hidden
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
|
||||||
.errorMessage
|
&__selector
|
||||||
|
border-bottom: 3px solid black
|
||||||
|
|
||||||
|
.errorMessage
|
||||||
color: var(--state-error-text)
|
color: var(--state-error-text)
|
||||||
font-size: 10px
|
font-size: 10px
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
.form-content
|
.form-content
|
||||||
border-bottom: 3px solid var(--primary-background-color)
|
border-bottom: 3px solid var(--primary-background-color)
|
||||||
margin-bottom: 20px
|
margin-bottom: 20px
|
||||||
margin-left: -22px
|
margin-left: -20px
|
||||||
margin-right: -22px
|
margin-right: -20px
|
||||||
max-height: 70vh
|
max-height: 70vh
|
||||||
overflow-y: auto
|
overflow-y: auto
|
||||||
padding: 22px
|
padding: 20px
|
||||||
min-height: 500px
|
min-height: 500px
|
||||||
|
|
||||||
.popover
|
.popover
|
||||||
width: 300px
|
width: 300px
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import './UserPreferencesForm.styl';
|
|||||||
|
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withTranslation } from '../../utils/LanguageProvider';
|
import { withTranslation } from '../../contextProviders';
|
||||||
|
|
||||||
import cloneDeep from 'lodash.clonedeep';
|
import cloneDeep from 'lodash.clonedeep';
|
||||||
import isEqual from 'lodash.isequal';
|
import isEqual from 'lodash.isequal';
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
.footer
|
.footer
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: row
|
flex-direction: row
|
||||||
padding-bottom: 20px
|
|
||||||
justify-content: space-between
|
justify-content: space-between
|
||||||
|
|
||||||
div
|
div
|
||||||
|
|||||||
208
platform/ui/src/contextProviders/DialogProvider.js
Normal file
208
platform/ui/src/contextProviders/DialogProvider.js
Normal file
@ -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 (
|
||||||
|
<DialogContext.Provider value={{ create, dismiss, dismissAll, dialogs }}>
|
||||||
|
<div className="DraggableArea">
|
||||||
|
{dialogs.map(dialog => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
content: Dialog,
|
||||||
|
position /* Position of the dialog. {{x: 0, y: 0}} */,
|
||||||
|
defaultPosition,
|
||||||
|
isDraggable = true,
|
||||||
|
onStart = () => {},
|
||||||
|
onStop = () => {},
|
||||||
|
onDrag = () => {},
|
||||||
|
} = dialog;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Draggable
|
||||||
|
key={id}
|
||||||
|
disabled={!isDraggable}
|
||||||
|
position={position}
|
||||||
|
defaultPosition={lastDialogPosition || defaultPosition}
|
||||||
|
bounds="parent"
|
||||||
|
onStart={event => {
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id={`draggableItem-${id}`}
|
||||||
|
className={classNames(
|
||||||
|
'DraggableItem',
|
||||||
|
isDragging && 'dragging'
|
||||||
|
)}
|
||||||
|
style={{ zIndex: '999', position: 'absolute' }}
|
||||||
|
onClick={() => _reorder(id)}
|
||||||
|
>
|
||||||
|
<Dialog {...dialog} />
|
||||||
|
</div>
|
||||||
|
</Draggable>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</DialogContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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 <Component {...props} dialog={{ create, dismiss, dismissAll }} />;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DialogProvider;
|
||||||
12
platform/ui/src/contextProviders/DialogProvider.styl
Normal file
12
platform/ui/src/contextProviders/DialogProvider.styl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.DraggableItem
|
||||||
|
div
|
||||||
|
cursor: grab !important
|
||||||
|
|
||||||
|
.DraggableItem.dragging
|
||||||
|
div
|
||||||
|
cursor: grabbing !important
|
||||||
|
|
||||||
|
.DraggableArea
|
||||||
|
width: 100vw
|
||||||
|
height: 100vh
|
||||||
|
position: absolute
|
||||||
@ -16,14 +16,11 @@ export const useModal = () => useContext(ModalContext);
|
|||||||
const ModalProvider = ({ children, modal: Modal, service }) => {
|
const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||||
const DEFAULT_OPTIONS = {
|
const DEFAULT_OPTIONS = {
|
||||||
component: null /* The component instance inside the modal. */,
|
component: null /* The component instance inside the modal. */,
|
||||||
header: null /* The content inside the modal header. */,
|
shouldCloseOnEsc: false /* Modal is dismissible via the esc key. */,
|
||||||
footer: null /* The content inside the modal footer. */,
|
isOpen: true /* Make the Modal visible or hidden. */,
|
||||||
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. */,
|
|
||||||
closeButton: true /* Should the modal body render the close button. */,
|
closeButton: true /* Should the modal body render the close button. */,
|
||||||
title: null /* Should the modal render the title independently of the body content. */,
|
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);
|
const [options, setOptions] = useState(DEFAULT_OPTIONS);
|
||||||
@ -69,14 +66,11 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
|||||||
options.customClassName,
|
options.customClassName,
|
||||||
options.component.className
|
options.component.className
|
||||||
)}
|
)}
|
||||||
backdrop={options.backdrop}
|
shouldCloseOnEsc={options.keyboard}
|
||||||
keyboard={options.keyboard}
|
isOpen={options.isOpen}
|
||||||
show={options.show}
|
|
||||||
title={options.title}
|
title={options.title}
|
||||||
closeButton={options.closeButton}
|
closeButton={options.closeButton}
|
||||||
footer={options.footer}
|
onClose={hide}
|
||||||
header={options.header}
|
|
||||||
onHide={hide}
|
|
||||||
>
|
>
|
||||||
<Component {...options} show={show} hide={hide} />
|
<Component {...options} show={show} hide={hide} />
|
||||||
</Modal>
|
</Modal>
|
||||||
20
platform/ui/src/contextProviders/index.js
Normal file
20
platform/ui/src/contextProviders/index.js
Normal file
@ -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';
|
||||||
@ -49,15 +49,18 @@ import { ScrollableArea } from './ScrollableArea/ScrollableArea.js';
|
|||||||
import Toolbar from './viewer/Toolbar.js';
|
import Toolbar from './viewer/Toolbar.js';
|
||||||
import ToolbarButton from './viewer/ToolbarButton.js';
|
import ToolbarButton from './viewer/ToolbarButton.js';
|
||||||
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
|
import ViewerbaseDragDropContext from './utils/viewerbaseDragDropContext.js';
|
||||||
import SnackbarProvider, {
|
import {
|
||||||
|
SnackbarProvider,
|
||||||
useSnackbarContext,
|
useSnackbarContext,
|
||||||
withSnackbar,
|
withSnackbar,
|
||||||
} from './utils/SnackbarProvider';
|
DialogProvider,
|
||||||
import ModalProvider, {
|
useDialog,
|
||||||
|
withDialog,
|
||||||
|
ModalProvider,
|
||||||
|
ModalConsumer,
|
||||||
useModal,
|
useModal,
|
||||||
withModal,
|
withModal,
|
||||||
ModalConsumer,
|
} from './contextProviders';
|
||||||
} from './utils/ModalProvider';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
// Elements
|
// Elements
|
||||||
@ -111,6 +114,9 @@ export {
|
|||||||
ModalConsumer,
|
ModalConsumer,
|
||||||
withModal,
|
withModal,
|
||||||
OHIFModal,
|
OHIFModal,
|
||||||
|
DialogProvider,
|
||||||
|
withDialog,
|
||||||
|
useDialog,
|
||||||
// Hooks
|
// Hooks
|
||||||
useDebounce,
|
useDebounce,
|
||||||
useMedia,
|
useMedia,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { Icon } from './../elements/Icon';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { withTranslation } from '../utils/LanguageProvider';
|
import { withTranslation } from '../contextProviders';
|
||||||
|
|
||||||
export function ToolbarButton(props) {
|
export function ToolbarButton(props) {
|
||||||
const { isActive, icon, labelWhenActive, onClick, t } = props;
|
const { isActive, icon, labelWhenActive, onClick, t } = props;
|
||||||
|
|||||||
@ -273,7 +273,7 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
cy.get('[data-cy="about-item-menu"]')
|
cy.get('[data-cy="about-item-menu"]')
|
||||||
.first()
|
.first()
|
||||||
.click();
|
.click();
|
||||||
cy.get('.modal-content')
|
cy.get('[data-cy="about-modal"]')
|
||||||
.as('aboutOverlay')
|
.as('aboutOverlay')
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
|
|
||||||
@ -296,7 +296,7 @@ describe('OHIF Study Viewer Page', function() {
|
|||||||
cy.percyCanvasSnapshot('About modal - Should display modal');
|
cy.percyCanvasSnapshot('About modal - Should display modal');
|
||||||
|
|
||||||
//close modal
|
//close modal
|
||||||
cy.get('.close').click();
|
cy.get('[data-cy="close-button"]').click();
|
||||||
cy.get('@aboutOverlay').should('not.be.enabled');
|
cy.get('@aboutOverlay').should('not.be.enabled');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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';
|
import { hot } from 'react-hot-loader/root';
|
||||||
|
|
||||||
// TODO: This should not be here
|
import {
|
||||||
import './config';
|
SnackbarProvider,
|
||||||
|
ModalProvider,
|
||||||
|
DialogProvider,
|
||||||
|
OHIFModal,
|
||||||
|
} from '@ohif/ui';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CommandsManager,
|
CommandsManager,
|
||||||
@ -10,44 +21,48 @@ import {
|
|||||||
HotkeysManager,
|
HotkeysManager,
|
||||||
createUINotificationService,
|
createUINotificationService,
|
||||||
createUIModalService,
|
createUIModalService,
|
||||||
|
createUIDialogService,
|
||||||
utils,
|
utils,
|
||||||
} from '@ohif/core';
|
} from '@ohif/core';
|
||||||
import React, { Component } from 'react';
|
|
||||||
|
import i18n from '@ohif/i18n';
|
||||||
|
|
||||||
|
// TODO: This should not be here
|
||||||
|
import './config';
|
||||||
|
|
||||||
|
/** Utils */
|
||||||
import {
|
import {
|
||||||
getUserManagerForOpenIdConnectClient,
|
getUserManagerForOpenIdConnectClient,
|
||||||
initWebWorkers,
|
initWebWorkers,
|
||||||
} from './utils/index.js';
|
} from './utils/index.js';
|
||||||
|
|
||||||
import { I18nextProvider } from 'react-i18next';
|
/** Extensions */
|
||||||
|
|
||||||
// ~~ EXTENSIONS
|
|
||||||
import { GenericViewerCommands, MeasurementsPanel } from './appExtensions';
|
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 WhiteLabellingContext from './context/WhiteLabellingContext';
|
||||||
import UserManagerContext from './context/UserManagerContext';
|
import UserManagerContext from './context/UserManagerContext';
|
||||||
import AppContext from './context/AppContext';
|
import AppContext from './context/AppContext';
|
||||||
|
|
||||||
// ~~~~ APP SETUP
|
/** ~~~~~~~~~~~~~ Application Setup */
|
||||||
const commandsManagerConfig = {
|
const commandsManagerConfig = {
|
||||||
getAppState: () => store.getState(),
|
getAppState: () => store.getState(),
|
||||||
getActiveContexts: () => getActiveContexts(store.getState()),
|
getActiveContexts: () => getActiveContexts(store.getState()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Services
|
/** Services */
|
||||||
const UINotificationService = createUINotificationService();
|
const UINotificationService = createUINotificationService();
|
||||||
const UIModalService = createUIModalService();
|
const UIModalService = createUIModalService();
|
||||||
|
const UIDialogService = createUIDialogService();
|
||||||
|
|
||||||
|
/** Managers */
|
||||||
const commandsManager = new CommandsManager(commandsManagerConfig);
|
const commandsManager = new CommandsManager(commandsManagerConfig);
|
||||||
const hotkeysManager = new HotkeysManager(commandsManager);
|
const hotkeysManager = new HotkeysManager(commandsManager);
|
||||||
const servicesManager = new ServicesManager();
|
const servicesManager = new ServicesManager();
|
||||||
@ -55,7 +70,7 @@ const extensionManager = new ExtensionManager({
|
|||||||
commandsManager,
|
commandsManager,
|
||||||
servicesManager,
|
servicesManager,
|
||||||
});
|
});
|
||||||
// ~~~~ END APP SETUP
|
/** ~~~~~~~~~~~~~ End Application Setup */
|
||||||
|
|
||||||
// TODO[react] Use a provider when the whole tree is React
|
// TODO[react] Use a provider when the whole tree is React
|
||||||
window.store = store;
|
window.store = store;
|
||||||
@ -72,6 +87,7 @@ class App extends Component {
|
|||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
hotkeys: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -91,7 +107,7 @@ class App extends Component {
|
|||||||
const { servers, extensions, hotkeys, oidc } = props;
|
const { servers, extensions, hotkeys, oidc } = props;
|
||||||
|
|
||||||
this.initUserManager(oidc);
|
this.initUserManager(oidc);
|
||||||
_initServices([UINotificationService, UIModalService]);
|
_initServices([UINotificationService, UIModalService, UIDialogService]);
|
||||||
_initExtensions(extensions, hotkeys);
|
_initExtensions(extensions, hotkeys);
|
||||||
_initServers(servers);
|
_initServers(servers);
|
||||||
initWebWorkers();
|
initWebWorkers();
|
||||||
@ -114,12 +130,14 @@ class App extends Component {
|
|||||||
<Router basename={routerBasename}>
|
<Router basename={routerBasename}>
|
||||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||||
<SnackbarProvider service={UINotificationService}>
|
<SnackbarProvider service={UINotificationService}>
|
||||||
|
<DialogProvider service={UIDialogService}>
|
||||||
<ModalProvider
|
<ModalProvider
|
||||||
modal={OHIFModal}
|
modal={OHIFModal}
|
||||||
service={UIModalService}
|
service={UIModalService}
|
||||||
>
|
>
|
||||||
<OHIFStandaloneViewer userManager={userManager} />
|
<OHIFStandaloneViewer userManager={userManager} />
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
|
</DialogProvider>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</WhiteLabellingContext.Provider>
|
</WhiteLabellingContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
@ -138,9 +156,11 @@ class App extends Component {
|
|||||||
<Router basename={routerBasename}>
|
<Router basename={routerBasename}>
|
||||||
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
<WhiteLabellingContext.Provider value={whiteLabelling}>
|
||||||
<SnackbarProvider service={UINotificationService}>
|
<SnackbarProvider service={UINotificationService}>
|
||||||
|
<DialogProvider service={UIDialogService}>
|
||||||
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
<ModalProvider modal={OHIFModal} service={UIModalService}>
|
||||||
<OHIFStandaloneViewer />
|
<OHIFStandaloneViewer />
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
|
</DialogProvider>
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</WhiteLabellingContext.Provider>
|
</WhiteLabellingContext.Provider>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@ -49,7 +49,6 @@ class Header extends Component {
|
|||||||
onClick: () =>
|
onClick: () =>
|
||||||
show(AboutContent, {
|
show(AboutContent, {
|
||||||
title: t('OHIF Viewer - About'),
|
title: t('OHIF Viewer - About'),
|
||||||
customClassName: 'AboutContent',
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
RoundedButtonGroup,
|
RoundedButtonGroup,
|
||||||
ToolbarButton,
|
ToolbarButton,
|
||||||
withModal,
|
withModal,
|
||||||
|
withDialog,
|
||||||
} from '@ohif/ui';
|
} from '@ohif/ui';
|
||||||
|
|
||||||
import './ToolbarRow.css';
|
import './ToolbarRow.css';
|
||||||
@ -45,7 +46,6 @@ class ToolbarRow extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
toolbarButtons: toolbarButtonDefinitions,
|
toolbarButtons: toolbarButtonDefinitions,
|
||||||
activeButtons: [],
|
activeButtons: [],
|
||||||
isCineDialogOpen: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this._handleBuiltIn = _handleBuiltIn.bind(this);
|
this._handleBuiltIn = _handleBuiltIn.bind(this);
|
||||||
@ -106,13 +106,6 @@ class ToolbarRow extends Component {
|
|||||||
this.state.activeButtons
|
this.state.activeButtons
|
||||||
);
|
);
|
||||||
|
|
||||||
const cineDialogContainerStyle = {
|
|
||||||
display: this.state.isCineDialogOpen ? 'block' : 'none',
|
|
||||||
position: 'absolute',
|
|
||||||
top: '82px',
|
|
||||||
zIndex: 999,
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPress = (side, value) => {
|
const onPress = (side, value) => {
|
||||||
this.props.handleSidePanelChange(side, value);
|
this.props.handleSidePanelChange(side, value);
|
||||||
};
|
};
|
||||||
@ -145,9 +138,6 @@ class ToolbarRow extends Component {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</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.
|
// Check if its a valid customComponent. Later on an CustomToolbarComponent interface could be implemented.
|
||||||
if (isValidComponent) {
|
if (isValidComponent) {
|
||||||
const parentContext = this;
|
const parentContext = this;
|
||||||
const isActive = activeButtons.includes(button.id);
|
const activeButtonsIds = activeButtons.map(button => button.id);
|
||||||
|
const isActive = activeButtonsIds.includes(button.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomComponent
|
<CustomComponent
|
||||||
@ -168,7 +159,7 @@ function _getCustomButtonComponent(button, activeButtons) {
|
|||||||
toolbarClickCallback={_handleToolbarButtonClick.bind(this)}
|
toolbarClickCallback={_handleToolbarButtonClick.bind(this)}
|
||||||
button={button}
|
button={button}
|
||||||
key={button.id}
|
key={button.id}
|
||||||
activeButtons={activeButtons}
|
activeButtons={activeButtonsIds}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -181,7 +172,7 @@ function _getExpandableButtonComponent(button, activeButtons) {
|
|||||||
const childButtons = button.buttons.map(childButton => {
|
const childButtons = button.buttons.map(childButton => {
|
||||||
childButton.onClick = _handleToolbarButtonClick.bind(this, 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;
|
activeCommand = childButton.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +197,7 @@ function _getDefaultButtonComponent(button, activeButtons) {
|
|||||||
label={button.label}
|
label={button.label}
|
||||||
icon={button.icon}
|
icon={button.icon}
|
||||||
onClick={_handleToolbarButtonClick.bind(this, button)}
|
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
|
* @param {*} props
|
||||||
*/
|
*/
|
||||||
function _handleToolbarButtonClick(button, evt, props) {
|
function _handleToolbarButtonClick(button, evt, props) {
|
||||||
|
const { activeButtons } = this.state;
|
||||||
|
|
||||||
if (button.commandName) {
|
if (button.commandName) {
|
||||||
const options = Object.assign({ evt }, button.commandOptions);
|
const options = Object.assign({ evt }, button.commandOptions);
|
||||||
commandsManager.runCommand(button.commandName, options);
|
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
|
// TODO: We can update this to be a `getter` on the extension to query
|
||||||
// For the active tools after we apply our updates?
|
// For the active tools after we apply our updates?
|
||||||
if (button.type === 'setToolActive') {
|
if (button.type === 'setToolActive') {
|
||||||
this.setState({
|
const toggables = activeButtons.filter(
|
||||||
activeButtons: [button.id],
|
({ options }) => options && !options.togglable
|
||||||
});
|
);
|
||||||
|
this.setState({ activeButtons: [...toggables, button] });
|
||||||
} else if (button.type === 'builtIn') {
|
} else if (button.type === 'builtIn') {
|
||||||
this._handleBuiltIn(button.options);
|
this._handleBuiltIn(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,21 +273,47 @@ function _getVisibleToolbarButtons() {
|
|||||||
return toolbarButtonDefinitions;
|
return toolbarButtonDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _handleBuiltIn({ behavior } = {}) {
|
function _handleBuiltIn(button) {
|
||||||
if (behavior === 'CINE') {
|
/* TODO: Keep cine button active until its unselected. */
|
||||||
this.setState({
|
const { dialog, modal, t } = this.props;
|
||||||
isCineDialogOpen: !this.state.isCineDialogOpen,
|
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') {
|
if (options.behavior === 'DOWNLOAD_SCREEN_SHOT') {
|
||||||
this.props.modal.show(ConnectedViewportDownloadForm, {
|
modal.show(ConnectedViewportDownloadForm, {
|
||||||
title: this.props.t('Download High Quality Image'),
|
title: t('Download High Quality Image'),
|
||||||
customClassName: 'ViewportDownloadForm',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTranslation(['Common', 'ViewportDownloadForm'])(
|
export default withTranslation(['Common', 'ViewportDownloadForm'])(
|
||||||
withModal(ToolbarRow)
|
withModal(withDialog(ToolbarRow))
|
||||||
);
|
);
|
||||||
|
|||||||
60
yarn.lock
60
yarn.lock
@ -1100,13 +1100,34 @@
|
|||||||
pirates "^4.0.0"
|
pirates "^4.0.0"
|
||||||
source-map-support "^0.5.9"
|
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"
|
version "7.5.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
|
||||||
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
|
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
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":
|
"@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"
|
version "7.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6"
|
||||||
@ -8074,6 +8095,11 @@ executable@4.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
pify "^2.2.0"
|
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:
|
exif-parser@^0.1.12, exif-parser@^0.1.9:
|
||||||
version "0.1.12"
|
version "0.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/exif-parser/-/exif-parser-0.1.12.tgz#58a9d2d72c02c1f6f02a0ef4a9166272b7760922"
|
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"
|
prop-types "^15.6.2"
|
||||||
scheduler "^0.17.0"
|
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:
|
react-dropzone@^10.1.7:
|
||||||
version "10.1.10"
|
version "10.1.10"
|
||||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-10.1.10.tgz#f340290dfc26ac09ad68abc020ab6232c23d6cf3"
|
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"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa"
|
||||||
integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw==
|
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"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
@ -16101,6 +16135,16 @@ react-live@2.0.1:
|
|||||||
react-simple-code-editor "^0.9.0"
|
react-simple-code-editor "^0.9.0"
|
||||||
unescape "^0.2.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:
|
react-moment-proptypes@^1.6.0:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-moment-proptypes/-/react-moment-proptypes-1.7.0.tgz#89881479840a76c13574a86e3bb214c4ba564e7a"
|
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"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||||
integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==
|
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:
|
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2:
|
||||||
version "0.13.3"
|
version "0.13.3"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5"
|
||||||
@ -20041,6 +20090,13 @@ warning@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.0.0"
|
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:
|
watchpack@^1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
|
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user