diff --git a/extensions/default/src/ViewerLayout/NestedToolbar.jsx b/extensions/default/src/ViewerLayout/NestedToolbar.jsx new file mode 100644 index 000000000..dae97bd0a --- /dev/null +++ b/extensions/default/src/ViewerLayout/NestedToolbar.jsx @@ -0,0 +1,41 @@ +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { ToolbarButton } from '@ohif/ui'; + +function NestedToolbar({ children }) { + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + function closeNestedToolbar() { + if (isOpen) { + setIsOpen(false); + } + } + window.addEventListener('click', closeNestedToolbar); + return () => { + window.removeEventListener('click', closeNestedToolbar); + }; + }, [isOpen]); + + const dropdownContent = isOpen ? children : undefined; + + return ( + { + setIsOpen(!isOpen); + }} + dropdownContent={dropdownContent} + isActive={isOpen} + type="primary" + /> + ); +} + +NestedToolbar.propTypes = { + children: PropTypes.any.isRequired, +}; + +export default NestedToolbar; diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index 78163350e..856fc84d4 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -1,8 +1,9 @@ import React, { useEffect, useState } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; -import { SidePanel, ToolbarButton } from '@ohif/ui'; +import { SidePanel } from '@ohif/ui'; import Header from './Header.jsx'; +import NestedToolbar from './NestedToolbar.jsx'; function ViewerLayout({ // From Extension Module Params @@ -79,12 +80,26 @@ function ViewerLayout({ return (
- {/* relative, flex, justify-center */} -
+
{toolbars.primary.map(toolDef => { - const { id, Component, componentProps } = toolDef; + const isNested = Array.isArray(toolDef); - return ; + if (!isNested) { + const { id, Component, componentProps } = toolDef; + + return ; + } else { + return ( + +
+ {toolDef.map(x => { + const { id, Component, componentProps } = x; + return ; + })} +
+
+ ); + } })}
diff --git a/modes/example/src/index.js b/modes/example/src/index.js index 70636f2a9..21c98e381 100644 --- a/modes/example/src/index.js +++ b/modes/example/src/index.js @@ -55,17 +55,17 @@ export default function mode({ modeConfiguration }) { ]); // Could import layout selector here from org.ohif.default (when it exists!) - ToolBarService.setToolBarLayout([ - // Primary - { - tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'], - moreTools: ['Zoom'], - }, - // Secondary - { - tools: ['Annotate', 'Bidirectional', 'Ellipse', 'Length'], - }, - ]); + // ToolBarService.setToolBarLayout([ + // // Primary + // { + // tools: ['Zoom', 'Levels', 'Pan', 'Capture', 'Layout'], + // moreTools: ['Zoom'], + // }, + // // Secondary + // { + // tools: ['Annotate', 'Bidirectional', 'Ellipse', 'Length'], + // }, + // ]); }, layoutTemplate: ({ routeProps }) => { return { diff --git a/modes/longitudinal/src/index.js b/modes/longitudinal/src/index.js index 82ad973e5..1012f7c7d 100644 --- a/modes/longitudinal/src/index.js +++ b/modes/longitudinal/src/index.js @@ -34,10 +34,10 @@ export default function mode({ modeConfiguration }) { 'Zoom', 'Wwwc', 'Pan', - 'Capture', // toggle --> command to open capture modal? needs to know when it should be off? (promise?) - 'Layout', // toggle --> command to open layout dialog? needs to know when it should be off? (promise?) + 'Capture', + // 'Layout', // toggle --> command to open layout dialog? needs to know when it should be off? (promise?) 'Divider', - 'Zoom', // ? array of arrays? + ['Zoom', 'Wwwc'], ]); ToolBarService.createButtonSection('secondary', [ 'Annotate', diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index 04cbb481f..53309cbc9 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -4,7 +4,7 @@ export default [ id: 'Divider', type: 'ohif.divider', }, - // Primary + // ~~ Primary { id: 'Zoom', type: 'ohif.radioGroup', @@ -50,7 +50,19 @@ export default [ type: 'primary', }, }, - // Secondary + { + id: 'Capture', + type: 'ohif.action', + props: { + icon: 'tool-capture', + label: 'Capture', + commandName: 'showDownloadViewportModal', + type: 'primary', + }, + }, + // Layout + // Expanded/Nested? + // ~~ Secondary { id: 'Annotate', type: 'ohif.radioGroup', @@ -63,8 +75,7 @@ export default [ label: 'Annotate', commandName: 'setToolActive', commandOptions: { toolName: 'ArrowAnnotate' }, - type: 'secondary', // Purely for background color/hover styles :| - // Layout Template can set this? + type: 'secondary', }, }, { diff --git a/platform/core/src/services/ToolBarService/ToolBarService.js b/platform/core/src/services/ToolBarService/ToolBarService.js index 1522fbca7..e5908f3b1 100644 --- a/platform/core/src/services/ToolBarService/ToolBarService.js +++ b/platform/core/src/services/ToolBarService/ToolBarService.js @@ -63,61 +63,40 @@ export default class ToolBarService { } getButtonSection(key) { - const buttonSection = this.buttonSections[key]; + const buttonSectionIds = this.buttonSections[key]; const buttonsInSection = []; - if (!buttonSection) { + if (!buttonSectionIds) { return buttonsInSection; } - buttonSection.forEach(btnId => { - const button = this.buttons[btnId]; - if (button) { - buttonsInSection.push(button); + buttonSectionIds.forEach(btnIdOrArray => { + const isNested = Array.isArray(btnIdOrArray); + + if (isNested) { + const btnIds = btnIdOrArray; + const nestedButtons = []; + + btnIds.forEach(nestedBtnId => { + const nestedBtn = this.buttons[nestedBtnId]; + const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key); + + nestedButtons.push(mappedNestedBtn); + }); + + if (nestedButtons.length) { + buttonsInSection.push(nestedButtons); + } } else { - console.warn(`${btnId} is not a registered button.`); + const btnId = btnIdOrArray; + const btn = this.buttons[btnId]; + const mappedBtn = this._mapButtonToDisplay(btn, key); + + buttonsInSection.push(mappedBtn); } }); - const mappedButtonSection = buttonsInSection.map(bt => { - const { id, type, component, props } = bt; - const buttonType = this._buttonTypes()[type]; - - // Filter out & warn - if (!buttonType) { - return; - } - - const onClick = evt => { - console.warn( - `Handling click for: BTN::${id}`, - evt, - bt, - buttonType, - btnSection - ); - const btnSection = key; - if (buttonType.clickHandler) { - buttonType.clickHandler(evt, bt, btnSection); - } - if (bt.props.onClick) { - bt.onClick(evt, bt, btnSection); - } - if (bt.props.clickHandler) { - bt.clickHandler(evt, bt, btnSection); - } - - this._trySetButtonActive(id); - }; - - return { - id, - Component: component || buttonType.defaultComponent, - componentProps: Object.assign({}, props, { onClick }), // - }; - }); - - return mappedButtonSection; + return buttonsInSection; } /** @@ -152,4 +131,38 @@ export default class ToolBarService { }); } }; + + /** + * + * @param {*} btn + * @param {*} btnSection + */ + _mapButtonToDisplay(btn, btnSection) { + const { id, type, component, props } = btn; + const buttonType = this._buttonTypes()[type]; + + if (!buttonType) { + return; + } + + const onClick = evt => { + if (buttonType.clickHandler) { + buttonType.clickHandler(evt, btn, btnSection); + } + if (btn.props.onClick) { + btn.onClick(evt, btn, btnSection); + } + if (btn.props.clickHandler) { + btn.clickHandler(evt, btn, btnSection); + } + + this._trySetButtonActive(id); + }; + + return { + id, + Component: component || buttonType.defaultComponent, + componentProps: Object.assign({}, props, { onClick }), // + }; + } } diff --git a/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx b/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx index 4c94af2fc..99ad2fa84 100644 --- a/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx +++ b/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx @@ -28,6 +28,7 @@ const ToolbarButton = ({ return (
diff --git a/platform/ui/src/components/Tooltip/Tooltip.jsx b/platform/ui/src/components/Tooltip/Tooltip.jsx index b4a1a48d4..8645b3846 100644 --- a/platform/ui/src/components/Tooltip/Tooltip.jsx +++ b/platform/ui/src/components/Tooltip/Tooltip.jsx @@ -24,7 +24,7 @@ const arrowPositionStyle = { }, }; -const Tooltip = ({ position, content, tight, children }) => { +const Tooltip = ({ content, isSticky, position, tight, children }) => { const [isActive, setIsActive] = useState(false); const handleMouseOver = () => { @@ -39,6 +39,8 @@ const Tooltip = ({ position, content, tight, children }) => { } }; + const isOpen = isSticky || isActive; + return (
{ {children}
{ > {content} { Tooltip.defaultProps = { tight: false, + isSticky: false, position: 'bottom', }; Tooltip.propTypes = { - tight: PropTypes.bool, + content: PropTypes.node.isRequired, position: PropTypes.oneOf([ 'bottom', 'bottom-left', @@ -92,8 +95,9 @@ Tooltip.propTypes = { 'left', 'right', ]), + isSticky: PropTypes.bool, + tight: PropTypes.bool, children: PropTypes.node.isRequired, - content: PropTypes.node.isRequired, }; export default Tooltip; diff --git a/platform/ui/src/components/Tooltip/tooltip.css b/platform/ui/src/components/Tooltip/tooltip.css index 1c388a835..28c0b3991 100644 --- a/platform/ui/src/components/Tooltip/tooltip.css +++ b/platform/ui/src/components/Tooltip/tooltip.css @@ -1,10 +1,10 @@ .tooltip { - @apply z-10 absolute; + @apply absolute z-10; } /* TOOLTIP WORKAROUND FOR ARROW UP */ .tooltip.tooltip-bottom .tooltip-box::before { - @apply bg-primary-dark absolute z-10; + @apply absolute z-10 bg-primary-dark; content: ''; width: 14px; height: 1px; @@ -44,7 +44,7 @@ } .tooltip.tooltip-right .tooltip-box::before { - @apply bg-primary-dark absolute z-10; + @apply absolute z-10 bg-primary-dark; content: ''; width: 2px; height: 15px; @@ -54,7 +54,7 @@ } .tooltip.tooltip-left .tooltip-box::before { - @apply bg-primary-dark absolute z-10; + @apply absolute z-10 bg-primary-dark; content: ''; width: 2px; height: 15px; @@ -64,7 +64,7 @@ } .tooltip.tooltip-bottom-right .tooltip-box::before { - @apply bg-primary-dark absolute z-10; + @apply absolute z-10 bg-primary-dark; content: ''; width: 15px; height: 2px; @@ -73,19 +73,10 @@ } .tooltip.tooltip-bottom-left .tooltip-box::before { - @apply bg-primary-dark absolute z-10; + @apply absolute z-10 bg-primary-dark; content: ''; width: 15px; height: 2px; left: 5px; top: -1px; } - -/* TODO: REMOVE BELOW CLASS "showTooltipOnHover" AFTER TOOLTIP REPLACEMENT */ -.showTooltipOnHover .tooltip { - display: none; -} - -.showTooltipOnHover:hover .tooltip { - display: block; -} diff --git a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx index fcf5f4745..139fe9927 100644 --- a/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx +++ b/platform/ui/src/components/ViewportActionBar/ViewportActionBar.jsx @@ -184,7 +184,7 @@ const ViewportActionBar = ({ studyData, onSeriesChange }) => {
} > -
+