feat(toolbar): new Toolbar to enable reactive state synchronization (#3983)
This commit is contained in:
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 };
|
||||
Reference in new issue
Block a user