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]); /** * 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 {ElementPosition} defaultPosition Specifies the `x` and `y` that the dragged item should start at. * @property {ElementPosition} position If this property is present, the item becomes 'controlled' and is not responsive to user input. * @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. */ /** * 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 }]); 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 = id => { setDialogs(dialogs => { const topDialog = dialogs.find(dialog => dialog.id === id); return topDialog ? [...dialogs.filter(dialog => dialog.id !== id), topDialog] : []; }); }; const renderDialogs = () => dialogs.map(dialog => { const { id, content: DialogContent, contentProps, position, 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', '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;