feature(segmentation): Add contour tools and utilities in a side panel (#5517)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
This commit is contained in:
73 files changed
+2770
-435
No files matched your search
@@ -28,7 +28,7 @@ const SidePanelWithServices = ({
|
||||
onClose,
|
||||
...props
|
||||
}: SidePanelWithServicesProps) => {
|
||||
const panelService = servicesManager?.services?.panelService;
|
||||
const { panelService, toolbarService, viewportGridService } = servicesManager.services;
|
||||
|
||||
// Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM.
|
||||
// Thus going to the Study List page and back to the viewer resets this flag for a SidePanel.
|
||||
@@ -37,9 +37,15 @@ const SidePanelWithServices = ({
|
||||
const [closedManually, setClosedManually] = useState(false);
|
||||
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
|
||||
|
||||
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
|
||||
setActiveTabIndex(activeTabIndex);
|
||||
}, []);
|
||||
const handleActiveTabIndexChange = useCallback(
|
||||
({ activeTabIndex }) => {
|
||||
const { activeViewportId: viewportId } = viewportGridService.getState();
|
||||
toolbarService.refreshToolbarState({ viewportId });
|
||||
|
||||
setActiveTabIndex(activeTabIndex);
|
||||
},
|
||||
[toolbarService, viewportGridService]
|
||||
);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
setSidePanelExpanded(true);
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
import React from 'react';
|
||||
import { useToolbar } from '@ohif/core';
|
||||
|
||||
/**
|
||||
* Props for the Toolbar component that renders a collection of toolbar buttons and/or button sections.
|
||||
*
|
||||
* @interface ToolbarProps
|
||||
*/
|
||||
interface ToolbarProps {
|
||||
/**
|
||||
* The section of buttons to display in the toolbar.
|
||||
* Common values include 'primary', 'secondary', 'tertiary', etc.
|
||||
* Defaults to 'primary' if not specified.
|
||||
*
|
||||
* @default 'primary'
|
||||
*/
|
||||
buttonSection?: string;
|
||||
|
||||
/**
|
||||
* The unique identifier of the viewport this toolbar is associated with.
|
||||
*/
|
||||
viewportId?: string;
|
||||
|
||||
/**
|
||||
* The numeric position or location of the toolbar.
|
||||
* Used for ordering and layout purposes in the UI.
|
||||
*/
|
||||
location?: number;
|
||||
}
|
||||
|
||||
@@ -60,7 +81,17 @@ export function Toolbar({ buttonSection = 'primary', viewportId, location }: Too
|
||||
/>
|
||||
);
|
||||
|
||||
return <div key={id}>{tool}</div>;
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
// This wrapper div exists solely for React's key prop requirement during reconciliation.
|
||||
// We use display:contents to make it transparent to the layout engine (children appear
|
||||
// as direct children of the parent) while keeping it in the DOM for React's virtual DOM.
|
||||
className="contents"
|
||||
>
|
||||
{tool}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import ToolButtonListWrapper from './Toolbar/ToolButtonListWrapper';
|
||||
import ToolRowWrapper from './Toolbar/ToolRowWrapper';
|
||||
import { ToolBoxButtonGroupWrapper, ToolBoxButtonWrapper } from './Toolbar/ToolBoxWrapper';
|
||||
import { ToolButtonWrapper } from './Toolbar/ToolButtonWrapper';
|
||||
import { Toolbar } from './Toolbar';
|
||||
|
||||
export default function getToolbarModule({ commandsManager, servicesManager }: withAppTypes) {
|
||||
const { cineService } = servicesManager.services;
|
||||
@@ -45,6 +46,10 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w
|
||||
name: 'ohif.progressDropdown',
|
||||
defaultComponent: ProgressDropdownWithService,
|
||||
},
|
||||
{
|
||||
name: 'ohif.Toolbar',
|
||||
defaultComponent: Toolbar,
|
||||
},
|
||||
{
|
||||
name: 'evaluate.cine',
|
||||
evaluate: () => {
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Icons, PanelSection, ToolSettings } from '@ohif/ui-next';
|
||||
import { useSystem, useToolbar } from '@ohif/core';
|
||||
import classnames from 'classnames';
|
||||
import { useSystem, useToolbar, useActiveToolOptions } from '@ohif/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ButtonProps {
|
||||
isActive?: boolean;
|
||||
options?: unknown;
|
||||
/**
|
||||
* Props for the Toolbox component that renders a collection of toolbar button sections.
|
||||
*/
|
||||
interface ToolboxProps {
|
||||
/**
|
||||
* The unique identifier of the button section this toolbox represents.
|
||||
*/
|
||||
buttonSectionId: string;
|
||||
|
||||
/**
|
||||
* The display title for the toolbox.
|
||||
*/
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -19,7 +28,7 @@ interface ButtonProps {
|
||||
* role in enhancing the app with a toolbox by providing a way to integrate
|
||||
* and display various tools and their corresponding options
|
||||
*/
|
||||
export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; title: string }) {
|
||||
export function Toolbox({ buttonSectionId, title }: ToolboxProps) {
|
||||
const { servicesManager } = useSystem();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -30,6 +39,8 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
|
||||
buttonSection: buttonSectionId,
|
||||
});
|
||||
|
||||
const { activeToolOptions } = useActiveToolOptions({ buttonSectionId });
|
||||
|
||||
if (!toolboxSections.length) {
|
||||
return null;
|
||||
}
|
||||
@@ -41,35 +52,6 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to check a list of buttons for an active tool.
|
||||
const findActiveOptions = (buttons: any[]): unknown => {
|
||||
for (const tool of buttons) {
|
||||
if (tool.componentProps.isActive) {
|
||||
return tool.componentProps.options;
|
||||
}
|
||||
if (tool.componentProps.buttonSection) {
|
||||
const nestedButtons = toolbarService.getButtonPropsInButtonSection(
|
||||
tool.componentProps.buttonSection
|
||||
) as ButtonProps[];
|
||||
const activeNested = nestedButtons.find(nested => nested.isActive);
|
||||
if (activeNested) {
|
||||
return activeNested.options;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Look for active tool options across all sections.
|
||||
const activeToolOptions = toolboxSections.reduce((activeOptions, section) => {
|
||||
if (activeOptions) {
|
||||
return activeOptions;
|
||||
}
|
||||
const sectionId = section.componentProps.buttonSection;
|
||||
const buttons = toolbarService.getButtonSection(sectionId);
|
||||
return findActiveOptions(buttons);
|
||||
}, null);
|
||||
|
||||
// Define the interaction handler once.
|
||||
const handleInteraction = ({ itemId }: { itemId: string }) => {
|
||||
onInteraction?.({ itemId });
|
||||
@@ -103,19 +85,20 @@ export function Toolbox({ buttonSectionId, title }: { buttonSectionId: string; t
|
||||
return (
|
||||
<div
|
||||
key={sectionId}
|
||||
className="bg-muted flex flex-wrap space-x-2 py-2 px-1"
|
||||
className="bg-muted flex flex-wrap gap-2 py-2 px-1"
|
||||
>
|
||||
{buttons.map(tool => {
|
||||
if (!tool) {
|
||||
// Skip over tools that are not visible. The visible flag is typically set to
|
||||
// false as a result of the evaluator function. The evaluator might explicitly
|
||||
// set visible to false. Alternatively, the ToolbarService will set the visible flag to
|
||||
// false when the evaluator sets disabled to true and the tool has the hideWhenDisabled flag set to true.
|
||||
if (!tool || !tool.componentProps.visible) {
|
||||
return null;
|
||||
}
|
||||
const { id, Component, componentProps } = tool;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={classnames('ml-1')}
|
||||
>
|
||||
<div key={id}>
|
||||
<Component
|
||||
{...componentProps}
|
||||
id={id}
|
||||
|
||||
Reference in new issue
Block a user