feat(toolbox): Refactor Toolbar and Toolbox to enable sections (#4825)
This commit is contained in:
1 parent
86c12246ac
commit
cc5cdfb081
79 files changed
+3527
-2992
No files matched your search
@@ -13,18 +13,23 @@ interface RadioOption {
|
||||
name: string;
|
||||
value: string;
|
||||
values: RadioValue[];
|
||||
commands?: (val: string) => void;
|
||||
onChange?: (val: string) => void;
|
||||
}
|
||||
|
||||
interface RowSegmentedControlProps {
|
||||
option: RadioOption;
|
||||
className?: string;
|
||||
onChange?: (val: string) => void;
|
||||
}
|
||||
|
||||
export const RowSegmentedControl: React.FC<RowSegmentedControlProps> = ({ option, className }) => {
|
||||
export const RowSegmentedControl: React.FC<RowSegmentedControlProps> = ({
|
||||
option,
|
||||
className,
|
||||
onChange,
|
||||
}) => {
|
||||
const handleValueChange = (newVal: string) => {
|
||||
if (option.commands) {
|
||||
option.commands(newVal);
|
||||
if (onChange) {
|
||||
onChange(newVal);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ const renderRangeSetting = option => {
|
||||
maxValue={option.max}
|
||||
step={option.step}
|
||||
value={option.value}
|
||||
onChange={value => option.commands?.(value)}
|
||||
onChange={value => option.onChange?.(value)}
|
||||
allowNumberEdit={true}
|
||||
inputClassName="ml-1 w-4/5 cursor-pointer"
|
||||
/>
|
||||
@@ -70,6 +70,7 @@ const renderRadioSetting = option => {
|
||||
<RowSegmentedControl
|
||||
key={option.id}
|
||||
option={option}
|
||||
onChange={option.onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -79,7 +80,7 @@ function renderDoubleRangeSetting(option) {
|
||||
<RowDoubleRange
|
||||
key={option.id}
|
||||
values={option.value}
|
||||
onChange={option.commands}
|
||||
onChange={option.onChange}
|
||||
minValue={option.min}
|
||||
maxValue={option.max}
|
||||
step={option.step}
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { ToolboxUI, useToolbox } from '../../';
|
||||
import { useToolbar } from '@ohif/core';
|
||||
|
||||
/**
|
||||
* A toolbox is a collection of buttons and commands that they invoke, used to provide
|
||||
* custom control panels to users. This component is a generic UI component that
|
||||
* interacts with services and commands in a generic fashion. While it might
|
||||
* seem unconventional to import it from the UI and integrate it into the JSX,
|
||||
* it belongs in the UI components as there isn't anything in this component that
|
||||
* couldn't be used for a completely different type of app. It plays a crucial
|
||||
* role in enhancing the app with a toolbox by providing a way to integrate
|
||||
* and display various tools and their corresponding options
|
||||
*/
|
||||
function Toolbox({
|
||||
servicesManager,
|
||||
buttonSectionId,
|
||||
commandsManager,
|
||||
title,
|
||||
...props
|
||||
}: withAppTypes) {
|
||||
// We should move these outside of the platform/ui-next, no file here
|
||||
// should rely on the managers and services
|
||||
const { state: toolboxState, api } = useToolbox(buttonSectionId);
|
||||
const { onInteraction, toolbarButtons } = useToolbar({
|
||||
servicesManager,
|
||||
buttonSection: buttonSectionId,
|
||||
});
|
||||
|
||||
const prevButtonIdsRef = useRef('');
|
||||
const prevToolboxStateRef = useRef('');
|
||||
|
||||
useEffect(() => {
|
||||
const currentButtonIdsStr = JSON.stringify(
|
||||
toolbarButtons.map(button => {
|
||||
const { id, componentProps } = button;
|
||||
if (componentProps.items?.length) {
|
||||
return componentProps.items.map(item => `${item.id}-${item.disabled}`);
|
||||
}
|
||||
return `${id}-${componentProps.disabled}`;
|
||||
})
|
||||
);
|
||||
|
||||
const currentToolBoxStateStr = JSON.stringify(
|
||||
Object.keys(toolboxState.toolOptions).map(tool => {
|
||||
const options = toolboxState.toolOptions[tool];
|
||||
if (Array.isArray(options)) {
|
||||
return options?.map(option => `${option.id}-${option.value}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
prevButtonIdsRef.current === currentButtonIdsStr &&
|
||||
prevToolboxStateRef.current === currentToolBoxStateStr
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
prevButtonIdsRef.current = currentButtonIdsStr;
|
||||
prevToolboxStateRef.current = currentToolBoxStateStr;
|
||||
|
||||
const initializeOptionsWithEnhancements = toolbarButtons.reduce(
|
||||
(accumulator, toolbarButton) => {
|
||||
const { id: buttonId, componentProps } = toolbarButton;
|
||||
|
||||
const createEnhancedOptions = (options, parentId) => {
|
||||
const optionsToUse = Array.isArray(options) ? options : [options];
|
||||
|
||||
return optionsToUse.map(option => {
|
||||
if (typeof option.optionComponent === 'function') {
|
||||
return option;
|
||||
}
|
||||
|
||||
const value =
|
||||
toolboxState.toolOptions?.[parentId]?.find(prop => prop.id === option.id)?.value ??
|
||||
option.value;
|
||||
|
||||
const updatedOptions = toolboxState.toolOptions?.[parentId];
|
||||
|
||||
return {
|
||||
...option,
|
||||
value,
|
||||
commands: value => {
|
||||
api.handleToolOptionChange(parentId, option.id, value);
|
||||
|
||||
const { isArray } = Array;
|
||||
const cmds = isArray(option.commands) ? option.commands : [option.commands];
|
||||
|
||||
cmds.forEach(command => {
|
||||
const isString = typeof command === 'string';
|
||||
const isObject = typeof command === 'object';
|
||||
const isFunction = typeof command === 'function';
|
||||
|
||||
if (isString) {
|
||||
commandsManager.run(command, { value });
|
||||
} else if (isObject) {
|
||||
commandsManager.run({
|
||||
...command,
|
||||
commandOptions: {
|
||||
...command.commandOptions,
|
||||
...option,
|
||||
value,
|
||||
options: updatedOptions,
|
||||
},
|
||||
});
|
||||
} else if (isFunction) {
|
||||
command({ value, commandsManager, servicesManager, options: updatedOptions });
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const { items, options } = componentProps;
|
||||
|
||||
if (items?.length) {
|
||||
items.forEach(({ options, id }) => {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
accumulator[id] = createEnhancedOptions(options, id);
|
||||
});
|
||||
} else if (options?.length) {
|
||||
accumulator[buttonId] = createEnhancedOptions(options, buttonId);
|
||||
} else if (options?.optionComponent) {
|
||||
accumulator[buttonId] = options.optionComponent;
|
||||
}
|
||||
|
||||
return accumulator;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
api.initializeToolOptions(initializeOptionsWithEnhancements);
|
||||
}, [toolbarButtons, api, toolboxState, commandsManager, servicesManager]);
|
||||
|
||||
const handleToolOptionChange = (toolName, optionName, newValue) => {
|
||||
api.handleToolOptionChange(toolName, optionName, newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
api.handleToolSelect(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ToolboxUI
|
||||
{...props}
|
||||
title={title}
|
||||
toolbarButtons={toolbarButtons}
|
||||
toolboxState={toolboxState}
|
||||
handleToolSelect={id => api.handleToolSelect(id)}
|
||||
handleToolOptionChange={handleToolOptionChange}
|
||||
onInteraction={onInteraction}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Toolbox;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { PanelSection } from '../../components';
|
||||
@@ -6,52 +6,39 @@ import { ToolSettings } from '../OHIFToolSettings';
|
||||
|
||||
const ItemsPerRow = 4;
|
||||
|
||||
function usePrevious(value) {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
interface ToolbarButton {
|
||||
id: string;
|
||||
Component: React.ComponentType<{
|
||||
id: string;
|
||||
onInteraction: (details: { itemId: string }) => void;
|
||||
size: string;
|
||||
}>;
|
||||
componentProps: {
|
||||
isActive?: boolean;
|
||||
buttonSection?: string;
|
||||
options?: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Just refactoring from the toolbox component to make it more readable
|
||||
*/
|
||||
function ToolboxUI(props: withAppTypes) {
|
||||
interface ToolboxProps {
|
||||
toolbarButtons: ToolbarButton[];
|
||||
numRows: number;
|
||||
title?: string;
|
||||
useCollapsedPanel?: boolean;
|
||||
onInteraction?: (details: { itemId: string }) => void;
|
||||
activeToolOptions?: unknown;
|
||||
}
|
||||
|
||||
function ToolboxUI(props: ToolboxProps) {
|
||||
const {
|
||||
toolbarButtons,
|
||||
handleToolSelect,
|
||||
toolboxState,
|
||||
toolbarButtons = [],
|
||||
numRows,
|
||||
servicesManager,
|
||||
title,
|
||||
useCollapsedPanel = true,
|
||||
onInteraction,
|
||||
activeToolOptions,
|
||||
} = props;
|
||||
|
||||
const { activeTool, toolOptions, selectedEvent } = toolboxState;
|
||||
const activeToolOptions = toolOptions?.[activeTool];
|
||||
|
||||
const prevToolOptions = usePrevious(activeToolOptions);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeToolOptions || Array.isArray(activeToolOptions) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeToolOptions.forEach((option, index) => {
|
||||
const prevOption = prevToolOptions ? prevToolOptions[index] : undefined;
|
||||
if (!prevOption || option.value !== prevOption.value || selectedEvent) {
|
||||
const isOptionValid = option.condition
|
||||
? option.condition({ options: activeToolOptions })
|
||||
: true;
|
||||
if (isOptionValid) {
|
||||
const { commands } = option;
|
||||
commands(option.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [activeToolOptions, selectedEvent]);
|
||||
|
||||
const render = () => {
|
||||
return (
|
||||
<>
|
||||
@@ -67,12 +54,9 @@ function ToolboxUI(props: withAppTypes) {
|
||||
|
||||
const toolClasses = `ml-1 ${isLastRow ? '' : 'mb-2'}`;
|
||||
|
||||
const onInteraction = ({ itemId, id, commands }) => {
|
||||
const idToUse = itemId || id;
|
||||
handleToolSelect(idToUse);
|
||||
props.onInteraction({
|
||||
const handleInteraction = ({ itemId }: { itemId: string }) => {
|
||||
onInteraction?.({
|
||||
itemId,
|
||||
commands,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -85,10 +69,8 @@ function ToolboxUI(props: withAppTypes) {
|
||||
>
|
||||
<Component
|
||||
{...componentProps}
|
||||
{...props}
|
||||
id={id}
|
||||
servicesManager={servicesManager}
|
||||
onInteraction={onInteraction}
|
||||
onInteraction={handleInteraction}
|
||||
size="toolbox"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import { ToolboxUI } from './ToolboxUI';
|
||||
import Toolbox from './Toolbox';
|
||||
export { ToolboxUI, Toolbox };
|
||||
export * from './ToolboxUI';
|
||||
@@ -41,7 +41,10 @@ const StudyItem = ({
|
||||
<div className="flex min-w-0 flex-col items-start text-[13px]">
|
||||
<Tooltip>
|
||||
<TooltipContent>{date}</TooltipContent>
|
||||
<TooltipTrigger className="w-full">
|
||||
<TooltipTrigger
|
||||
className="w-full"
|
||||
asChild
|
||||
>
|
||||
<div className="h-[18px] w-full max-w-[160px] overflow-hidden truncate whitespace-nowrap text-left text-white">
|
||||
{date}
|
||||
</div>
|
||||
@@ -49,7 +52,10 @@ const StudyItem = ({
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipContent>{description}</TooltipContent>
|
||||
<TooltipTrigger className="w-full">
|
||||
<TooltipTrigger
|
||||
className="w-full"
|
||||
asChild
|
||||
>
|
||||
<div className="text-muted-foreground h-[18px] w-full overflow-hidden truncate whitespace-nowrap text-left">
|
||||
{description}
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,7 @@ function ToolButton(props: ToolButtonProps) {
|
||||
className,
|
||||
} = props;
|
||||
|
||||
const { buttonSizeClass, iconSizeClass } = sizeClasses[size];
|
||||
const { buttonSizeClass, iconSizeClass } = sizeClasses[size] || sizeClasses.default;
|
||||
|
||||
const buttonClasses = cn(
|
||||
baseClasses,
|
||||
|
||||
@@ -52,7 +52,7 @@ import { ThumbnailList } from './ThumbnailList';
|
||||
import { PanelSection } from './PanelSection';
|
||||
import { DisplaySetMessageListTooltip } from './DisplaySetMessageListTooltip';
|
||||
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './Tooltip';
|
||||
import { ToolboxUI, Toolbox } from './OHIFToolbox';
|
||||
import { ToolboxUI } from './OHIFToolbox';
|
||||
import Numeric from './Numeric';
|
||||
import { InputDialog, PresetDialog } from './OHIFDialogs';
|
||||
import { AboutModal, ImageModal, UserPreferencesModal } from './OHIFModals';
|
||||
@@ -105,6 +105,7 @@ import {
|
||||
ToolButtonListItem,
|
||||
ToolButtonListDivider,
|
||||
} from './ToolButton';
|
||||
import { ToolSettings } from './OHIFToolSettings';
|
||||
|
||||
export {
|
||||
Numeric,
|
||||
@@ -173,7 +174,6 @@ export {
|
||||
PanelSection,
|
||||
DisplaySetMessageListTooltip,
|
||||
ToolboxUI,
|
||||
Toolbox,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
@@ -236,4 +236,5 @@ export {
|
||||
ImageModal,
|
||||
UserPreferencesModal,
|
||||
FooterAction,
|
||||
ToolSettings,
|
||||
};
|
||||
Reference in new issue
Block a user