feat: Notification provider and service (#1703)

* Create basic structure for Viewport dialog provier and Dialog component

* Implementation of a UIViewportDialogService

* Update viewportDialogProvider docz

* Create example of use of UIViewportDialogService
This commit is contained in:
Gustavo André Lelis 2020-05-04 17:16:03 -03:00 committed by James A. Petts
parent cee09fcf8d
commit e633eb80eb
11 changed files with 395 additions and 23 deletions

View File

@ -22,10 +22,11 @@ import errorHandler from './errorHandler.js';
import utils, { hotkeys } from './utils/';
import {
UINotificationService,
UIModalService,
UIDialogService,
MeasurementService,
UIDialogService,
UIModalService,
UINotificationService,
UIViewportDialogService,
} from './services';
const OHIF = {
@ -56,9 +57,10 @@ const OHIF = {
measurements,
hangingProtocols,
//
UINotificationService,
UIModalService,
UIDialogService,
UIModalService,
UINotificationService,
UIViewportDialogService,
MeasurementService,
};
@ -89,9 +91,10 @@ export {
measurements,
hangingProtocols,
//
UINotificationService,
UIModalService,
UIDialogService,
UIModalService,
UINotificationService,
UIViewportDialogService,
MeasurementService,
};

View File

@ -0,0 +1,99 @@
/**
* Viewport Dialog
*
* @typedef {Object} ViewportDialogProps
* @property {ReactElement|HTMLElement} [content=null] Modal content.
* @property {Object} [contentProps=null] Modal content props.
* @property {boolean} [viewportIndex=false] Modal is dismissible via the esc key.
*/
const name = 'UIViewportDialogService';
const publicAPI = {
name,
hide: _hide,
show: _show,
setServiceImplementation,
};
const serviceImplementation = {
_viewports: [],
};
/**
* Show a new UI viewport dialog on the specified viewportIndex;
*
* @param {ViewportDialogProps} props { content, contentProps, viewportIndex }
*/
function _show({ content = null, contentProps = null, viewportIndex }) {
const viewportIndexImplementation =
(viewportIndex !== undefined &&
serviceImplementation._viewports[viewportIndex]) ||
{};
if (!viewportIndexImplementation._show) {
console.warn('show() NOT IMPLEMENTED');
return;
}
return viewportIndexImplementation._show({
content,
contentProps,
viewportIndex,
});
}
/**
* Hides/dismisses the viewport dialog, if currently shown
*
* @param {*} { viewportIndex }
*/
function _hide({ viewportIndex }) {
const viewportIndexImplementation =
(viewportIndex && serviceImplementation._viewports[viewportIndex]) || {};
if (!viewportIndexImplementation._hide) {
console.warn('hide() NOT IMPLEMENTED');
return;
}
return viewportIndexImplementation._hide();
}
/**
*
*
* @param {*} {
* hide: hideImplementation,
* show: showImplementation,
* viewportIndex,
* }
*/
function setServiceImplementation({
hide: hideImplementation,
show: showImplementation,
viewportIndex,
}) {
if (viewportIndex !== undefined) {
const newImplementations = {};
if (hideImplementation) {
newImplementations._hide = hideImplementation;
}
if (showImplementation) {
newImplementations._show = showImplementation;
}
serviceImplementation._viewports[viewportIndex] = Object.assign(
{},
serviceImplementation._viewports[viewportIndex],
newImplementations
);
}
}
export default {
name,
create: ({ configuration = {} }) => {
return publicAPI;
},
};

View File

@ -1,13 +1,15 @@
import ServicesManager from './ServicesManager.js';
import UINotificationService from './UINotificationService';
import UIModalService from './UIModalService';
import UIDialogService from './UIDialogService';
import MeasurementService from './MeasurementService';
import ServicesManager from './ServicesManager.js';
import UIDialogService from './UIDialogService';
import UIModalService from './UIModalService';
import UINotificationService from './UINotificationService';
import UIViewportDialogService from './UIViewportDialogService';
export {
UINotificationService,
UIModalService,
UIDialogService,
ServicesManager,
MeasurementService,
ServicesManager,
UIDialogService,
UIModalService,
UINotificationService,
UIViewportDialogService,
};

View File

@ -8,6 +8,8 @@ export {
ModalConsumer,
useModal,
withModal,
ViewportDialogProvider,
useViewportDialog,
} from './src/contextProviders';
/** COMPONENTS */
@ -15,6 +17,7 @@ export {
Button,
ButtonGroup,
DateRange,
Dialog,
EmptyStudies,
Icon,
IconButton,

View File

@ -0,0 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
const Dialog = ({ children }) => {
return <div className="absolute top-0 left-0">{children}</div>;
};
Dialog.propTypes = {
children: PropTypes.node,
};
export default Dialog;

View File

@ -0,0 +1,2 @@
import Dialog from './Dialog';
export default Dialog;

View File

@ -1,6 +1,7 @@
import Button from './Button';
import ButtonGroup from './ButtonGroup';
import DateRange from './DateRange';
import Dialog from './Dialog';
import EmptyStudies from './EmptyStudies';
import Icon from './Icon';
import IconButton from './IconButton';
@ -47,6 +48,7 @@ export {
Button,
ButtonGroup,
DateRange,
Dialog,
EmptyStudies,
Icon,
IconButton,

View File

@ -0,0 +1,79 @@
import React, {
useState,
createContext,
useContext,
useCallback,
useEffect,
} from 'react';
import PropTypes from 'prop-types';
const DEFAULT_OPTIONS = {
content: null,
contentProps: null,
customClassName: null,
};
const ViewportDialogContext = createContext(null);
const { Provider } = ViewportDialogContext;
export const useViewportDialog = () => useContext(ViewportDialogContext);
const ViewportDialogProvider = ({
children,
dialog: Dialog,
service,
viewportIndex,
}) => {
const [options, setOptions] = useState(DEFAULT_OPTIONS);
const show = useCallback((props) => setOptions({ ...options, ...props }), [
options,
]);
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), []);
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show, viewportIndex });
}
}, [hide, service, show, viewportIndex]);
const {
content: ViewportDialogContent,
contentProps,
customClassName,
} = options;
return (
<Provider value={{ show, hide }}>
<div className="relative w-full h-full">
{ViewportDialogContent && (
<Dialog className={customClassName}>
<ViewportDialogContent {...contentProps} show={show} hide={hide} />
</Dialog>
)}
{children}
</div>
</Provider>
);
};
ViewportDialogProvider.propTypes = {
/** Children that will be wrapped with Modal Context */
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
/** dialog component */
dialog: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
viewportIndex: PropTypes.number,
};
export default ViewportDialogProvider;

View File

@ -0,0 +1,165 @@
---
name: ViewportDialogProvider
route: customHooks/viewportDialogProvider
---
import { Playground, Props } from 'docz';
<!-- This is a workaround to import things from ohif/core as docz does not allow us to access window element and @ohif/core does use it once we import to instanciate cornerstone -->
import { UIViewportDialogService, ServicesManager } from './../../../core/src/services';
import {
ViewportDialogProvider,
Dialog,
Button,
useViewportDialog,
Notification,
} from '@ohif/ui';
# Viewport Dialog Provider
This is a context provider that allow the application to share the modal
component across all application.
## Sample
<Playground>
{() => {
const ViewportNotification = ({ hide }) => {
return (
<Notification
text="Track all measurement for this series?"
type="info"
actionButtons={
<div>
<Button onClick={hide}>No</Button>
<Button onClick={hide} className="ml-2">
No, do not ask again
</Button>
<Button onClick={hide} className="ml-2" color="primary">
Yes
</Button>
</div>
}
/>
);
};
const ViewportActionButtons = () => {
const dialog = useViewportDialog();
return (
<Button
onClick={() =>
dialog.show({
content: ViewportNotification,
})
}
>
Open Dialog
</Button>
);
};
return (
<div className="w-full flex flex-row p-4" style={{height: '400px'}}>
<div className="flex flex-1 items-center justify-center h-full w-full bg-black text-white border border-primary-main">
<ViewportDialogProvider dialog={Dialog}>
<div className="flex flex-1 flex-col items-center justify-center h-full">
<ViewportActionButtons />
<span>CONTENT 1</span>
</div>
</ViewportDialogProvider>
</div>
<div className="flex flex-1 items-center justify-center h-full w-full bg-black text-white border border-primary-main">
<ViewportDialogProvider dialog={Dialog}>
<div className="flex flex-1 flex-col items-center justify-center h-full">
<ViewportActionButtons />
<span>CONTENT 1</span>
</div>
</ViewportDialogProvider>
</div>
</div>
);
}}
</Playground>
## Example using UIViewportDialogService
<Playground>
{() => {
const ViewportNotification = ({ hide }) => {
return (
<Notification
text="Track all measurement for this series?"
type="info"
actionButtons={
<div>
<Button onClick={hide}>No</Button>
<Button className="ml-2">No, do not ask again</Button>
<Button className="ml-2" color="primary">
Yes
</Button>
</div>
}
/>
);
};
// Creating servicesManager and register services should be in the root of your app
const servicesManager = new ServicesManager();
servicesManager.registerServices([UIViewportDialogService]);
// Get service instance
const _UIViewportDialogService =
servicesManager.services.UIViewportDialogService;
return (
<div className="w-full flex flex-col p-4" style={{height: '400px'}}>
<div className="flex flex-2">
<Button
className="mr-4 mb-4"
onClick={() =>
_UIViewportDialogService.show({
content: ViewportNotification,
viewportIndex: 0,
})
}
>
Open dialog on 0
</Button>
<Button
className="mr-4 mb-4"
onClick={() =>
_UIViewportDialogService.show({
content: ViewportNotification,
viewportIndex: 1,
})
}
>
Open dialog on 1
</Button>
</div>
<div className="flex flex-1 w-full flex-row">
<ViewportDialogProvider
dialog={Dialog}
service={_UIViewportDialogService}
viewportIndex={0}
>
<div className="flex flex-1 items-center justify-center h-full bg-black text-white border border-primary-main">
CONTENT 0
</div>
</ViewportDialogProvider>
<ViewportDialogProvider
dialog={Dialog}
service={_UIViewportDialogService}
viewportIndex={1}
>
<div className="flex flex-1 items-center justify-center h-full bg-black text-white border border-primary-main">
CONTENT 1
</div>
</ViewportDialogProvider>
</div>
</div>
);
}}
</Playground>
## Properties:
<Props of={ViewportDialogProvider} />

View File

@ -3,4 +3,9 @@ export {
useModal,
withModal,
ModalConsumer,
} from './ModalProvider.js';
} from './ModalProvider';
export {
default as ViewportDialogProvider,
useViewportDialog,
} from './ViewportDialogProvider';

View File

@ -15,7 +15,7 @@ export const Sidebar = React.forwardRef((props, ref) => {
const menus = useMenus({ query });
const currentDoc = useCurrentDoc();
const currentDocRef = useRef();
const handleChange = ev => {
const handleChange = (ev) => {
setQuery(ev.target.value);
};
useEffect(() => {
@ -40,26 +40,26 @@ export const Sidebar = React.forwardRef((props, ref) => {
'Data Display',
'Other',
],
'Custom Hooks': ['ModalProvider'],
'Custom Hooks': ['ModalProvider', 'ViewportDialogProvider'],
Examples: ['Views'],
System: ['Colors'],
};
const renderMenuCategories = () => {
return Object.keys(customMenus).map(menuName => {
return Object.keys(customMenus).map((menuName) => {
return (
<div key={menuName}>
<h2 className="pl-2 border-l-8 -ml-4 border-secondary-active">
{menuName}
</h2>
{customMenus[menuName].map(item => item)}
{customMenus[menuName].map((item) => item)}
</div>
);
});
};
const getMenuCategory = menuName => {
return Object.keys(MENU_CATEGORIES).find(category => {
const getMenuCategory = (menuName) => {
return Object.keys(MENU_CATEGORIES).find((category) => {
if (MENU_CATEGORIES[category].includes(menuName)) {
return category;
}
@ -99,7 +99,7 @@ export const Sidebar = React.forwardRef((props, ref) => {
onChange={handleChange}
/>
{menus &&
menus.map(menu => {
menus.map((menu) => {
const isGroup = !!menu.menu;
const Component = isGroup ? NavGroup : NavLink;
const menuCategory = getMenuCategory(menu.name) || null;