fix(bugs): Update dependencies and enhance UI components (#4478)

This commit is contained in:
Alireza 2024-11-11 14:28:34 -05:00 committed by GitHub
parent 61e3a9cb52
commit 05d41c5206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 275 additions and 147 deletions

View File

@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.1.17",
"@cornerstonejs/core": "^2.1.17",
"@cornerstonejs/adapters": "^2.2.1",
"@cornerstonejs/core": "^2.2.1",
"@kitware/vtk.js": "32.1.0",
"react-color": "^2.19.3"
}

View File

@ -46,8 +46,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.1.17",
"@cornerstonejs/core": "^2.1.17",
"@cornerstonejs/adapters": "^2.2.1",
"@cornerstonejs/core": "^2.2.1",
"@kitware/vtk.js": "32.1.0",
"react-color": "^2.19.3"
}

View File

@ -4,7 +4,6 @@ import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } f
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
import promptHydrateSEG from '../utils/promptHydrateSEG';
import _getStatusComponent from './_getStatusComponent';
import { useSegmentationPresentationStore } from '@ohif/extension-cornerstone';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
@ -251,6 +250,14 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
if (toolGroup) {
return;
}
// keep the already stored segmentationPresentation for this viewport in memory
// so that we can restore it after hydrating the SEG
commandsManager.runCommand('updateStoredSegmentationPresentation', {
displaySet: segDisplaySet,
type: SegmentationRepresentations.Labelmap,
});
// always start fresh for this viewport since it is special type of viewport
// that should only show one segmentation at a time.
segmentationService.clearSegmentationRepresentations(viewportId);

View File

@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.1.17",
"@cornerstonejs/core": "^2.1.17",
"@cornerstonejs/tools": "^2.1.17",
"@cornerstonejs/adapters": "^2.2.1",
"@cornerstonejs/core": "^2.2.1",
"@cornerstonejs/tools": "^2.2.1",
"classnames": "^2.3.2"
}
}

View File

@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^2.1.17",
"@cornerstonejs/tools": "^2.1.17",
"@cornerstonejs/core": "^2.2.1",
"@cornerstonejs/tools": "^2.2.1",
"classnames": "^2.3.2"
}
}

View File

@ -38,7 +38,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^2.1.17",
"@cornerstonejs/dicom-image-loader": "^2.2.1",
"@icr/polyseg-wasm": "^0.4.0",
"@ohif/core": "3.9.0-beta.109",
"@ohif/ui": "3.9.0-beta.109",
@ -55,9 +55,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.1.17",
"@cornerstonejs/core": "^2.1.17",
"@cornerstonejs/tools": "^2.1.17",
"@cornerstonejs/adapters": "^2.2.1",
"@cornerstonejs/core": "^2.2.1",
"@cornerstonejs/tools": "^2.2.1",
"@icr/polyseg-wasm": "^0.4.0",
"@kitware/vtk.js": "32.1.0",
"html2canvas": "^1.4.1",

View File

@ -111,16 +111,14 @@ function commandsModule({
commandsManager.run(options, optionsToUse);
},
updateStoredSegmentationPresentation: ({ displaySet, type }) => {
const { setSegmentationPresentation } = useSegmentationPresentationStore.getState();
const { addSegmentationPresentationItem } = useSegmentationPresentationStore.getState();
const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID;
setSegmentationPresentation(referencedDisplaySetInstanceUID, [
{
segmentationId: displaySet.displaySetInstanceUID,
hydrated: true,
type,
},
]);
addSegmentationPresentationItem(referencedDisplaySetInstanceUID, {
segmentationId: displaySet.displaySetInstanceUID,
hydrated: true,
type,
});
},
updateStoredPositionPresentation: ({ viewportId, displaySetInstanceUID }) => {
const presentations = cornerstoneViewportService.getPresentations(viewportId);
@ -1043,7 +1041,7 @@ function commandsModule({
* @param props.key - The style key to set
* @param props.value - The style value
*/
setSegmentationStyleCommand: ({ segmentationId, type, key, value }) => {
setSegmentationStyleCommand: ({ type, key, value }) => {
const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { [key]: value });
},
@ -1150,12 +1148,17 @@ function commandsModule({
const segment = segmentation.segments[segmentIndex];
const { label } = segment;
callInputDialog(uiDialogService, label, (label, actionId) => {
const callback = (label, actionId) => {
if (label === '') {
return;
}
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label);
};
callInputDialog(uiDialogService, label, callback, false, {
dialogTitle: 'Edit Segment Label',
inputLabel: 'Enter new label',
});
},
@ -1169,12 +1172,17 @@ function commandsModule({
const { label } = segmentation;
callInputDialog(uiDialogService, label, (label, actionId) => {
const callback = (label, actionId) => {
if (label === '') {
return;
}
segmentationService.addOrUpdateSegmentation({ segmentationId, label });
};
callInputDialog(uiDialogService, label, callback, false, {
dialogTitle: 'Edit Segmentation Label',
inputLabel: 'Enter new label',
});
},

View File

@ -6,8 +6,6 @@ import { metaData } from '@cornerstonejs/core';
export default function PanelSegmentation({
servicesManager,
commandsManager,
extensionManager,
configuration,
children,
}: withAppTypes) {
const { customizationService, viewportGridService, displaySetService } = servicesManager.services;
@ -91,23 +89,23 @@ export default function PanelSegmentation({
commandsManager.run('deleteSegmentation', { segmentationId });
},
setFillAlpha: (type, value) => {
setFillAlpha: ({ type }, value) => {
commandsManager.run('setFillAlpha', { type, value });
},
setOutlineWidth: (type, value) => {
setOutlineWidth: ({ type }, value) => {
commandsManager.run('setOutlineWidth', { type, value });
},
setRenderFill: (type, value) => {
setRenderFill: ({ type }, value) => {
commandsManager.run('setRenderFill', { type, value });
},
setRenderOutline: (type, value) => {
setRenderOutline: ({ type }, value) => {
commandsManager.run('setRenderOutline', { type, value });
},
setFillAlphaInactive: (type, value) => {
setFillAlphaInactive: ({ type }, value) => {
commandsManager.run('setFillAlphaInactive', { type, value });
},
@ -152,6 +150,14 @@ export default function PanelSegmentation({
const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => {
const { representationData, segmentationId } = segmentation;
const { Labelmap } = representationData;
if (!Labelmap) {
return {
segmentationId,
isExportable: true,
};
}
const referencedImageIds = Labelmap.referencedImageIds;
const firstImageId = referencedImageIds[0];

View File

@ -100,10 +100,16 @@ class SegmentationService extends PubSubService {
this._segmentationIdToColorLUTIndexMap = new Map();
this.servicesManager = servicesManager;
}
public onModeEnter(): void {
this._initSegmentationService();
}
public onModeExit(): void {
this.destroy();
}
/**
* Retrieves a segmentation by its ID.
*
@ -1386,7 +1392,11 @@ class SegmentationService extends PubSubService {
const frameOfReferenceUID = viewport.getFrameOfReferenceUID();
const segImage = cache.getImage(imageIds[0]);
if (segImage?.FrameOfReferenceUID === frameOfReferenceUID) {
if (
segImage?.FrameOfReferenceUID &&
frameOfReferenceUID &&
segImage.FrameOfReferenceUID === frameOfReferenceUID
) {
const isConverted = await this.convertStackToVolumeViewport(viewport);
triggerSegmentationRepresentationModified(
viewportId,

View File

@ -763,6 +763,10 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
});
}
if (!volumesNotLoaded.length) {
return;
}
// This returns the async continuation only
return this.setVolumesForViewport(viewport, volumeInputArray, presentations);
}
@ -1114,8 +1118,8 @@ class CornerstoneViewportService extends PubSubService implements IViewportServi
const { segmentationService } = this.servicesManager.services;
segmentationPresentation.forEach((presentation: SegmentationPresentationItem) => {
const { segmentationId, type, hydrated } = presentation;
segmentationPresentation.forEach((presentationItem: SegmentationPresentationItem) => {
const { segmentationId, type, hydrated } = presentationItem;
if (hydrated) {
segmentationService.addSegmentationRepresentation(viewport.id, {

View File

@ -1,6 +1,6 @@
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import { SegmentationPresentation } from '../types/Presentation';
import { SegmentationPresentation, SegmentationPresentationItem } from '../types/Presentation';
import { JOIN_STR } from './presentationUtils';
import { getViewportOrientationFromImageOrientationPatient } from '../utils/getViewportOrientationFromImageOrientationPatient';
@ -62,10 +62,9 @@ type SegmentationPresentationStore = {
* @param segmentationPresentation - The `SegmentationPresentation` to add.
* @param servicesManager - The services manager instance.
*/
addSegmentationPresentation: (
addSegmentationPresentationItem: (
presentationId: string,
segmentationPresentation: SegmentationPresentation,
{ servicesManager }: { servicesManager: AppTypes.ServicesManager }
segmentationPresentationItem: SegmentationPresentationItem
) => void;
/**
@ -183,33 +182,52 @@ const createSegmentationPresentationStore = set => ({
set({ segmentationPresentationStore: {} }, false, 'clearSegmentationPresentationStore'),
/**
* Adds a new segmentation presentation to the store.
* Adds a new segmentation presentation item to the store.
*
* segmentationPresentationItem: {
* segmentationId: string;
* type: SegmentationRepresentations;
* hydrated: boolean | null;
* config?: unknown;
* }
*/
addSegmentationPresentation: (
addSegmentationPresentationItem: (
presentationId: string,
segmentationPresentation: SegmentationPresentation,
{ servicesManager }: { servicesManager: AppTypes.ServicesManager }
segmentationPresentationItem: SegmentationPresentationItem
) =>
set(
state => ({
segmentationPresentationStore: {
...state.segmentationPresentationStore,
[presentationId]: segmentationPresentation,
[presentationId]: [
...(state.segmentationPresentationStore[presentationId] || []),
segmentationPresentationItem,
],
},
}),
false,
'addSegmentationPresentation'
'addSegmentationPresentationItem'
),
/**
* Sets the segmentation presentation for a given presentation ID.
* Sets the segmentation presentation for a given presentation ID. A segmentation
* presentation is an array of SegmentationPresentationItem.
*
* segmentationPresentationItem: {
* segmentationId: string;
* type: SegmentationRepresentations;
* hydrated: boolean | null;
* config?: unknown;
* }
*
* segmentationPresentation: SegmentationPresentationItem[]
*/
setSegmentationPresentation: (presentationId: string, value: SegmentationPresentation) =>
setSegmentationPresentation: (presentationId: string, values: SegmentationPresentation) =>
set(
state => ({
segmentationPresentationStore: {
...state.segmentationPresentationStore,
[presentationId]: value,
[presentationId]: values,
},
}),
false,

View File

@ -4,9 +4,9 @@ import getDisplayUnit from './getDisplayUnit';
export const getStatisticDisplayString = (numbers, unit, key) => {
if (Array.isArray(numbers) && numbers.length > 0) {
const results = numbers.map(number => utils.roundNumber(number, 2));
return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${results.join(', ')} <small>${getDisplayUnit(unit)}</small>`;
return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${results.join(', ')} ${getDisplayUnit(unit)}`;
}
const result = utils.roundNumber(numbers, 2);
return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${result} <small>${getDisplayUnit(unit)}</small>`;
return `${key.charAt(0).toUpperCase() + key.slice(1)}: ${result} ${getDisplayUnit(unit)}`;
};

View File

@ -23,16 +23,26 @@ const SidePanelWithServices = ({
// Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM.
// Thus going to the Study List page and back to the viewer resets this flag for a SidePanel.
const [hasBeenOpened, setHasBeenOpened] = useState(false);
const [sidePanelOpen, setSidePanelOpen] = useState(activeTabIndexProp !== null);
const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
const handleSidePanelOpen = useCallback(() => {
setHasBeenOpened(true);
}, []);
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
setActiveTabIndex(activeTabIndex);
setSidePanelOpen(activeTabIndex !== null);
}, []);
const handleOpen = useCallback(() => {
setSidePanelOpen(true);
// If panel is being opened but no tab is active, set first tab as active
if (activeTabIndex === null && tabs.length > 0) {
setActiveTabIndex(0);
}
}, [activeTabIndex, tabs]);
const handleClose = useCallback(() => {
setSidePanelOpen(false);
setActiveTabIndex(null);
}, []);
/** update the active tab index from outside */
@ -61,7 +71,7 @@ const SidePanelWithServices = ({
const activatePanelSubscription = panelService.subscribe(
panelService.EVENTS.ACTIVATE_PANEL,
(activatePanelEvent: Types.ActivatePanelEvent) => {
if (!hasBeenOpened || activatePanelEvent.forceActive) {
if (sidePanelOpen || activatePanelEvent.forceActive) {
const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId);
if (tabIndex !== -1) {
setActiveTabIndex(tabIndex);
@ -73,7 +83,7 @@ const SidePanelWithServices = ({
return () => {
activatePanelSubscription.unsubscribe();
};
}, [tabs, hasBeenOpened, panelService]);
}, [tabs, sidePanelOpen, panelService]);
return (
<SidePanel
@ -81,10 +91,11 @@ const SidePanelWithServices = ({
side={side}
tabs={tabs}
activeTabIndex={activeTabIndex}
onOpen={handleSidePanelOpen}
onOpen={handleOpen}
onClose={handleClose}
onActiveTabIndexChange={handleActiveTabIndexChange}
expandedWidth={expandedWidth}
></SidePanel>
/>
);
};

View File

@ -32,8 +32,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "^2.1.17",
"@cornerstonejs/tools": "^2.1.17",
"@cornerstonejs/core": "^2.2.1",
"@cornerstonejs/tools": "^2.2.1",
"@ohif/core": "3.9.0-beta.109",
"@ohif/extension-cornerstone-dicom-sr": "3.9.0-beta.109",
"@ohif/extension-default": "3.9.0-beta.109",

View File

@ -98,6 +98,9 @@ function TrackedCornerstoneViewport(
useEffect(() => {
if (isTracked) {
annotation.config.style.setViewportToolStyles(viewportId, {
ReferenceLines: {
lineDash: '4,4',
},
global: {
lineDash: '',
},

View File

@ -140,7 +140,13 @@ const toolbarButtons: Button[] = [
context: 'CORNERSTONE',
},
],
evaluate: 'evaluate.action',
evaluate: [
'evaluate.action',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'wholeSlide'],
},
],
},
},
{

View File

@ -213,7 +213,13 @@ const toolbarButtons: Button[] = [
icon: 'tool-capture',
label: 'Capture',
commands: 'showDownloadViewportModal',
evaluate: 'evaluate.action',
evaluate: [
'evaluate.action',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'wholeSlide'],
},
],
},
},
{

View File

@ -85,8 +85,14 @@ function modeFactory({ modeConfiguration }) {
* Lifecycle hooks
*/
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
const { measurementService, toolbarService, toolGroupService, customizationService } =
servicesManager.services;
const {
measurementService,
toolbarService,
toolGroupService,
customizationService,
panelService,
segmentationService,
} = servicesManager.services;
measurementService.clearMeasurements();
@ -110,25 +116,23 @@ function modeFactory({ modeConfiguration }) {
// // ActivatePanel event trigger for when a segmentation or measurement is added.
// // Do not force activation so as to respect the state the user may have left the UI in.
// _activatePanelTriggersSubscriptions = [
// ...panelService.addActivatePanelTriggers(dicomSeg.panel, [
// {
// sourcePubSubService: segmentationService,
// sourceEvents: [
// segmentationService.EVENTS.SEGMENTATION_PIXEL_DATA_CREATED,
// ],
// },
// ]),
// ...panelService.addActivatePanelTriggers(tracked.measurements, [
// {
// sourcePubSubService: measurementService,
// sourceEvents: [
// measurementService.EVENTS.MEASUREMENT_ADDED,
// measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
// ],
// },
// ]),
// ];
_activatePanelTriggersSubscriptions = [
...panelService.addActivatePanelTriggers(cornerstone.segmentation, [
{
sourcePubSubService: segmentationService,
sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED],
},
]),
...panelService.addActivatePanelTriggers(tracked.measurements, [
{
sourcePubSubService: measurementService,
sourceEvents: [
measurementService.EVENTS.MEASUREMENT_ADDED,
measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
],
},
]),
];
},
onModeExit: ({ servicesManager }: withAppTypes) => {
const {

View File

@ -172,7 +172,7 @@ const toolbarButtons: Button[] = [
'evaluate.action',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video'],
unsupportedViewportTypes: ['video', 'wholeSlide'],
},
],
},

View File

@ -114,8 +114,7 @@ function modeFactory({ modeConfiguration }) {
},
],
extensions: extensionDependencies,
hangingProtocol: ['default'],
hangingProtocol: 'default',
sopClassHandlers: [
'@ohif/extension-cornerstone.sopClassHandlerModule.DicomMicroscopySopClassHandler',
'@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler',

View File

@ -114,7 +114,13 @@ const toolbarButtons = [
icon: 'tool-capture',
label: 'Capture',
commands: 'showDownloadViewportModal',
evaluate: 'evaluate.action',
evaluate: [
'evaluate.action',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'wholeSlide'],
},
],
},
},
{

View File

@ -133,7 +133,7 @@ function modeFactory({ modeConfiguration }) {
props: {
leftPanels: [ohif.leftPanel],
rightPanels: [cornerstone.panelTool],
leftPanelClosed: true,
// leftPanelClosed: true,
viewports: [
{
namespace: cornerstone.viewport,

View File

@ -69,7 +69,13 @@ const toolbarButtons: Button[] = [
icon: 'tool-capture',
label: 'Capture',
commands: 'showDownloadViewportModal',
evaluate: 'evaluate.action',
evaluate: [
'evaluate.action',
{
name: 'evaluate.viewport.supported',
unsupportedViewportTypes: ['video', 'wholeSlide'],
},
],
},
},
{

View File

@ -53,7 +53,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^2.1.17",
"@cornerstonejs/dicom-image-loader": "^2.2.1",
"@emotion/serialize": "^1.1.3",
"@ohif/core": "3.9.0-beta.109",
"@ohif/extension-cornerstone": "3.9.0-beta.109",

View File

@ -37,7 +37,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^2.1.17",
"@cornerstonejs/dicom-image-loader": "^2.2.1",
"@ohif/ui": "3.9.0-beta.109",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"

View File

@ -165,6 +165,10 @@ export default class CustomizationService extends PubSubService {
this.modeCustomizations.clear();
}
public onModeExit(): void {
this.onModeEnter();
}
public getModeCustomizations(): Map<string, Customization> {
return this.modeCustomizations;
}

View File

@ -9,15 +9,20 @@ const generateAcceptHeader = (
}
let acceptHeader = ['multipart/related'];
let hasTransferSyntax = false;
if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) {
const type = typeForTS[requestTransferSyntaxUID];
acceptHeader.push('type=' + type);
acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID);
hasTransferSyntax = true;
} else {
acceptHeader.push('type=application/octet-stream');
}
acceptHeader.push('transfer-syntax=*');
if (!hasTransferSyntax) {
acceptHeader.push('transfer-syntax=*');
}
if (!omitQuotationForMultipartRequest) {
//need to add quotation for each mime type of each accept entry
acceptHeader = acceptHeader.map(mime => {

View File

@ -178,6 +178,31 @@ Now:
---
## SidePanel auto switch if open
In `basic viewer` mode, if the side panel is open and the segmentation panel is active, adding a measurement will automatically switch to the measurement panel. This switch won't occur if the side panel is closed. To enable or disable this feature, adjust your mode configuration accordingly.
```js
panelService.addActivatePanelTriggers('your.panel.id', [
{
sourcePubSubService: segmentationService,
sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED],
},
])
panelService.addActivatePanelTriggers('your.panel.id', [
{
sourcePubSubService: measurementService,
sourceEvents: [
measurementService.EVENTS.MEASUREMENT_ADDED,
measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
],
},
])
```
---
## DicomUpload
The DICOM upload functionality in OHIF has been refactored to use the standard customization service pattern. Now you don't need to put

View File

@ -53,7 +53,6 @@
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-shepherd": "6.1.1",
"sanitize-html": "^2.13.1",
"shepherd.js": "13.0.3",
"sonner": "^1.5.0",
"tailwind-merge": "^2.3.0",

View File

@ -8,7 +8,6 @@ import {
} from '../../components/DropdownMenu';
import { Icons } from '../../components/Icons/Icons';
import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/Tooltip/Tooltip';
import sanitizeHtml from 'sanitize-html';
/**
* DataRow is a complex UI component that displays a selectable, interactive row with hierarchical data.
@ -131,7 +130,7 @@ const DataRow: React.FC<DataRowProps> = ({
key={`empty-${indent}`}
className="h-2"
></div>
); // Empty row
);
}
const cleanText = decodeHTML(text);
return (
@ -142,11 +141,11 @@ const DataRow: React.FC<DataRowProps> = ({
{indentation}
{cleanText.includes(':') ? (
<>
<span className="font-medium">{sanitizeHtml(cleanText.split(':')[0])}:</span>
{sanitizeHtml(cleanText.split(':')[1])}
<span className="font-medium">{cleanText.split(':')[0]}:</span>
{cleanText.split(':')[1]}
</>
) : (
<span className="font-medium">{sanitizeHtml(cleanText)}</span>
<span className="font-medium">{cleanText}</span>
)}
</div>
);

View File

@ -51,17 +51,19 @@ export const AddSegmentRow: React.FC<AddSegmentRowProps> = ({ children = null, s
return (
<div className="bg-primary-dark my-px flex h-7 w-full items-center justify-between rounded pl-0.5 pr-7">
{allowAddSegment ? (
<Button
size="sm"
variant="ghost"
className="pr pl-0.5"
onClick={() => onSegmentAdd(segmentationIdToUse)}
>
<Icons.Add />
Add Segment
</Button>
) : null}
<div className="flex-1">
{allowAddSegment ? (
<Button
size="sm"
variant="ghost"
className="pr pl-0.5"
onClick={() => onSegmentAdd(segmentationIdToUse)}
>
<Icons.Add />
Add Segment
</Button>
) : null}
</div>
<Button
size="icon"
variant="ghost"

View File

@ -61,7 +61,7 @@ export const SegmentationSelectorHeader: React.FC<{ children?: React.ReactNode }
info: seg.segmentation.cachedStats?.info,
}));
const allowExport = exportOptions.find(
const allowExport = exportOptions?.find(
({ segmentationId }) => segmentationId === activeSegmentation.id
)?.isExportable;

View File

@ -149,6 +149,7 @@ const SidePanel = ({
activeTabIndex: activeTabIndexProp = null,
tabs,
onOpen,
onClose,
expandedWidth = 280,
onActiveTabIndexChange,
}) => {
@ -167,9 +168,11 @@ const SidePanel = ({
setPanelOpen(panelOpen);
if (panelOpen && onOpen) {
onOpen();
} else if (onClose && !panelOpen) {
onClose();
}
},
[onOpen]
[onOpen, onClose]
);
const updateActiveTabIndex = useCallback(
@ -407,6 +410,7 @@ SidePanel.propTypes = {
),
]),
onOpen: PropTypes.func,
onClose: PropTypes.func,
onActiveTabIndexChange: PropTypes.func,
expandedWidth: PropTypes.number,
};

View File

@ -2580,10 +2580,10 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^2.1.17":
version "2.1.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.1.17.tgz#509e62d500c733ab8babde7a3918548d59d6fa08"
integrity sha512-+eptnnP+C+qKfRh0t0ZoZqQvxtDtwXZzMWk5IzNs/APRPh4XTt/VGh4hWt9UPUwxldG67k/bndjxilQkFUVNkA==
"@cornerstonejs/adapters@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.2.1.tgz#9bed051a7fd9ca5ad5af3ccb97fbb79862feadc1"
integrity sha512-7eIZTN3PO4s95vaLrAAD018bNKsD1w7l0FL+bLv+eqebNHrhBMQQbAfEWuQCueE+tYd1mVDCpNWVDMKMMabHmQ==
dependencies:
"@babel/runtime-corejs2" "^7.17.8"
buffer "^6.0.3"
@ -2616,32 +2616,33 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
"@cornerstonejs/core@^2.1.17":
version "2.1.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.1.17.tgz#3cf4e7b586fccc0cd430947933f69bb08aaa6839"
integrity sha512-rtjRzRlEWk95Q4xlWkUaLitcX9g6bPsGXLASDm2nds8Dd5aEXCnPyEdBo6ow/rhvanohd7I1eC2fK2Wz7WWbJw==
"@cornerstonejs/core@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.2.1.tgz#37cc052d40fffcc093f0f773051787c610d07386"
integrity sha512-5ZZcpa7hU+E45jM4MvEJrIFA+WHFaLWIVh9FO1HksJXtkIeBurdYr6R95aOfk7ujkkft2rsgQA29a5Jl8E6aGg==
dependencies:
"@kitware/vtk.js" "32.1.0"
comlink "^4.4.1"
gl-matrix "^3.4.3"
"@cornerstonejs/dicom-image-loader@^2.1.17":
version "2.1.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.1.17.tgz#5429a10b921ed5586f093f51a363524e45cf6ecb"
integrity sha512-UIH4th4+F+a2s/myACpEmMjkPWFljAFbXeoj2Qo0JpL02FYYYGQuO7Ov+gjK/gv90twAh7q8Qg2+yNbHkC9pzA==
"@cornerstonejs/dicom-image-loader@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.2.1.tgz#01f38061cd128c56b00fb5a3c47488880fb0f6bf"
integrity sha512-yFO6K4vFZqG1dkUBpVSsQhOxS6nkb7uKEETwobhroAs8lIIfQ8141R8u2DfxBTIY7o9uJuYpabYzlhbyUYr4eA==
dependencies:
"@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
"@cornerstonejs/codec-openjpeg" "^1.2.2"
"@cornerstonejs/codec-openjph" "^2.4.5"
comlink "^4.4.1"
jpeg-lossless-decoder-js "^2.1.0"
pako "^2.0.4"
uuid "^9.0.0"
"@cornerstonejs/tools@^2.1.17":
version "2.1.17"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.1.17.tgz#74e7a18845da779f5bc513902de6dbfe1880add0"
integrity sha512-vHdCVKV0kxYgvbS2ff9lDjd6AOKy6hP2Ohp96ac0enbZBNakUwDhqxWp94SezKqK7vn19fxRdSnPRLIchrqvJA==
"@cornerstonejs/tools@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.2.1.tgz#a6d34c39c330927c485e4af8b4c99026226249e4"
integrity sha512-cxTZAr+zA1atAJjspA4u4yD+mMZsV+H/fqf2Oa8yn3EZU5ONmFF37QbwLiXd3Zo47GYfEgTPDKQwFWG7wIAsxg==
dependencies:
"@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1"
@ -5490,6 +5491,11 @@
estree-walker "^2.0.2"
picomatch "^2.3.1"
"@rollup/rollup-linux-x64-gnu@4.13.0":
version "4.13.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz#f672f6508f090fc73f08ba40ff76c20b57424778"
integrity sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==
"@rspack/binding-darwin-arm64@1.0.14":
version "1.0.14"
resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.0.14.tgz#b9d99fb71e047f5300a851614f89cb9d7168db3e"
@ -13955,7 +13961,7 @@ htmlparser2@^6.1.0:
domutils "^2.5.2"
entities "^2.0.0"
htmlparser2@^8.0.0, htmlparser2@^8.0.1:
htmlparser2@^8.0.1:
version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@ -15488,6 +15494,13 @@ joi@^17.9.2:
"@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0"
jpeg-lossless-decoder-js@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/jpeg-lossless-decoder-js/-/jpeg-lossless-decoder-js-2.1.2.tgz#9ff0ecfcafa6acaee6097c532720bd526278aa3d"
integrity sha512-fYf/plymnuKwVw+s8gJ8O/BPw8y8OLaN11MBvEcT05kuy3m45o7BEC4J0JbxMNUYyO+MdK/I/jq0q1gkVYZm2Q==
optionalDependencies:
"@rollup/rollup-linux-x64-gnu" "4.13.0"
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -18990,11 +19003,6 @@ parse-path@^7.0.0:
dependencies:
protocols "^2.0.0"
parse-srcset@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==
parse-url@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d"
@ -20145,7 +20153,7 @@ postcss@^8.2.1, postcss@^8.3.5, postcss@^8.4.18, postcss@^8.4.23, postcss@^8.4.3
picocolors "^1.0.0"
source-map-js "^1.2.0"
postcss@^8.3.11, postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.38, postcss@^8.4.47:
postcss@^8.4.21, postcss@^8.4.24, postcss@^8.4.26, postcss@^8.4.38, postcss@^8.4.47:
version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
@ -21860,18 +21868,6 @@ safe-regex-test@^1.0.3:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sanitize-html@^2.13.1:
version "2.13.1"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.1.tgz#b4639b0a09574ab62b1b353cb99b1b87af742834"
integrity sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==
dependencies:
deepmerge "^4.2.2"
escape-string-regexp "^4.0.0"
htmlparser2 "^8.0.0"
is-plain-object "^5.0.0"
parse-srcset "^1.0.2"
postcss "^8.3.11"
sax@^1.2.4:
version "1.4.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"