This commit is contained in:
mario
2025-03-07 13:47:44 +07:00
commit c4efec5a14
3358 changed files with 303774 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui';
function LegacyLayoutSelectorWithServices({
servicesManager,
rows = 3,
columns = 3,
onLayoutChange = () => {},
...props
}) {
const { toolbarService } = servicesManager.services;
const onSelection = useCallback(
props => {
toolbarService.recordInteraction({
interactionType: 'action',
commands: [
{
commandName: 'setViewportGridLayout',
commandOptions: { ...props },
context: 'DEFAULT',
},
],
});
},
[toolbarService]
);
return (
<LayoutSelector
{...props}
onSelection={onSelection}
/>
);
}
function LayoutSelector({ rows, columns, className, onSelection, ...rest }) {
const [isOpen, setIsOpen] = useState(false);
const closeOnOutsideClick = () => {
if (isOpen) {
setIsOpen(false);
}
};
useEffect(() => {
window.addEventListener('click', closeOnOutsideClick);
return () => {
window.removeEventListener('click', closeOnOutsideClick);
};
}, [isOpen]);
const onInteractionHandler = () => setIsOpen(!isOpen);
const DropdownContent = isOpen ? OHIFLayoutSelector : null;
return (
<ToolbarButton
id="Layout"
label="Grid Layout"
icon="tool-layout"
onInteraction={onInteractionHandler}
className={className}
rounded={rest.rounded}
dropdownContent={
DropdownContent !== null && (
<DropdownContent
rows={rows}
columns={columns}
onSelection={onSelection}
/>
)
}
isActive={isOpen}
type="toggle"
/>
);
}
LayoutSelector.propTypes = {
rows: PropTypes.number,
columns: PropTypes.number,
onLayoutChange: PropTypes.func,
servicesManager: PropTypes.object.isRequired,
};
export default LegacyLayoutSelectorWithServices;

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { Tooltip } from '@ohif/ui';
import classnames from 'classnames';
import { useToolbar } from '@ohif/core';
export function Toolbar({ servicesManager, buttonSection = 'primary' }) {
const { toolbarButtons, onInteraction } = useToolbar({
servicesManager,
buttonSection,
});
if (!toolbarButtons.length) {
return null;
}
return (
<>
{toolbarButtons.map(toolDef => {
if (!toolDef) {
return null;
}
const { id, Component, componentProps } = toolDef;
const tool = (
<Component
key={id}
id={id}
onInteraction={onInteraction}
servicesManager={servicesManager}
{...componentProps}
/>
);
return <div key={id}>{tool}</div>;
})}
</>
);
}

View File

@@ -0,0 +1,36 @@
import { ToolbarButton, ButtonGroup } from '@ohif/ui';
import React, { useCallback } from 'react';
function ToolbarButtonGroupWithServices({ groupId, items, onInteraction, size }) {
const getSplitButtonItems = useCallback(
items =>
items.map((item, index) => (
<ToolbarButton
key={item.id}
icon={item.icon}
label={item.label}
disabled={item.disabled}
className={item.className}
disabledText={item.disabledText}
id={item.id}
size={size}
onClick={() => {
onInteraction({
groupId,
itemId: item.id,
commands: item.commands,
});
}}
// Note: this is necessary since tooltip will add
// default styles to the tooltip container which
// we don't want for groups
toolTipClassName=""
/>
)),
[onInteraction, groupId]
);
return <ButtonGroup>{getSplitButtonItems(items)}</ButtonGroup>;
}
export default ToolbarButtonGroupWithServices;

View File

@@ -0,0 +1,5 @@
import React from 'react';
export default function ToolbarDivider() {
return <span className="border-common-dark mx-2 h-8 w-4 self-center border-l" />;
}

View File

@@ -0,0 +1,246 @@
import React, { useEffect, useState, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import { LayoutSelector as OHIFLayoutSelector, ToolbarButton, LayoutPreset } from '@ohif/ui';
const defaultCommonPresets = [
{
icon: 'layout-common-1x1',
commandOptions: {
numRows: 1,
numCols: 1,
},
},
{
icon: 'layout-common-1x2',
commandOptions: {
numRows: 1,
numCols: 2,
},
},
{
icon: 'layout-common-2x2',
commandOptions: {
numRows: 2,
numCols: 2,
},
},
{
icon: 'layout-common-2x3',
commandOptions: {
numRows: 2,
numCols: 3,
},
},
];
const _areSelectorsValid = (hp, displaySets, hangingProtocolService) => {
if (!hp.displaySetSelectors || Object.values(hp.displaySetSelectors).length === 0) {
return true;
}
return hangingProtocolService.areRequiredSelectorsValid(
Object.values(hp.displaySetSelectors),
displaySets[0]
);
};
const generateAdvancedPresets = ({ servicesManager }: withAppTypes) => {
const { hangingProtocolService, viewportGridService, displaySetService } =
servicesManager.services;
const hangingProtocols = Array.from(hangingProtocolService.protocols.values());
const viewportId = viewportGridService.getActiveViewportId();
if (!viewportId) {
return [];
}
const displaySetInsaneUIDs = viewportGridService.getDisplaySetsUIDsForViewport(viewportId);
if (!displaySetInsaneUIDs) {
return [];
}
const displaySets = displaySetInsaneUIDs.map(uid => displaySetService.getDisplaySetByUID(uid));
return hangingProtocols
.map(hp => {
if (!hp.isPreset) {
return null;
}
const areValid = _areSelectorsValid(hp, displaySets, hangingProtocolService);
return {
icon: hp.icon,
title: hp.name,
commandOptions: {
protocolId: hp.id,
},
disabled: !areValid,
};
})
.filter(preset => preset !== null);
};
function ToolbarLayoutSelectorWithServices({
commandsManager,
servicesManager,
...props
}: withAppTypes) {
const [isDisabled, setIsDisabled] = useState(false);
const handleMouseEnter = () => {
setIsDisabled(false);
};
const onSelection = useCallback(props => {
commandsManager.run({
commandName: 'setViewportGridLayout',
commandOptions: { ...props },
});
setIsDisabled(true);
}, []);
const onSelectionPreset = useCallback(props => {
commandsManager.run({
commandName: 'setHangingProtocol',
commandOptions: { ...props },
});
setIsDisabled(true);
}, []);
return (
<div onMouseEnter={handleMouseEnter}>
<LayoutSelector
{...props}
onSelection={onSelection}
onSelectionPreset={onSelectionPreset}
servicesManager={servicesManager}
tooltipDisabled={isDisabled}
/>
</div>
);
}
function LayoutSelector({
rows = 3,
columns = 4,
onLayoutChange = () => {},
className,
onSelection,
onSelectionPreset,
servicesManager,
tooltipDisabled,
...rest
}: withAppTypes) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef(null);
const { customizationService } = servicesManager.services;
const commonPresets = customizationService.get('commonPresets') || defaultCommonPresets;
const advancedPresets =
customizationService.get('advancedPresets') || generateAdvancedPresets({ servicesManager });
const closeOnOutsideClick = event => {
if (isOpen && dropdownRef.current) {
setIsOpen(false);
}
};
useEffect(() => {
if (!isOpen) {
return;
}
setTimeout(() => {
window.addEventListener('click', closeOnOutsideClick);
}, 0);
return () => {
window.removeEventListener('click', closeOnOutsideClick);
dropdownRef.current = null;
};
}, [isOpen]);
const onInteractionHandler = () => {
setIsOpen(!isOpen);
};
const DropdownContent = isOpen ? OHIFLayoutSelector : null;
return (
<ToolbarButton
id="Layout"
label="Layout"
icon="tool-layout"
onInteraction={onInteractionHandler}
className={className}
rounded={rest.rounded}
disableToolTip={tooltipDisabled}
dropdownContent={
DropdownContent !== null && (
<div
className="flex"
ref={dropdownRef}
>
<div className="bg-secondary-dark flex flex-col gap-2.5 p-2">
<div className="text-aqua-pale text-xs">Common</div>
<div className="flex gap-4">
{commonPresets.map((preset, index) => (
<LayoutPreset
key={index}
classNames="hover:bg-primary-dark group p-1 cursor-pointer"
icon={preset.icon}
commandOptions={preset.commandOptions}
onSelection={onSelection}
/>
))}
</div>
<div className="h-[2px] bg-black"></div>
<div className="text-aqua-pale text-xs">Advanced</div>
<div className="flex flex-col gap-2.5">
{advancedPresets.map((preset, index) => (
<LayoutPreset
key={index + commonPresets.length}
classNames="hover:bg-primary-dark group flex gap-2 p-1 cursor-pointer"
icon={preset.icon}
title={preset.title}
disabled={preset.disabled}
commandOptions={preset.commandOptions}
onSelection={onSelectionPreset}
/>
))}
</div>
</div>
<div className="bg-primary-dark flex flex-col gap-2.5 border-l-2 border-solid border-black p-2">
<div className="text-aqua-pale text-xs">Custom</div>
<DropdownContent
rows={rows}
columns={columns}
onSelection={onSelection}
/>
<p className="text-aqua-pale text-xs leading-tight">
Hover to select <br></br>rows and columns <br></br> Click to apply
</p>
</div>
</div>
)
}
isActive={isOpen}
type="toggle"
/>
);
}
LayoutSelector.propTypes = {
rows: PropTypes.number,
columns: PropTypes.number,
onLayoutChange: PropTypes.func,
servicesManager: PropTypes.object.isRequired,
};
export default ToolbarLayoutSelectorWithServices;

View File

@@ -0,0 +1,89 @@
import { SplitButton, ToolbarButton } from '@ohif/ui';
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
function ToolbarSplitButtonWithServices({
groupId,
primary,
secondary,
items,
renderer,
onInteraction,
servicesManager,
}: withAppTypes) {
const { toolbarService } = servicesManager?.services;
/* Bubbles up individual item clicks */
const getSplitButtonItems = useCallback(
items =>
items.map((item, index) => ({
...item,
index,
onClick: () => {
onInteraction({
groupId,
itemId: item.id,
commands: item.commands,
});
},
})),
[groupId, onInteraction]
);
const PrimaryButtonComponent =
toolbarService?.getButtonComponentForUIType(primary.uiType) ?? ToolbarButton;
const listItemRenderer = renderer;
return (
<SplitButton
primary={primary}
secondary={secondary}
items={getSplitButtonItems(items)}
groupId={groupId}
renderer={listItemRenderer}
onInteraction={onInteraction}
Component={props => (
<PrimaryButtonComponent
{...props}
servicesManager={servicesManager}
/>
)}
/>
);
}
ToolbarSplitButtonWithServices.propTypes = {
groupId: PropTypes.string,
primary: PropTypes.shape({
id: PropTypes.string.isRequired,
uiType: PropTypes.string,
}),
secondary: PropTypes.shape({
id: PropTypes.string,
icon: PropTypes.string.isRequired,
label: PropTypes.string,
tooltip: PropTypes.string.isRequired,
disabled: PropTypes.bool,
className: PropTypes.string,
}),
items: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
icon: PropTypes.string,
label: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
className: PropTypes.string,
})
),
renderer: PropTypes.func,
onInteraction: PropTypes.func.isRequired,
servicesManager: PropTypes.shape({
services: PropTypes.shape({
toolbarService: PropTypes.object,
}),
}),
};
export default ToolbarSplitButtonWithServices;