From 911f93d378c161ff5e75bde000dc1c8ee1079631 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Wed, 24 Jun 2020 12:11:54 -0300 Subject: [PATCH 1/8] Update toolbar service and add active icon in nested button --- .../ViewerLayout/ToolbarButtonNestedMenu.jsx | 36 +++++++++++-------- extensions/default/src/ViewerLayout/index.jsx | 13 ++++--- .../services/ToolBarService/ToolBarService.js | 15 ++++---- .../ToolbarButton/ToolbarButton.jsx | 1 + 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx index 097257e75..c6d8f7a35 100644 --- a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx +++ b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx @@ -2,32 +2,31 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { ToolbarButton } from '@ohif/ui'; -function NestedMenu({ children }) { +function NestedMenu({ children, label, icon }) { const [isOpen, setIsOpen] = useState(false); - useEffect(() => { - function closeNestedMenu() { - if (isOpen) { - setIsOpen(false); - } + const toggleNestedMenu = () => setIsOpen(!isOpen); + + const closeNestedMenu = () => { + if (isOpen) { + setIsOpen(false); } + }; + + useEffect(() => { window.addEventListener('click', closeNestedMenu); return () => { window.removeEventListener('click', closeNestedMenu); }; }, [isOpen]); - const dropdownContent = isOpen ? children : undefined; - return ( { - setIsOpen(!isOpen); - }} - dropdownContent={dropdownContent} + id="NestedMenu" + label={label} + icon={icon} + onClick={toggleNestedMenu} + dropdownContent={isOpen && children} isActive={isOpen} type="primary" /> @@ -36,6 +35,13 @@ function NestedMenu({ children }) { NestedMenu.propTypes = { children: PropTypes.any.isRequired, + icon: PropTypes.string, + label: PropTypes.string, +}; + +NestedMenu.defaultProps = { + icon: "tool-more-menu", + label: "More", }; export default NestedMenu; diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index 730ee515b..8a92a2d93 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -55,7 +55,13 @@ function ViewerLayout({ }; }; + const defaultTool = { icon: 'tool-more-menu', label: 'More' }; const [toolbars, setToolbars] = useState({ primary: [], secondary: [] }); + const [activeTool, setActiveTool] = useState(defaultTool); + const onSecondaryClickHandler = () => setActiveTool(defaultTool); + const onPrimaryClickHandler = (evt, btn) => { + setActiveTool(btn.props.isActive ? btn.props : defaultTool); + }; useEffect(() => { const { unsubscribe } = ToolBarService.subscribe( @@ -63,8 +69,8 @@ function ViewerLayout({ () => { console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT'); const updatedToolbars = { - primary: ToolBarService.getButtonSection('primary'), - secondary: ToolBarService.getButtonSection('secondary'), + primary: ToolBarService.getButtonSection('primary', { onClick: onPrimaryClickHandler }), + secondary: ToolBarService.getButtonSection('secondary', { onClick: onSecondaryClickHandler }), }; setToolbars(updatedToolbars); } @@ -86,11 +92,10 @@ function ViewerLayout({ if (!isNested) { const { id, Component, componentProps } = toolDef; - return ; } else { return ( - +
{toolDef.map(x => { const { id, Component, componentProps } = x; diff --git a/platform/core/src/services/ToolBarService/ToolBarService.js b/platform/core/src/services/ToolBarService/ToolBarService.js index 95cc1b431..589839448 100644 --- a/platform/core/src/services/ToolBarService/ToolBarService.js +++ b/platform/core/src/services/ToolBarService/ToolBarService.js @@ -62,7 +62,7 @@ export default class ToolBarService { this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {}); } - getButtonSection(key) { + getButtonSection(key, props) { const buttonSectionIds = this.buttonSections[key]; const buttonsInSection = []; @@ -79,7 +79,7 @@ export default class ToolBarService { btnIds.forEach(nestedBtnId => { const nestedBtn = this.buttons[nestedBtnId]; - const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key); + const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key, props); nestedButtons.push(mappedNestedBtn); }); @@ -90,7 +90,7 @@ export default class ToolBarService { } else { const btnId = btnIdOrArray; const btn = this.buttons[btnId]; - const mappedBtn = this._mapButtonToDisplay(btn, key); + const mappedBtn = this._mapButtonToDisplay(btn, key, props); buttonsInSection.push(mappedBtn); } @@ -136,8 +136,8 @@ export default class ToolBarService { * @param {*} btn * @param {*} btnSection */ - _mapButtonToDisplay(btn, btnSection) { - const { id, type, component, props } = btn; + _mapButtonToDisplay(btn, btnSection, props) { + const { id, type, component } = btn; const buttonType = this._buttonTypes()[type]; if (!buttonType) { @@ -154,12 +154,15 @@ export default class ToolBarService { if (btn.props.clickHandler) { btn.clickHandler(evt, btn, btnSection); } + if (props && props.onClick) { + props.onClick(evt, btn, btnSection, props); + } }; return { id, Component: component || buttonType.defaultComponent, - componentProps: Object.assign({}, props, { onClick }), // + componentProps: Object.assign({}, btn.props, props, { onClick }), // }; } } diff --git a/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx b/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx index 904d0dcf3..3ce1b20e1 100644 --- a/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx +++ b/platform/ui/src/components/ToolbarButton/ToolbarButton.jsx @@ -25,6 +25,7 @@ const ToolbarButton = ({ }; const shouldShowDropdown = !!isActive && !!dropdownContent; + return (
Date: Thu, 25 Jun 2020 12:18:23 -0400 Subject: [PATCH 2/8] fix: add missing `supportsFuzzyMatching` and `defaultDataSourceName` keys to appConfig --- platform/viewer/public/config/netlify.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platform/viewer/public/config/netlify.js b/platform/viewer/public/config/netlify.js index 39b804d2a..6861a50ed 100644 --- a/platform/viewer/public/config/netlify.js +++ b/platform/viewer/public/config/netlify.js @@ -19,7 +19,9 @@ window.config = { imageRendering: 'wadors', thumbnailRendering: 'wadors', enableStudyLazyLoad: true, + supportsFuzzyMatching: true, }, }, ], + defaultDataSourceName: 'dicomweb', }; From 6aac69e1ba6323074c7bf118c1670bfe539f49f0 Mon Sep 17 00:00:00 2001 From: James Petts Date: Thu, 25 Jun 2020 17:46:17 +0100 Subject: [PATCH 3/8] OHIF-176: v2 - Render non-tracked measurements as a dashed lines. (#1805) * Render non-tracked measurements as a dashed line. * bump CST version. * Update extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js remove tracked series console log * Remove empty console log * Remove debugger * Remove debugger Co-authored-by: Danny Brown --- extensions/cornerstone/package.json | 2 +- extensions/dicom-rt/package.json | 2 +- extensions/dicom-sr/package.json | 2 +- .../viewports/TrackedCornerstoneViewport.js | 92 +++++++++++++++++++ extensions/vtk/package.json | 2 +- platform/core/package.json | 2 +- platform/viewer/package.json | 2 +- yarn.lock | 8 +- 8 files changed, 102 insertions(+), 10 deletions(-) diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index 4dd657d40..d1b8588ad 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -33,7 +33,7 @@ "@ohif/ui": "^0.50.0", "cornerstone-core": "^2.3.0", "cornerstone-math": "^0.1.8", - "cornerstone-tools": "4.15.1", + "cornerstone-tools": "4.16.0", "dcmjs": "^0.12.3", "cornerstone-wado-image-loader": "^3.1.2", "dicom-parser": "^1.8.3", diff --git a/extensions/dicom-rt/package.json b/extensions/dicom-rt/package.json index fb67f9acf..ddd9bfac0 100644 --- a/extensions/dicom-rt/package.json +++ b/extensions/dicom-rt/package.json @@ -30,7 +30,7 @@ "peerDependencies": { "@ohif/core": "^0.50.0", "cornerstone-core": "^2.2.8", - "cornerstone-tools": "4.15.1", + "cornerstone-tools": "4.16.0", "dcmjs": "^0.12.3", "prop-types": "^15.6.2", "react": "^16.8.6", diff --git a/extensions/dicom-sr/package.json b/extensions/dicom-sr/package.json index 6b14984c9..0757083bf 100644 --- a/extensions/dicom-sr/package.json +++ b/extensions/dicom-sr/package.json @@ -33,7 +33,7 @@ "@ohif/ui": "^0.50.0", "cornerstone-core": "^2.3.0", "cornerstone-math": "^0.1.8", - "cornerstone-tools": "4.15.1", + "cornerstone-tools": "4.16.0", "dcmjs": "^0.12.3", "cornerstone-wado-image-loader": "^3.1.2", "dicom-parser": "^1.8.3", diff --git a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js index 327c49a77..c582bd929 100644 --- a/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js +++ b/extensions/measurement-tracking/src/viewports/TrackedCornerstoneViewport.js @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import cornerstone from 'cornerstone-core'; +import cornerstoneTools from 'cornerstone-tools'; import CornerstoneViewport from 'react-cornerstone-viewport'; import OHIF, { DicomMetadataStore } from '@ohif/core'; import { @@ -13,6 +14,18 @@ import debounce from 'lodash.debounce'; import throttle from 'lodash.throttle'; import { useTrackedMeasurements } from './../getContextModule'; +// TODO -> Get this list from the list of tracked measurements. +const { + ArrowAnnotateTool, + BidirectionalTool, + EllipticalRoiTool, + LengthTool, +} = cornerstoneTools; + +const BaseAnnotationTool = cornerstoneTools.importInternal( + 'base/BaseAnnotationTool' +); + // const cine = viewportSpecificData.cine; // isPlaying = cine.isPlaying === true; @@ -27,6 +40,7 @@ function TrackedCornerstoneViewport({ viewportIndex, }) { const [trackedMeasurements] = useTrackedMeasurements(); + const [ { activeViewportIndex, viewports }, dispatchViewportGrid, @@ -34,6 +48,8 @@ function TrackedCornerstoneViewport({ // viewportIndex, onSubmit const [viewportDialogState, viewportDialogApi] = useViewportDialog(); const [viewportData, setViewportData] = useState(null); + const [element, setElement] = useState(null); + const [isTracked, setIsTracked] = useState(false); // TODO: Still needed? Better way than import `OHIF` and destructure? // Why is this managed by `core`? useEffect(() => { @@ -42,6 +58,75 @@ function TrackedCornerstoneViewport({ }; }, []); + useEffect(() => { + if (!element) { + return; + } + const allTools = cornerstoneTools.store.state.tools; + const toolsForElement = allTools.filter(tool => tool.element === element); + + toolsForElement.forEach(tool => { + if ( + tool instanceof ArrowAnnotateTool || + tool instanceof BidirectionalTool || + tool instanceof EllipticalRoiTool || + tool instanceof LengthTool + ) { + const configuration = tool.configuration; + + configuration.renderDashed = !isTracked; + + tool.configuration = configuration; + } + }); + + const enabledElement = cornerstone.getEnabledElement(element); + + if (enabledElement.image) { + cornerstone.updateImage(element); + } + }, [isTracked]); + + const onElementEnabled = evt => { + const eventData = evt.detail; + const targetElement = eventData.element; + + const allTools = cornerstoneTools.store.state.tools; + + const toolsForElement = allTools.filter( + tool => tool.element === targetElement + ); + + toolsForElement.forEach(tool => { + if ( + tool instanceof ArrowAnnotateTool || + tool instanceof BidirectionalTool || + tool instanceof EllipticalRoiTool || + tool instanceof LengthTool + ) { + const configuration = tool.configuration; + + configuration.renderDashed = !isTracked; + + tool.configuration = configuration; + } else if (tool instanceof BaseAnnotationTool) { + const configuration = tool.configuration; + + configuration.renderDashed = true; + + tool.configuration = configuration; + } + }); + + const enabledElement = cornerstone.getEnabledElement(targetElement); + + if (enabledElement.image) { + cornerstone.updateImage(targetElement); + } + + setElement(targetElement); + }; + useEffect(() => { const { StudyInstanceUID, @@ -116,6 +201,7 @@ function TrackedCornerstoneViewport({ vp => vp.displaySetInstanceUID === displaySet.displaySetInstanceUID ); const { trackedSeries } = trackedMeasurements.context; + const { Modality, SeriesDate, @@ -131,6 +217,11 @@ function TrackedCornerstoneViewport({ SliceThickness, } = displaySet.images[0]; + + if (trackedSeries.includes(SeriesInstanceUID) !== isTracked) { + setIsTracked(!isTracked); + } + return ( <> Date: Thu, 25 Jun 2020 15:27:42 -0300 Subject: [PATCH 4/8] Create window level dropdown menu --- extensions/default/src/ViewerLayout/index.jsx | 9 +- modes/longitudinal/src/toolbarButtons.js | 50 ++++++++++- .../services/ToolBarService/ToolBarService.js | 15 ++-- platform/ui/index.js | 2 + .../ExpandableToolbarButton.css | 16 ++++ .../ExpandableToolbarButton.jsx | 86 +++++++++++++++++++ .../ExpandableToolbarButton.mdx | 58 +++++++++++++ .../ExpandableToolbarButton/index.js | 2 + .../src/components/IconButton/IconButton.jsx | 4 +- .../ui/src/components/ListMenu/ListMenu.jsx | 65 ++++++++++++++ .../ui/src/components/ListMenu/ListMenu.mdx | 52 +++++++++++ platform/ui/src/components/ListMenu/index.js | 2 + platform/ui/src/components/Select/Select.jsx | 2 +- .../ui/src/components/Tooltip/Tooltip.jsx | 6 +- platform/ui/src/components/index.js | 4 + platform/ui/tailwind.config.js | 7 ++ 16 files changed, 367 insertions(+), 13 deletions(-) create mode 100644 platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css create mode 100644 platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx create mode 100644 platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx create mode 100644 platform/ui/src/components/ExpandableToolbarButton/index.js create mode 100644 platform/ui/src/components/ListMenu/ListMenu.jsx create mode 100644 platform/ui/src/components/ListMenu/ListMenu.mdx create mode 100644 platform/ui/src/components/ListMenu/index.js diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index 730ee515b..941a5487a 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -57,13 +57,20 @@ function ViewerLayout({ const [toolbars, setToolbars] = useState({ primary: [], secondary: [] }); + const onPrimaryClickHandler = (evt, btn) => { + if (btn.props && btn.props.commands && evt.value && btn.props.commands[evt.value]) { + const { commandName, commandOptions } = btn.props.commands[evt.value]; + commandsManager.runCommand(commandName, commandOptions); + } + }; + useEffect(() => { const { unsubscribe } = ToolBarService.subscribe( ToolBarService.EVENTS.TOOL_BAR_MODIFIED, () => { console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT'); const updatedToolbars = { - primary: ToolBarService.getButtonSection('primary'), + primary: ToolBarService.getButtonSection('primary', { onClick: onPrimaryClickHandler }), secondary: ToolBarService.getButtonSection('secondary'), }; setToolbars(updatedToolbars); diff --git a/modes/longitudinal/src/toolbarButtons.js b/modes/longitudinal/src/toolbarButtons.js index 3f4db8160..6fc5634ea 100644 --- a/modes/longitudinal/src/toolbarButtons.js +++ b/modes/longitudinal/src/toolbarButtons.js @@ -1,5 +1,8 @@ // TODO: torn, can either bake this here; or have to create a whole new button type // Only ways that you can pass in a custom React component for render :l +import { ExpandableToolbarButton, ListMenu } from '@ohif/ui'; +import React from 'react'; +import classnames from 'classnames'; export default [ // Divider @@ -29,13 +32,58 @@ export default [ config: { groupName: 'primaryTool', }, + component: ExpandableToolbarButton, props: { isActive: true, icon: 'tool-window-level', - label: 'Levels', commandName: 'setToolActive', commandOptions: { toolName: 'Wwwc' }, + commands: { + 'windowLevelPreset1': { + commandName: 'windowLevelPreset1', + commandOptions: {}, + }, + 'windowLevelPreset2': { + commandName: 'windowLevelPreset2', + commandOptions: {}, + }, + 'windowLevelPreset3': { + commandName: 'windowLevelPreset3', + commandOptions: {}, + }, + 'windowLevelPreset4': { + commandName: 'windowLevelPreset4', + commandOptions: {}, + }, + 'windowLevelPreset5': { + commandName: 'windowLevelPreset5', + commandOptions: {}, + } + }, type: 'primary', + content: ListMenu, + contentProps: { + options: [ + { value: 'windowLevelPreset1', title: 'Soft tissue', subtitle: '400 / 40' }, + { value: 'windowLevelPreset2', title: 'Lung', subtitle: '1500 / -600' }, + { value: 'windowLevelPreset3', title: 'Liver', subtitle: '150 / 90' }, + { value: 'windowLevelPreset4', title: 'Bone', subtitle: '80 / 40' }, + { value: 'windowLevelPreset5', title: 'Brain', subtitle: '2500 / 480' }, + ], + renderer: ({ title, subtitle, isActive, index }) => ( + <> +
+ + {title} + + + {subtitle} + +
+ {index + 1} + + ) + } }, }, { diff --git a/platform/core/src/services/ToolBarService/ToolBarService.js b/platform/core/src/services/ToolBarService/ToolBarService.js index 95cc1b431..589839448 100644 --- a/platform/core/src/services/ToolBarService/ToolBarService.js +++ b/platform/core/src/services/ToolBarService/ToolBarService.js @@ -62,7 +62,7 @@ export default class ToolBarService { this._broadcastChange(this.EVENTS.TOOL_BAR_MODIFIED, {}); } - getButtonSection(key) { + getButtonSection(key, props) { const buttonSectionIds = this.buttonSections[key]; const buttonsInSection = []; @@ -79,7 +79,7 @@ export default class ToolBarService { btnIds.forEach(nestedBtnId => { const nestedBtn = this.buttons[nestedBtnId]; - const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key); + const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key, props); nestedButtons.push(mappedNestedBtn); }); @@ -90,7 +90,7 @@ export default class ToolBarService { } else { const btnId = btnIdOrArray; const btn = this.buttons[btnId]; - const mappedBtn = this._mapButtonToDisplay(btn, key); + const mappedBtn = this._mapButtonToDisplay(btn, key, props); buttonsInSection.push(mappedBtn); } @@ -136,8 +136,8 @@ export default class ToolBarService { * @param {*} btn * @param {*} btnSection */ - _mapButtonToDisplay(btn, btnSection) { - const { id, type, component, props } = btn; + _mapButtonToDisplay(btn, btnSection, props) { + const { id, type, component } = btn; const buttonType = this._buttonTypes()[type]; if (!buttonType) { @@ -154,12 +154,15 @@ export default class ToolBarService { if (btn.props.clickHandler) { btn.clickHandler(evt, btn, btnSection); } + if (props && props.onClick) { + props.onClick(evt, btn, btnSection, props); + } }; return { id, Component: component || buttonType.defaultComponent, - componentProps: Object.assign({}, props, { onClick }), // + componentProps: Object.assign({}, btn.props, props, { onClick }), // }; } } diff --git a/platform/ui/index.js b/platform/ui/index.js index db85039d9..2025cfb3b 100644 --- a/platform/ui/index.js +++ b/platform/ui/index.js @@ -32,6 +32,8 @@ export { DateRange, Dialog, EmptyStudies, + ExpandableToolbarButton, + ListMenu, Icon, IconButton, Input, diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css new file mode 100644 index 000000000..3a5a6c8be --- /dev/null +++ b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.css @@ -0,0 +1,16 @@ +.ExpandableToolbarButton:hover .ExpandableToolbarButton__arrow:after { + content: ""; + position: absolute; + bottom: -10px; + border-width: 10px 10px 0; + border-style: solid; + border-color:#5acce6 transparent; +} + +.ExpandableToolbarButton .ExpandableToolbarButton__content { + display: none; +} + +.ExpandableToolbarButton:hover .ExpandableToolbarButton__content { + display: block; +} diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx new file mode 100644 index 000000000..b343d9ed7 --- /dev/null +++ b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.jsx @@ -0,0 +1,86 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +import { IconButton, Icon } from '@ohif/ui'; + +import './ExpandableToolbarButton.css'; + +const ExpandableToolbarButton = ({ + type, + id, + isActive, + onClick, + icon, + className, + content: Content, + contentProps +}) => { + const classes = { + type: { + primary: isActive + ? 'text-black' + : 'text-common-bright hover:bg-primary-dark hover:text-primary-light', + secondary: isActive + ? 'text-black' + : 'text-white hover:bg-secondary-dark focus:bg-secondary-dark', + }, + }; + + const onChildClickHandler = (...args) => { + onClick(...args); + + if (contentProps.onClick) { + contentProps.onClick(...args); + } + }; + + const onClickHandler = (...args) => { + onClick(...args); + }; + + return ( +
+ + + +
+
+ +
+
+
+ ); +}; + +const noop = () => { }; + +ExpandableToolbarButton.defaultProps = { + isActive: false, + type: 'primary', + content: null, + onClick: noop, +}; + +ExpandableToolbarButton.propTypes = { + /* Influences background/hover styling */ + type: PropTypes.oneOf(['primary', 'secondary']), + id: PropTypes.string.isRequired, + isActive: PropTypes.bool, + onClick: PropTypes.func.isRequired, + icon: PropTypes.string.isRequired, + /** Expandable toolbar button content can be replaced for a customized content by passing a node to this value. */ + content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + contentProps: PropTypes.object, +}; + +export default ExpandableToolbarButton; diff --git a/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx new file mode 100644 index 000000000..b6867f370 --- /dev/null +++ b/platform/ui/src/components/ExpandableToolbarButton/ExpandableToolbarButton.mdx @@ -0,0 +1,58 @@ +--- +name: Expandable Toolbar Button +menu: General +route: components/expandableToolbarButton +--- + +import { useState } from 'react'; +import { Playground, Props } from 'docz'; +import { ExpandableToolbarButton, ListMenu } from '@ohif/ui'; + +# Toolbar Button + +Expandable Toolbar Buttons are used to populate the Toolbar. + +## Import + +```javascript +import { ExpandableToolbarButton, ListMenu } from '@ohif/ui'; +``` + + + {() => { + const props = { + content: ListMenu, + contentProps: { + options: [ + { value: 'windowLevelPreset1', title: 'Soft tissue', subtitle: '400 / 40' }, + { value: 'windowLevelPreset2', title: 'Lung', subtitle: '1500 / -600' }, + { value: 'windowLevelPreset3', title: 'Liver', subtitle: '150 / 90' }, + { value: 'windowLevelPreset4', title: 'Bone', subtitle: '80 / 40' }, + { value: 'windowLevelPreset5', title: 'Brain', subtitle: '2500 / 480' }, + ], + renderer: ({ title, subtitle, isActive, index }) => ( + <> +
+ + {title} + + + {subtitle} + +
+ {index + 1} + + ) + } + }; + return ( +
+ +
+ ); + }} +
+ +## Properties + + diff --git a/platform/ui/src/components/ExpandableToolbarButton/index.js b/platform/ui/src/components/ExpandableToolbarButton/index.js new file mode 100644 index 000000000..2a5e6032c --- /dev/null +++ b/platform/ui/src/components/ExpandableToolbarButton/index.js @@ -0,0 +1,2 @@ +import ExpandableToolbarButton from './ExpandableToolbarButton'; +export default ExpandableToolbarButton; diff --git a/platform/ui/src/components/IconButton/IconButton.jsx b/platform/ui/src/components/IconButton/IconButton.jsx index 475e61620..d92a6b9ca 100644 --- a/platform/ui/src/components/IconButton/IconButton.jsx +++ b/platform/ui/src/components/IconButton/IconButton.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import classnames from 'classnames'; const baseClasses = - 'text-center items-center justify-center outline-none transition duration-300 ease-in-out font-bold focus:outline-none'; + 'text-center items-center justify-center outline-none font-bold focus:outline-none'; const roundedClasses = { none: '', @@ -113,7 +113,7 @@ const IconButton = ({ }; IconButton.defaultProps = { - onClick: () => {}, + onClick: () => { }, color: 'default', disabled: false, fullWidth: false, diff --git a/platform/ui/src/components/ListMenu/ListMenu.jsx b/platform/ui/src/components/ListMenu/ListMenu.jsx new file mode 100644 index 000000000..56a693740 --- /dev/null +++ b/platform/ui/src/components/ListMenu/ListMenu.jsx @@ -0,0 +1,65 @@ +import React, { useState } from 'react'; +import classnames from 'classnames'; +import PropTypes from 'prop-types'; + +const ListMenu = ({ options = [], renderer, onClick }) => { + const [selectedIndex, setSelectedIndex] = useState(null); + + const ListItem = (props) => { + const flex = 'flex flex-row justify-between items-center'; + const theme = 'bg-indigo-dark'; + const hover = 'hover:bg-primary-dark'; + const spacing = 'p-3 h-8'; + + return ( +
+ {renderer && renderer(props)} +
+ ); + }; + + return ( +
+ {options.map((option, index) => { + const onClickHandler = () => { + setSelectedIndex(index); + onClick({ ...option, index }); + }; + + return ( + + ); + })} +
+ ); +}; + +const noop = () => { }; + +ListMenu.propTypes = { + options: PropTypes.array.isRequired, + renderer: PropTypes.func.isRequired, + onClick: PropTypes.func +}; + +ListMenu.defaultProps = { + onClick: noop +}; + +export default ListMenu; diff --git a/platform/ui/src/components/ListMenu/ListMenu.mdx b/platform/ui/src/components/ListMenu/ListMenu.mdx new file mode 100644 index 000000000..195361e21 --- /dev/null +++ b/platform/ui/src/components/ListMenu/ListMenu.mdx @@ -0,0 +1,52 @@ +--- +name: List Menu +menu: General +route: components/listMenu +--- + +import { useState } from 'react'; +import { Playground, Props } from 'docz'; +import { ListMenu } from '@ohif/ui'; + +# List Menu + +List Menus are used to populate expandable Toolbar. + +## Import + +```javascript +import { ListMenu } from '@ohif/ui'; +``` + + + {() => { + return ( + ( + <> +
+ + {title} + + + {subtitle} + +
+ {index + 1} + + )} + /> + ); + }} +
+ +## Properties + + diff --git a/platform/ui/src/components/ListMenu/index.js b/platform/ui/src/components/ListMenu/index.js new file mode 100644 index 000000000..b9d95c885 --- /dev/null +++ b/platform/ui/src/components/ListMenu/index.js @@ -0,0 +1,2 @@ +import ListMenu from './ListMenu'; +export default ListMenu; diff --git a/platform/ui/src/components/Select/Select.jsx b/platform/ui/src/components/Select/Select.jsx index 3a47d8a6a..399edd541 100644 --- a/platform/ui/src/components/Select/Select.jsx +++ b/platform/ui/src/components/Select/Select.jsx @@ -76,7 +76,7 @@ const Select = ({ options={options} value={selectedOptions} onChange={(selectedOptions, { action }) => { - const newSelection = selectedOptions.reduce( + const newSelection = !selectedOptions.length ? selectedOptions : selectedOptions.reduce( (acc, curr) => acc.concat([curr.value]), [] ); diff --git a/platform/ui/src/components/Tooltip/Tooltip.jsx b/platform/ui/src/components/Tooltip/Tooltip.jsx index fabfa18f6..85119f7c1 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 = ({ content, isSticky, position, tight, children }) => { +const Tooltip = ({ content, isSticky, position, tight, children, isDisabled }) => { const [isActive, setIsActive] = useState(false); const handleMouseOver = () => { @@ -39,7 +39,7 @@ const Tooltip = ({ content, isSticky, position, tight, children }) => { } }; - const isOpen = isSticky || isActive; + const isOpen = (isSticky || isActive) && !isDisabled; return (
Date: Thu, 25 Jun 2020 16:03:06 -0300 Subject: [PATCH 5/8] Update code to avoid intercepting onclick --- extensions/default/src/ViewerLayout/index.jsx | 10 +++++----- extensions/default/src/getToolbarModule.js | 10 +++++++--- .../src/services/ToolBarService/ToolBarService.js | 15 +++++++-------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index 8a92a2d93..0e9a58904 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -58,9 +58,9 @@ function ViewerLayout({ const defaultTool = { icon: 'tool-more-menu', label: 'More' }; const [toolbars, setToolbars] = useState({ primary: [], secondary: [] }); const [activeTool, setActiveTool] = useState(defaultTool); - const onSecondaryClickHandler = () => setActiveTool(defaultTool); - const onPrimaryClickHandler = (evt, btn) => { - setActiveTool(btn.props.isActive ? btn.props : defaultTool); + + const setActiveToolHandler = (tool, isNested) => { + setActiveTool(isNested ? tool : defaultTool); }; useEffect(() => { @@ -69,8 +69,8 @@ function ViewerLayout({ () => { console.warn('~~~ TOOL BAR MODIFIED EVENT CAUGHT'); const updatedToolbars = { - primary: ToolBarService.getButtonSection('primary', { onClick: onPrimaryClickHandler }), - secondary: ToolBarService.getButtonSection('secondary', { onClick: onSecondaryClickHandler }), + primary: ToolBarService.getButtonSection('primary', { setActiveTool: setActiveToolHandler }), + secondary: ToolBarService.getButtonSection('secondary', { setActiveTool: setActiveToolHandler }), }; setToolbars(updatedToolbars); } diff --git a/extensions/default/src/getToolbarModule.js b/extensions/default/src/getToolbarModule.js index 4cc66fda3..293e5cc43 100644 --- a/extensions/default/src/getToolbarModule.js +++ b/extensions/default/src/getToolbarModule.js @@ -9,7 +9,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) { { name: 'ohif.divider', defaultComponent: ToolbarDivider, - clickHandler: () => {}, + clickHandler: () => { }, }, { name: 'ohif.action', @@ -30,7 +30,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) { optionalConfig: [], requiredProps: [], optionalProps: [], - clickHandler: (evt, clickedBtn, btnSectionName) => { + clickHandler: (evt, clickedBtn, btnSectionName, metadata, viewerProps) => { const { props } = clickedBtn; const allButtons = toolbarService.getButtons(); @@ -47,6 +47,10 @@ export default function getToolbarModule({ commandsManager, servicesManager }) { clickedBtn.config.groupName === btn.config.groupName ) { btn.props.isActive = false; + + if (viewerProps.setActiveTool) { + viewerProps.setActiveTool(props, metadata.isNested); + } } }); @@ -63,7 +67,7 @@ export default function getToolbarModule({ commandsManager, servicesManager }) { { name: 'ohif.layoutSelector', defaultComponent: ToolbarLayoutSelector, - clickHandler: (evt, clickedBtn, btnSectionName) => {}, + clickHandler: (evt, clickedBtn, btnSectionName) => { }, }, { name: 'ohif.toggle', diff --git a/platform/core/src/services/ToolBarService/ToolBarService.js b/platform/core/src/services/ToolBarService/ToolBarService.js index 589839448..08cc1e3cb 100644 --- a/platform/core/src/services/ToolBarService/ToolBarService.js +++ b/platform/core/src/services/ToolBarService/ToolBarService.js @@ -79,7 +79,8 @@ export default class ToolBarService { btnIds.forEach(nestedBtnId => { const nestedBtn = this.buttons[nestedBtnId]; - const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key, props); + const metadata = { isNested: true }; + const mappedNestedBtn = this._mapButtonToDisplay(nestedBtn, key, metadata, props); nestedButtons.push(mappedNestedBtn); }); @@ -90,7 +91,8 @@ export default class ToolBarService { } else { const btnId = btnIdOrArray; const btn = this.buttons[btnId]; - const mappedBtn = this._mapButtonToDisplay(btn, key, props); + const metadata = { isNested: false }; + const mappedBtn = this._mapButtonToDisplay(btn, key, metadata, props); buttonsInSection.push(mappedBtn); } @@ -136,7 +138,7 @@ export default class ToolBarService { * @param {*} btn * @param {*} btnSection */ - _mapButtonToDisplay(btn, btnSection, props) { + _mapButtonToDisplay(btn, btnSection, metadata, props) { const { id, type, component } = btn; const buttonType = this._buttonTypes()[type]; @@ -146,7 +148,7 @@ export default class ToolBarService { const onClick = evt => { if (buttonType.clickHandler) { - buttonType.clickHandler(evt, btn, btnSection); + buttonType.clickHandler(evt, btn, btnSection, metadata, props); } if (btn.props.onClick) { btn.onClick(evt, btn, btnSection); @@ -154,15 +156,12 @@ export default class ToolBarService { if (btn.props.clickHandler) { btn.clickHandler(evt, btn, btnSection); } - if (props && props.onClick) { - props.onClick(evt, btn, btnSection, props); - } }; return { id, Component: component || buttonType.defaultComponent, - componentProps: Object.assign({}, btn.props, props, { onClick }), // + componentProps: Object.assign({}, btn.props, { onClick }), // }; } } From 9c3159cc158a541d166d742917ad317981349182 Mon Sep 17 00:00:00 2001 From: Danny Brown Date: Thu, 25 Jun 2020 15:06:14 -0400 Subject: [PATCH 6/8] more descriptive comment for isDisabled prop --- platform/ui/src/components/Tooltip/Tooltip.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/ui/src/components/Tooltip/Tooltip.jsx b/platform/ui/src/components/Tooltip/Tooltip.jsx index 85119f7c1..42ef4498b 100644 --- a/platform/ui/src/components/Tooltip/Tooltip.jsx +++ b/platform/ui/src/components/Tooltip/Tooltip.jsx @@ -88,7 +88,7 @@ Tooltip.defaultProps = { }; Tooltip.propTypes = { - // Allow null +/** prevents tooltip from rendering despite hover/active/sticky */ isDisabled: PropTypes.bool, content: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), position: PropTypes.oneOf([ From 52521c5b56cb71360805d219c22abafbd93d7645 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Thu, 25 Jun 2020 16:49:46 -0300 Subject: [PATCH 7/8] Add active state --- .../default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx index c6d8f7a35..1b0bb5144 100644 --- a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx +++ b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx @@ -20,6 +20,8 @@ function NestedMenu({ children, label, icon }) { }; }, [isOpen]); + const isActive = isOpen || children.props.children.some(c => c.props.isActive); + return ( ); From 6eb474be88f258bd6bfd254995bbed2f171ce592 Mon Sep 17 00:00:00 2001 From: igoroctaviano Date: Thu, 25 Jun 2020 16:51:48 -0300 Subject: [PATCH 8/8] Add active state --- .../default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx | 6 ++---- extensions/default/src/ViewerLayout/index.jsx | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx index 1b0bb5144..6e91c0418 100644 --- a/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx +++ b/extensions/default/src/ViewerLayout/ToolbarButtonNestedMenu.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { ToolbarButton } from '@ohif/ui'; -function NestedMenu({ children, label, icon }) { +function NestedMenu({ children, label, icon, isActive }) { const [isOpen, setIsOpen] = useState(false); const toggleNestedMenu = () => setIsOpen(!isOpen); @@ -20,8 +20,6 @@ function NestedMenu({ children, label, icon }) { }; }, [isOpen]); - const isActive = isOpen || children.props.children.some(c => c.props.isActive); - return ( ); diff --git a/extensions/default/src/ViewerLayout/index.jsx b/extensions/default/src/ViewerLayout/index.jsx index e82352c57..cf2c11826 100644 --- a/extensions/default/src/ViewerLayout/index.jsx +++ b/extensions/default/src/ViewerLayout/index.jsx @@ -55,7 +55,7 @@ function ViewerLayout({ }; }; - const defaultTool = { icon: 'tool-more-menu', label: 'More' }; + const defaultTool = { icon: 'tool-more-menu', label: 'More', isActive: false }; const [toolbars, setToolbars] = useState({ primary: [], secondary: [] }); const [activeTool, setActiveTool] = useState(defaultTool); @@ -102,7 +102,7 @@ function ViewerLayout({ return ; } else { return ( - +
{toolDef.map(x => { const { id, Component, componentProps } = x;