diff --git a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
index 78df25a66..7e901804b 100644
--- a/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
+++ b/extensions/cornerstone-dynamic-volume/src/actions/updateSegmentationsChartDisplaySet.ts
@@ -262,7 +262,6 @@ function _getInstanceFromSegmentations(segmentations, { servicesManager }) {
}
function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void {
- debugger;
const { segmentationService } = servicesManager.services;
const segmentations = segmentationService.getSegmentations();
const { seriesMetadata, instance } =
diff --git a/extensions/cornerstone/src/commandsModule.ts b/extensions/cornerstone/src/commandsModule.ts
index 2e4558431..f6282f97b 100644
--- a/extensions/cornerstone/src/commandsModule.ts
+++ b/extensions/cornerstone/src/commandsModule.ts
@@ -6,6 +6,8 @@ import {
Types as CoreTypes,
cache,
BaseVolumeViewport,
+ triggerEvent,
+ eventTarget,
} from '@cornerstonejs/core';
import {
ToolGroupManager,
@@ -13,6 +15,7 @@ import {
utilities as cstUtils,
ReferenceLinesTool,
annotation,
+ Types as ToolTypes,
} from '@cornerstonejs/tools';
import * as cornerstoneTools from '@cornerstonejs/tools';
import * as labelmapInterpolation from '@cornerstonejs/labelmap-interpolation';
@@ -35,6 +38,7 @@ import { toolNames } from './initCornerstoneTools';
import CornerstoneViewportDownloadForm from './utils/CornerstoneViewportDownloadForm';
import { updateSegmentBidirectionalStats } from './utils/updateSegmentationStats';
import { generateSegmentationCSVReport } from './utils/generateSegmentationCSVReport';
+
const { DefaultHistoryMemo } = csUtils.HistoryMemo;
const toggleSyncFunctions = {
imageSlice: toggleImageSliceSync,
@@ -436,25 +440,26 @@ function commandsModule({
},
removeMeasurement: ({ uid }) => {
- measurementService.remove(uid);
+ if (Array.isArray(uid)) {
+ measurementService.removeMany(uid);
+ } else {
+ measurementService.remove(uid);
+ }
},
toggleLockMeasurement: ({ uid }) => {
measurementService.toggleLockMeasurement(uid);
},
- toggleVisibilityMeasurement: ({ uid }) => {
- measurementService.toggleVisibilityMeasurement(uid);
- },
-
- /**
- * Clear the measurements
- */
- clearMeasurements: options => {
- const { measurementFilter } = options;
- measurementService.clearMeasurements(
- measurementFilter ? measurementFilter.bind(options) : null
- );
+ toggleVisibilityMeasurement: ({ uid, items, visibility }) => {
+ if (visibility === undefined && items?.length) {
+ visibility = !items[0].isVisible;
+ }
+ if (Array.isArray(uid)) {
+ measurementService.toggleVisibilityMeasurementMany(uid, visibility);
+ } else {
+ measurementService.toggleVisibilityMeasurement(uid, visibility);
+ }
},
/**
@@ -514,16 +519,18 @@ function commandsModule({
viewportGridService.setActiveViewportId(viewportId);
},
- arrowTextCallback: ({ callback }) => {
+ arrowTextCallback: async ({ callback }) => {
const labelConfig = customizationService.getCustomization('measurementLabels');
const renderContent = customizationService.getCustomization('ui.labellingComponent');
- callInputDialogAutoComplete({
+ const value = await callInputDialogAutoComplete({
uiDialogService,
labelConfig,
renderContent,
});
+ callback?.(value);
},
+
toggleCine: () => {
const { viewports } = viewportGridService.getState();
const { isCineEnabled } = cineService.getState();
@@ -879,7 +886,7 @@ function commandsModule({
const options = { imageIndex: jumpIndex };
csUtils.jumpToSlice(viewport.element, options);
},
- scroll: ({ direction }) => {
+ scroll: (options: ToolTypes.ScrollOptions) => {
const enabledElement = _getActiveViewportEnabledElement();
if (!enabledElement) {
@@ -887,7 +894,6 @@ function commandsModule({
}
const { viewport } = enabledElement;
- const options = { delta: direction };
csUtils.scroll(viewport, options);
},
@@ -1490,6 +1496,7 @@ function commandsModule({
viewportGridService.getActiveViewportId()
);
},
+
deleteActiveAnnotation: () => {
const activeAnnotationsUID = cornerstoneTools.annotation.selection.getAnnotationsSelected();
activeAnnotationsUID.forEach(activeAnnotationUID => {
@@ -1577,9 +1584,6 @@ function commandsModule({
updateMeasurement: {
commandFn: actions.updateMeasurement,
},
- clearMeasurements: {
- commandFn: actions.clearMeasurements,
- },
jumpToMeasurement: {
commandFn: actions.jumpToMeasurement,
},
diff --git a/extensions/cornerstone/src/components/AccordionGroup/AccordionGroup.tsx b/extensions/cornerstone/src/components/AccordionGroup/AccordionGroup.tsx
new file mode 100644
index 000000000..2091089a8
--- /dev/null
+++ b/extensions/cornerstone/src/components/AccordionGroup/AccordionGroup.tsx
@@ -0,0 +1,192 @@
+import React from 'react';
+import { useSystem } from '@ohif/core';
+import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@ohif/ui-next';
+import { ChevronDownIcon } from '@radix-ui/react-icons';
+
+export type AccordionGrouping = {
+ [name: string]: unknown;
+};
+
+export type AccordionGroupProps = {
+ grouping: AccordionGrouping;
+};
+
+/**
+ * Searches for the required type from the provided allChildren list and
+ * renders them.
+ */
+export const CloneChildren = props => {
+ const { group, allChildren, children, childType, defaultTypes } = props;
+
+ const subType = group?.subType;
+
+ for (const child of allChildren) {
+ if (subType && child.props?.subType !== subType) {
+ continue;
+ }
+ if (childType && child.type === childType) {
+ return React.cloneElement(child, { ...props, children: child.props.children });
+ }
+ if (defaultTypes?.indexOf(child.type) === -1) {
+ return childType({ ...props, children: child });
+ }
+ }
+
+ if (!children) {
+ throw new Error(`No children defined for ${props.name} CloneChildren in group ${group?.name}`);
+ }
+ return React.cloneElement(children, props);
+};
+
+/** Used to exclude defaults */
+const DEFAULT_TYPES = [GroupAccordion, Content, Trigger];
+
+/**
+ * An AccordionGroup is a component that splits a set of items into different
+ * groups according to a set of grouping rules. It then puts the groups
+ * into a set of accordion folds selected from the body of the accordion group,
+ * looking for matching trigger/content sections according to the type definition
+ * in the group with first one found being used.
+ *
+ * This design allows for easy customization of the component by declaring grouping
+ * functions with default grouping setups and then only overriding the specific
+ * children needing to be changed. See the PanelMeasurement for some example
+ * possibilities of how to modify the default grouping, or the test-extension
+ * measurements panel for a practical, working example.
+ */
+export function AccordionGroup(props) {
+ const { grouping, items, children, sourceChildren, type } = props;
+ const childProps = useSystem();
+ let defaultValue = props.defaultValue;
+ const groups = grouping.groupingFunction(items, grouping, childProps);
+
+ if (!defaultValue) {
+ const defaultGroup = groups.values().find(group => group.isSelected);
+ defaultValue = defaultGroup?.key || defaultGroup?.title;
+ }
+
+ const valueArr =
+ (Array.isArray(defaultValue) && defaultValue) || (defaultValue && [defaultValue]) || [];
+ const sourceChildrenArr = sourceChildren ? React.Children.toArray(sourceChildren) : [];
+ const childrenArr = children ? React.Children.toArray(children) : [];
+ const allChildren = sourceChildrenArr.concat(childrenArr);
+
+ return (
+