From e633eb80eb1be3915fac31860f3ea29d5370c1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Andr=C3=A9=20Lelis?= Date: Mon, 4 May 2020 17:16:03 -0300 Subject: [PATCH] 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 --- platform/core/src/index.js | 17 +- .../services/UIViewportDialogService/index.js | 99 +++++++++++ platform/core/src/services/index.js | 18 +- platform/ui/index.js | 3 + platform/ui/src/components/Dialog/Dialog.jsx | 12 ++ platform/ui/src/components/Dialog/index.js | 2 + platform/ui/src/components/index.js | 2 + .../ViewportDialogProvider.jsx | 79 +++++++++ .../ViewportDialogProvider.mdx | 165 ++++++++++++++++++ platform/ui/src/contextProviders/index.js | 7 +- .../components/Sidebar/index.js | 14 +- 11 files changed, 395 insertions(+), 23 deletions(-) create mode 100644 platform/core/src/services/UIViewportDialogService/index.js create mode 100644 platform/ui/src/components/Dialog/Dialog.jsx create mode 100644 platform/ui/src/components/Dialog/index.js create mode 100644 platform/ui/src/contextProviders/ViewportDialogProvider.jsx create mode 100644 platform/ui/src/contextProviders/ViewportDialogProvider.mdx diff --git a/platform/core/src/index.js b/platform/core/src/index.js index 3f7727d2d..f12d7e2ec 100644 --- a/platform/core/src/index.js +++ b/platform/core/src/index.js @@ -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, }; diff --git a/platform/core/src/services/UIViewportDialogService/index.js b/platform/core/src/services/UIViewportDialogService/index.js new file mode 100644 index 000000000..8b0aeddeb --- /dev/null +++ b/platform/core/src/services/UIViewportDialogService/index.js @@ -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; + }, +}; diff --git a/platform/core/src/services/index.js b/platform/core/src/services/index.js index a7fa17072..096283956 100644 --- a/platform/core/src/services/index.js +++ b/platform/core/src/services/index.js @@ -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, }; diff --git a/platform/ui/index.js b/platform/ui/index.js index 0097336b1..0ab595a6d 100644 --- a/platform/ui/index.js +++ b/platform/ui/index.js @@ -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, diff --git a/platform/ui/src/components/Dialog/Dialog.jsx b/platform/ui/src/components/Dialog/Dialog.jsx new file mode 100644 index 000000000..aa0295709 --- /dev/null +++ b/platform/ui/src/components/Dialog/Dialog.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +const Dialog = ({ children }) => { + return
{children}
; +}; + +Dialog.propTypes = { + children: PropTypes.node, +}; + +export default Dialog; diff --git a/platform/ui/src/components/Dialog/index.js b/platform/ui/src/components/Dialog/index.js new file mode 100644 index 000000000..42ce789bf --- /dev/null +++ b/platform/ui/src/components/Dialog/index.js @@ -0,0 +1,2 @@ +import Dialog from './Dialog'; +export default Dialog; diff --git a/platform/ui/src/components/index.js b/platform/ui/src/components/index.js index 72e217170..b5825a9b6 100644 --- a/platform/ui/src/components/index.js +++ b/platform/ui/src/components/index.js @@ -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, diff --git a/platform/ui/src/contextProviders/ViewportDialogProvider.jsx b/platform/ui/src/contextProviders/ViewportDialogProvider.jsx new file mode 100644 index 000000000..e412188ee --- /dev/null +++ b/platform/ui/src/contextProviders/ViewportDialogProvider.jsx @@ -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 ( + +
+ {ViewportDialogContent && ( + + + + )} + {children} +
+
+ ); +}; + +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; diff --git a/platform/ui/src/contextProviders/ViewportDialogProvider.mdx b/platform/ui/src/contextProviders/ViewportDialogProvider.mdx new file mode 100644 index 000000000..ffc0ee55e --- /dev/null +++ b/platform/ui/src/contextProviders/ViewportDialogProvider.mdx @@ -0,0 +1,165 @@ +--- +name: ViewportDialogProvider +route: customHooks/viewportDialogProvider +--- + +import { Playground, Props } from 'docz'; + + +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 + + + {() => { + const ViewportNotification = ({ hide }) => { + return ( + + + + + + } + /> + ); + }; + const ViewportActionButtons = () => { + const dialog = useViewportDialog(); + return ( + + ); + }; + return ( +
+
+ +
+ + CONTENT 1 +
+
+
+
+ +
+ + CONTENT 1 +
+
+
+
+ ); + }} +
+ +## Example using UIViewportDialogService + + + {() => { + const ViewportNotification = ({ hide }) => { + return ( + + + + + + } + /> + ); + }; + // 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 ( +
+
+ + +
+
+ +
+ CONTENT 0 +
+
+ +
+ CONTENT 1 +
+
+
+
+ ); + }} +
+ + +## Properties: + + diff --git a/platform/ui/src/contextProviders/index.js b/platform/ui/src/contextProviders/index.js index f2e6811fb..177d456af 100644 --- a/platform/ui/src/contextProviders/index.js +++ b/platform/ui/src/contextProviders/index.js @@ -3,4 +3,9 @@ export { useModal, withModal, ModalConsumer, -} from './ModalProvider.js'; +} from './ModalProvider'; + +export { + default as ViewportDialogProvider, + useViewportDialog, +} from './ViewportDialogProvider'; diff --git a/platform/ui/src/gatsby-theme-docz/components/Sidebar/index.js b/platform/ui/src/gatsby-theme-docz/components/Sidebar/index.js index 587ca6365..c34861d72 100644 --- a/platform/ui/src/gatsby-theme-docz/components/Sidebar/index.js +++ b/platform/ui/src/gatsby-theme-docz/components/Sidebar/index.js @@ -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 (

{menuName}

- {customMenus[menuName].map(item => item)} + {customMenus[menuName].map((item) => item)}
); }); }; - 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;