* Fix ExtensionManager bug and add test to bandaid * Add tools configuration to extension manager preinit * Fix reducing of configs * Merge internal with external configs * Merge internal with external configs * Remove dialog from init in measurementstable * Testing injected configuration * New way to set config * Add new prop to dialog provider to allow disabling last position * Remove code from preinit in cornerstone * Add new prop to dialog provider to allow disabling last position * Add centralize to dialogs * Reorder dialogs when adding them * Fix draggable styles (cursor) * Remove repositioning methods from labelling flow and remove overlay from labelling manager * Fix empty array being set in bringToFront * Add new command to update table and pass commands manager to modules/preinit hook * Ad UIContextMenu service / factory * Use new contextmenu service in measurementspanel extension * Use dialogs for arrow annotate in default * Remove positioning funcionality from tool context menu * Add context menu service * Pass commandsModule to extension * Update edit description dialog and simple dialog to position relative * Remove style code from labelling flow and manager * Remove eventdata from labelling * Remove labelling code from measurement init * Add commandsmanager to provider * Update contextmenu provider and service * Add touchstart and mouseclick to hide contextmenu * Hide labelling if click/touch * Remove labelling and context menu dead code * Fix undefined bug if ViewerMain grid has no children * Fix broken prop on context menu * Update commandsmodule based on master * Fix broken configuration * Update script tag config * Remove cornerstone from toolcontextmenu * Remove cornerstone from toolcontextmenu * Split labelling and context menu providers * Split labelling and context menu providers * Update test * Destructure extensions into new array * CR Update: Move default arrow config to cornerstone instead of default * CR Update: Fix app configuration props structure * CR Update: Fix app configuration prop in script tag and extract commands manager from providers * CR Update: Create custom providers to use commandsManager * CR Update: Use services directly in measurementspanel * CR Update: Pass components to providers * CR Update: Remove position from dialog * CR Update: fix dialog prop check * CR Update: Fix comments * CR Update: Update documentation * CR Update: Add test default configuration * CR Update: Add default empy array to extensions * CR Update: Update i18n configuration all ot match current function configuration * CR Update: Add defaults to injected dependencies in configuration and extension configuration * CR Update: Add defaults to configuration with no args * Update documentation * CR Update: Add default for tools * CR Update: Update config object to i18n * CR Update: spread defaults * CR Update: Add tool configuration example to cornerstone extension * CR Update: Add tool configuration to netlify (testing) * CR Update: Remove netlify config for tools * CR Update: Rollback changes to i18n to be fixed later * CR Update: Update documentation and pass whole cornerstone config object instead of tools key SEE: https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with-both-and-breaking-change-footer BREAKING CHANGE: modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
283 lines
7.4 KiB
JavaScript
283 lines
7.4 KiB
JavaScript
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 [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 (
|
|
<Draggable
|
|
key={id}
|
|
disabled={!isDraggable}
|
|
position={position}
|
|
defaultPosition={position}
|
|
bounds="parent"
|
|
onStart={event => {
|
|
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);
|
|
}
|
|
}}
|
|
>
|
|
<div
|
|
id={`draggableItem-${id}`}
|
|
className={classNames(
|
|
'DraggableItem',
|
|
isDragging && 'dragging',
|
|
isDraggable && 'draggable'
|
|
)}
|
|
style={{ zIndex: '999', position: 'absolute' }}
|
|
onClick={() => _bringToFront(id)}
|
|
>
|
|
<DialogContent {...dialog} {...contentProps} />
|
|
</div>
|
|
</Draggable>
|
|
);
|
|
});
|
|
|
|
/**
|
|
* 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 (
|
|
<DialogContext.Provider value={{ create, dismiss, dismissAll, isEmpty }}>
|
|
<div className="DraggableArea">
|
|
{dialogs.some(dialog => dialog.showOverlay) ? (
|
|
<div className="Overlay active">{renderDialogs()}</div>
|
|
) : (
|
|
renderDialogs()
|
|
)}
|
|
</div>
|
|
{children}
|
|
</DialogContext.Provider>
|
|
);
|
|
};
|
|
|
|
/**
|
|
*
|
|
* 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 (
|
|
<Component {...props} dialog={{ create, dismiss, dismissAll, isEmpty }} />
|
|
);
|
|
};
|
|
};
|
|
|
|
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;
|