feat(toolbar): new Toolbar to enable reactive state synchronization (#3983)

This commit is contained in:
Alireza authored and GitHub committed 2024-03-27 16:01:32 -04:00
1 parent 79d5c36bda
commit 566b25a544
155 files changed
+6311 -6577

No files matched your search

@@ -0,0 +1,109 @@
import React, { useEffect, useRef } from 'react';
import { useToolbar } from '@ohif/core';
import { ToolboxUI } from './';
import { useToolbox } from '../../contextProviders';
/**
* 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 }) {
const { state: toolboxState, api } = useToolbox(buttonSectionId);
const { onInteraction, toolbarButtons } = useToolbar({
servicesManager,
buttonSection: buttonSectionId,
});
const prevButtonIdsRef = 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}`;
})
);
if (prevButtonIdsRef.current === currentButtonIdsStr) {
return;
}
prevButtonIdsRef.current = currentButtonIdsStr;
const initializeOptionsWithEnhancements = toolbarButtons.reduce(
(accumulator, toolbarButton) => {
const { id: buttonId, componentProps } = toolbarButton;
const createEnhancedOptions = (options, parentId) =>
options.map(option => {
return {
...option,
value:
toolboxState.toolOptions?.[parentId]?.find(prop => prop.id === option.id)?.value ??
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';
if (isString) {
commandsManager.run(command, { value });
} else if (isObject) {
commandsManager.run({
...command,
commandOptions: { ...command.commandOptions, ...option, value },
});
}
});
},
};
});
if (componentProps.items?.length) {
componentProps.items.forEach(item => {
accumulator[item.id] = createEnhancedOptions(item.options, item.id);
});
} else if (componentProps.options?.length) {
accumulator[buttonId] = createEnhancedOptions(componentProps.options, buttonId);
}
return accumulator;
},
{}
);
api.initializeToolOptions(initializeOptionsWithEnhancements);
}, [toolbarButtons, api]);
const handleToolOptionChange = (toolName, optionName, newValue) => {
api.handleToolOptionChange(toolName, optionName, newValue);
};
return (
<ToolboxUI
{...props}
title={title}
toolbarButtons={toolbarButtons}
activeToolOptions={toolboxState.toolOptions?.[toolboxState.activeTool]}
handleToolSelect={id => api.handleToolSelect(id)}
handleToolOptionChange={handleToolOptionChange}
onInteraction={onInteraction}
/>
);
}
export default Toolbox;
@@ -0,0 +1,92 @@
import React from 'react';
import { PanelSection, ToolSettings, Tooltip } from '../../components';
import classnames from 'classnames';
const ItemsPerRow = 4;
/**
* Just refactoring from the toolbox component to make it more readable
*/
function ToolboxUI(props) {
const {
toolbarButtons,
handleToolSelect,
activeToolOptions,
numRows,
servicesManager,
title,
useCollapsedPanel = true,
} = props;
const render = () => {
return (
<>
<div className="flex flex-col bg-black">
<div className="bg-primary-dark mt-0.5 flex flex-wrap py-2">
{toolbarButtons.map((toolDef, index) => {
if (!toolDef) {
return null;
}
const { id, Component, componentProps } = toolDef;
const isLastRow = Math.floor(index / ItemsPerRow) + 1 === numRows;
const toolClasses = `ml-1 ${isLastRow ? '' : 'mb-2'}`;
const onInteraction = ({ itemId, id, commands }) => {
handleToolSelect(itemId || id);
props.onInteraction({
itemId,
commands,
});
};
return (
<div
key={id}
className={classnames({
[toolClasses]: true,
'flex flex-col items-center justify-center': true,
})}
>
{componentProps.disabled ? (
<Tooltip
position="bottom"
content={componentProps.label}
secondaryContent={'Not available on the current viewport'}
>
<Component
{...componentProps}
{...props}
id={id}
servicesManager={servicesManager}
onInteraction={onInteraction}
size="toolbox"
/>
</Tooltip>
) : (
<Component
{...componentProps}
{...props}
id={id}
servicesManager={servicesManager}
onInteraction={onInteraction}
size="toolbox"
/>
)}
</div>
);
})}
</div>
</div>
<div className="bg-primary-dark h-auto px-2">
{activeToolOptions && <ToolSettings options={activeToolOptions} />}
</div>
</>
);
};
return useCollapsedPanel ? <PanelSection title={title}>{render()}</PanelSection> : render();
}
export { ToolboxUI };
@@ -0,0 +1,3 @@
import { ToolboxUI } from './ToolboxUI';
import Toolbox from './Toolbox';
export { ToolboxUI, Toolbox };