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": { "dependencies": {
"@babel/runtime": "^7.20.13", "@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.1.17", "@cornerstonejs/adapters": "^2.2.1",
"@cornerstonejs/core": "^2.1.17", "@cornerstonejs/core": "^2.2.1",
"@kitware/vtk.js": "32.1.0", "@kitware/vtk.js": "32.1.0",
"react-color": "^2.19.3" "react-color": "^2.19.3"
} }

View File

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

View File

@ -4,7 +4,6 @@ import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } f
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
import promptHydrateSEG from '../utils/promptHydrateSEG'; import promptHydrateSEG from '../utils/promptHydrateSEG';
import _getStatusComponent from './_getStatusComponent'; import _getStatusComponent from './_getStatusComponent';
import { useSegmentationPresentationStore } from '@ohif/extension-cornerstone';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums'; import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup'; const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
@ -251,6 +250,14 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
if (toolGroup) { if (toolGroup) {
return; 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 // always start fresh for this viewport since it is special type of viewport
// that should only show one segmentation at a time. // that should only show one segmentation at a time.
segmentationService.clearSegmentationRepresentations(viewportId); segmentationService.clearSegmentationRepresentations(viewportId);

View File

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

View File

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

View File

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

View File

@ -111,16 +111,14 @@ function commandsModule({
commandsManager.run(options, optionsToUse); commandsManager.run(options, optionsToUse);
}, },
updateStoredSegmentationPresentation: ({ displaySet, type }) => { updateStoredSegmentationPresentation: ({ displaySet, type }) => {
const { setSegmentationPresentation } = useSegmentationPresentationStore.getState(); const { addSegmentationPresentationItem } = useSegmentationPresentationStore.getState();
const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID; const referencedDisplaySetInstanceUID = displaySet.referencedDisplaySetInstanceUID;
setSegmentationPresentation(referencedDisplaySetInstanceUID, [ addSegmentationPresentationItem(referencedDisplaySetInstanceUID, {
{ segmentationId: displaySet.displaySetInstanceUID,
segmentationId: displaySet.displaySetInstanceUID, hydrated: true,
hydrated: true, type,
type, });
},
]);
}, },
updateStoredPositionPresentation: ({ viewportId, displaySetInstanceUID }) => { updateStoredPositionPresentation: ({ viewportId, displaySetInstanceUID }) => {
const presentations = cornerstoneViewportService.getPresentations(viewportId); const presentations = cornerstoneViewportService.getPresentations(viewportId);
@ -1043,7 +1041,7 @@ function commandsModule({
* @param props.key - The style key to set * @param props.key - The style key to set
* @param props.value - The style value * @param props.value - The style value
*/ */
setSegmentationStyleCommand: ({ segmentationId, type, key, value }) => { setSegmentationStyleCommand: ({ type, key, value }) => {
const { segmentationService } = servicesManager.services; const { segmentationService } = servicesManager.services;
segmentationService.setStyle({ type }, { [key]: value }); segmentationService.setStyle({ type }, { [key]: value });
}, },
@ -1150,12 +1148,17 @@ function commandsModule({
const segment = segmentation.segments[segmentIndex]; const segment = segmentation.segments[segmentIndex];
const { label } = segment; const { label } = segment;
callInputDialog(uiDialogService, label, (label, actionId) => { const callback = (label, actionId) => {
if (label === '') { if (label === '') {
return; return;
} }
segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); 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; const { label } = segmentation;
callInputDialog(uiDialogService, label, (label, actionId) => { const callback = (label, actionId) => {
if (label === '') { if (label === '') {
return; return;
} }
segmentationService.addOrUpdateSegmentation({ segmentationId, label }); 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({ export default function PanelSegmentation({
servicesManager, servicesManager,
commandsManager, commandsManager,
extensionManager,
configuration,
children, children,
}: withAppTypes) { }: withAppTypes) {
const { customizationService, viewportGridService, displaySetService } = servicesManager.services; const { customizationService, viewportGridService, displaySetService } = servicesManager.services;
@ -91,23 +89,23 @@ export default function PanelSegmentation({
commandsManager.run('deleteSegmentation', { segmentationId }); commandsManager.run('deleteSegmentation', { segmentationId });
}, },
setFillAlpha: (type, value) => { setFillAlpha: ({ type }, value) => {
commandsManager.run('setFillAlpha', { type, value }); commandsManager.run('setFillAlpha', { type, value });
}, },
setOutlineWidth: (type, value) => { setOutlineWidth: ({ type }, value) => {
commandsManager.run('setOutlineWidth', { type, value }); commandsManager.run('setOutlineWidth', { type, value });
}, },
setRenderFill: (type, value) => { setRenderFill: ({ type }, value) => {
commandsManager.run('setRenderFill', { type, value }); commandsManager.run('setRenderFill', { type, value });
}, },
setRenderOutline: (type, value) => { setRenderOutline: ({ type }, value) => {
commandsManager.run('setRenderOutline', { type, value }); commandsManager.run('setRenderOutline', { type, value });
}, },
setFillAlphaInactive: (type, value) => { setFillAlphaInactive: ({ type }, value) => {
commandsManager.run('setFillAlphaInactive', { type, value }); commandsManager.run('setFillAlphaInactive', { type, value });
}, },
@ -152,6 +150,14 @@ export default function PanelSegmentation({
const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => { const exportOptions = segmentationsWithRepresentations.map(({ segmentation }) => {
const { representationData, segmentationId } = segmentation; const { representationData, segmentationId } = segmentation;
const { Labelmap } = representationData; const { Labelmap } = representationData;
if (!Labelmap) {
return {
segmentationId,
isExportable: true,
};
}
const referencedImageIds = Labelmap.referencedImageIds; const referencedImageIds = Labelmap.referencedImageIds;
const firstImageId = referencedImageIds[0]; const firstImageId = referencedImageIds[0];

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import { devtools } from 'zustand/middleware'; import { devtools } from 'zustand/middleware';
import { SegmentationPresentation } from '../types/Presentation'; import { SegmentationPresentation, SegmentationPresentationItem } from '../types/Presentation';
import { JOIN_STR } from './presentationUtils'; import { JOIN_STR } from './presentationUtils';
import { getViewportOrientationFromImageOrientationPatient } from '../utils/getViewportOrientationFromImageOrientationPatient'; import { getViewportOrientationFromImageOrientationPatient } from '../utils/getViewportOrientationFromImageOrientationPatient';
@ -62,10 +62,9 @@ type SegmentationPresentationStore = {
* @param segmentationPresentation - The `SegmentationPresentation` to add. * @param segmentationPresentation - The `SegmentationPresentation` to add.
* @param servicesManager - The services manager instance. * @param servicesManager - The services manager instance.
*/ */
addSegmentationPresentation: ( addSegmentationPresentationItem: (
presentationId: string, presentationId: string,
segmentationPresentation: SegmentationPresentation, segmentationPresentationItem: SegmentationPresentationItem
{ servicesManager }: { servicesManager: AppTypes.ServicesManager }
) => void; ) => void;
/** /**
@ -183,33 +182,52 @@ const createSegmentationPresentationStore = set => ({
set({ segmentationPresentationStore: {} }, false, 'clearSegmentationPresentationStore'), 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, presentationId: string,
segmentationPresentation: SegmentationPresentation, segmentationPresentationItem: SegmentationPresentationItem
{ servicesManager }: { servicesManager: AppTypes.ServicesManager }
) => ) =>
set( set(
state => ({ state => ({
segmentationPresentationStore: { segmentationPresentationStore: {
...state.segmentationPresentationStore, ...state.segmentationPresentationStore,
[presentationId]: segmentationPresentation, [presentationId]: [
...(state.segmentationPresentationStore[presentationId] || []),
segmentationPresentationItem,
],
}, },
}), }),
false, 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( set(
state => ({ state => ({
segmentationPresentationStore: { segmentationPresentationStore: {
...state.segmentationPresentationStore, ...state.segmentationPresentationStore,
[presentationId]: value, [presentationId]: values,
}, },
}), }),
false, false,

View File

@ -4,9 +4,9 @@ import getDisplayUnit from './getDisplayUnit';
export const getStatisticDisplayString = (numbers, unit, key) => { export const getStatisticDisplayString = (numbers, unit, key) => {
if (Array.isArray(numbers) && numbers.length > 0) { if (Array.isArray(numbers) && numbers.length > 0) {
const results = numbers.map(number => utils.roundNumber(number, 2)); 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); 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. // 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. // 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 [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp);
const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side)); const [tabs, setTabs] = useState(tabsProp ?? panelService.getPanels(side));
const handleSidePanelOpen = useCallback(() => {
setHasBeenOpened(true);
}, []);
const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => { const handleActiveTabIndexChange = useCallback(({ activeTabIndex }) => {
setActiveTabIndex(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 */ /** update the active tab index from outside */
@ -61,7 +71,7 @@ const SidePanelWithServices = ({
const activatePanelSubscription = panelService.subscribe( const activatePanelSubscription = panelService.subscribe(
panelService.EVENTS.ACTIVATE_PANEL, panelService.EVENTS.ACTIVATE_PANEL,
(activatePanelEvent: Types.ActivatePanelEvent) => { (activatePanelEvent: Types.ActivatePanelEvent) => {
if (!hasBeenOpened || activatePanelEvent.forceActive) { if (sidePanelOpen || activatePanelEvent.forceActive) {
const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId);
if (tabIndex !== -1) { if (tabIndex !== -1) {
setActiveTabIndex(tabIndex); setActiveTabIndex(tabIndex);
@ -73,7 +83,7 @@ const SidePanelWithServices = ({
return () => { return () => {
activatePanelSubscription.unsubscribe(); activatePanelSubscription.unsubscribe();
}; };
}, [tabs, hasBeenOpened, panelService]); }, [tabs, sidePanelOpen, panelService]);
return ( return (
<SidePanel <SidePanel
@ -81,10 +91,11 @@ const SidePanelWithServices = ({
side={side} side={side}
tabs={tabs} tabs={tabs}
activeTabIndex={activeTabIndex} activeTabIndex={activeTabIndex}
onOpen={handleSidePanelOpen} onOpen={handleOpen}
onClose={handleClose}
onActiveTabIndexChange={handleActiveTabIndexChange} onActiveTabIndexChange={handleActiveTabIndexChange}
expandedWidth={expandedWidth} expandedWidth={expandedWidth}
></SidePanel> />
); );
}; };

View File

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

View File

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

View File

@ -140,7 +140,13 @@ const toolbarButtons: Button[] = [
context: 'CORNERSTONE', 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', icon: 'tool-capture',
label: 'Capture', label: 'Capture',
commands: 'showDownloadViewportModal', 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 * Lifecycle hooks
*/ */
onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) { onModeEnter: function ({ servicesManager, extensionManager, commandsManager }: withAppTypes) {
const { measurementService, toolbarService, toolGroupService, customizationService } = const {
servicesManager.services; measurementService,
toolbarService,
toolGroupService,
customizationService,
panelService,
segmentationService,
} = servicesManager.services;
measurementService.clearMeasurements(); measurementService.clearMeasurements();
@ -110,25 +116,23 @@ function modeFactory({ modeConfiguration }) {
// // ActivatePanel event trigger for when a segmentation or measurement is added. // // 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. // // Do not force activation so as to respect the state the user may have left the UI in.
// _activatePanelTriggersSubscriptions = [ _activatePanelTriggersSubscriptions = [
// ...panelService.addActivatePanelTriggers(dicomSeg.panel, [ ...panelService.addActivatePanelTriggers(cornerstone.segmentation, [
// { {
// sourcePubSubService: segmentationService, sourcePubSubService: segmentationService,
// sourceEvents: [ sourceEvents: [segmentationService.EVENTS.SEGMENTATION_ADDED],
// segmentationService.EVENTS.SEGMENTATION_PIXEL_DATA_CREATED, },
// ], ]),
// }, ...panelService.addActivatePanelTriggers(tracked.measurements, [
// ]), {
// ...panelService.addActivatePanelTriggers(tracked.measurements, [ sourcePubSubService: measurementService,
// { sourceEvents: [
// sourcePubSubService: measurementService, measurementService.EVENTS.MEASUREMENT_ADDED,
// sourceEvents: [ measurementService.EVENTS.RAW_MEASUREMENT_ADDED,
// measurementService.EVENTS.MEASUREMENT_ADDED, ],
// measurementService.EVENTS.RAW_MEASUREMENT_ADDED, },
// ], ]),
// }, ];
// ]),
// ];
}, },
onModeExit: ({ servicesManager }: withAppTypes) => { onModeExit: ({ servicesManager }: withAppTypes) => {
const { const {

View File

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

View File

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

View File

@ -114,7 +114,13 @@ const toolbarButtons = [
icon: 'tool-capture', icon: 'tool-capture',
label: 'Capture', label: 'Capture',
commands: 'showDownloadViewportModal', 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: { props: {
leftPanels: [ohif.leftPanel], leftPanels: [ohif.leftPanel],
rightPanels: [cornerstone.panelTool], rightPanels: [cornerstone.panelTool],
leftPanelClosed: true, // leftPanelClosed: true,
viewports: [ viewports: [
{ {
namespace: cornerstone.viewport, namespace: cornerstone.viewport,

View File

@ -69,7 +69,13 @@ const toolbarButtons: Button[] = [
icon: 'tool-capture', icon: 'tool-capture',
label: 'Capture', label: 'Capture',
commands: 'showDownloadViewportModal', 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-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@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", "@emotion/serialize": "^1.1.3",
"@ohif/core": "3.9.0-beta.109", "@ohif/core": "3.9.0-beta.109",
"@ohif/extension-cornerstone": "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-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4", "@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5", "@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", "@ohif/ui": "3.9.0-beta.109",
"cornerstone-math": "0.1.9", "cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21" "dicom-parser": "^1.8.21"

View File

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

View File

@ -9,15 +9,20 @@ const generateAcceptHeader = (
} }
let acceptHeader = ['multipart/related']; let acceptHeader = ['multipart/related'];
let hasTransferSyntax = false;
if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) { if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) {
const type = typeForTS[requestTransferSyntaxUID]; const type = typeForTS[requestTransferSyntaxUID];
acceptHeader.push('type=' + type); acceptHeader.push('type=' + type);
acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID); acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID);
hasTransferSyntax = true;
} else { } else {
acceptHeader.push('type=application/octet-stream'); acceptHeader.push('type=application/octet-stream');
} }
acceptHeader.push('transfer-syntax=*'); if (!hasTransferSyntax) {
acceptHeader.push('transfer-syntax=*');
}
if (!omitQuotationForMultipartRequest) { if (!omitQuotationForMultipartRequest) {
//need to add quotation for each mime type of each accept entry //need to add quotation for each mime type of each accept entry
acceptHeader = acceptHeader.map(mime => { 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 ## DicomUpload
The DICOM upload functionality in OHIF has been refactored to use the standard customization service pattern. Now you don't need to put 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": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-shepherd": "6.1.1", "react-shepherd": "6.1.1",
"sanitize-html": "^2.13.1",
"shepherd.js": "13.0.3", "shepherd.js": "13.0.3",
"sonner": "^1.5.0", "sonner": "^1.5.0",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",

View File

@ -8,7 +8,6 @@ import {
} from '../../components/DropdownMenu'; } from '../../components/DropdownMenu';
import { Icons } from '../../components/Icons/Icons'; import { Icons } from '../../components/Icons/Icons';
import { Tooltip, TooltipTrigger, TooltipContent } from '../../components/Tooltip/Tooltip'; 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. * 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}`} key={`empty-${indent}`}
className="h-2" className="h-2"
></div> ></div>
); // Empty row );
} }
const cleanText = decodeHTML(text); const cleanText = decodeHTML(text);
return ( return (
@ -142,11 +141,11 @@ const DataRow: React.FC<DataRowProps> = ({
{indentation} {indentation}
{cleanText.includes(':') ? ( {cleanText.includes(':') ? (
<> <>
<span className="font-medium">{sanitizeHtml(cleanText.split(':')[0])}:</span> <span className="font-medium">{cleanText.split(':')[0]}:</span>
{sanitizeHtml(cleanText.split(':')[1])} {cleanText.split(':')[1]}
</> </>
) : ( ) : (
<span className="font-medium">{sanitizeHtml(cleanText)}</span> <span className="font-medium">{cleanText}</span>
)} )}
</div> </div>
); );

View File

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

View File

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

View File

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

View File

@ -2580,10 +2580,10 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cornerstonejs/adapters@^2.1.17": "@cornerstonejs/adapters@^2.2.1":
version "2.1.17" version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.1.17.tgz#509e62d500c733ab8babde7a3918548d59d6fa08" resolved "https://registry.yarnpkg.com/@cornerstonejs/adapters/-/adapters-2.2.1.tgz#9bed051a7fd9ca5ad5af3ccb97fbb79862feadc1"
integrity sha512-+eptnnP+C+qKfRh0t0ZoZqQvxtDtwXZzMWk5IzNs/APRPh4XTt/VGh4hWt9UPUwxldG67k/bndjxilQkFUVNkA== integrity sha512-7eIZTN3PO4s95vaLrAAD018bNKsD1w7l0FL+bLv+eqebNHrhBMQQbAfEWuQCueE+tYd1mVDCpNWVDMKMMabHmQ==
dependencies: dependencies:
"@babel/runtime-corejs2" "^7.17.8" "@babel/runtime-corejs2" "^7.17.8"
buffer "^6.0.3" buffer "^6.0.3"
@ -2616,32 +2616,33 @@
resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0" resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjph/-/codec-openjph-2.4.5.tgz#8690b61a86fa53ef38a70eee9d665a79229517c0"
integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg== integrity sha512-MZCUy8VG0VG5Nl1l58+g+kH3LujAzLYTfJqkwpWI2gjSrGXnP6lgwyy4GmPRZWVoS40/B1LDNALK905cNWm+sg==
"@cornerstonejs/core@^2.1.17": "@cornerstonejs/core@^2.2.1":
version "2.1.17" version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.1.17.tgz#3cf4e7b586fccc0cd430947933f69bb08aaa6839" resolved "https://registry.yarnpkg.com/@cornerstonejs/core/-/core-2.2.1.tgz#37cc052d40fffcc093f0f773051787c610d07386"
integrity sha512-rtjRzRlEWk95Q4xlWkUaLitcX9g6bPsGXLASDm2nds8Dd5aEXCnPyEdBo6ow/rhvanohd7I1eC2fK2Wz7WWbJw== integrity sha512-5ZZcpa7hU+E45jM4MvEJrIFA+WHFaLWIVh9FO1HksJXtkIeBurdYr6R95aOfk7ujkkft2rsgQA29a5Jl8E6aGg==
dependencies: dependencies:
"@kitware/vtk.js" "32.1.0" "@kitware/vtk.js" "32.1.0"
comlink "^4.4.1" comlink "^4.4.1"
gl-matrix "^3.4.3" gl-matrix "^3.4.3"
"@cornerstonejs/dicom-image-loader@^2.1.17": "@cornerstonejs/dicom-image-loader@^2.2.1":
version "2.1.17" version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.1.17.tgz#5429a10b921ed5586f093f51a363524e45cf6ecb" resolved "https://registry.yarnpkg.com/@cornerstonejs/dicom-image-loader/-/dicom-image-loader-2.2.1.tgz#01f38061cd128c56b00fb5a3c47488880fb0f6bf"
integrity sha512-UIH4th4+F+a2s/myACpEmMjkPWFljAFbXeoj2Qo0JpL02FYYYGQuO7Ov+gjK/gv90twAh7q8Qg2+yNbHkC9pzA== integrity sha512-yFO6K4vFZqG1dkUBpVSsQhOxS6nkb7uKEETwobhroAs8lIIfQ8141R8u2DfxBTIY7o9uJuYpabYzlhbyUYr4eA==
dependencies: dependencies:
"@cornerstonejs/codec-charls" "^1.2.3" "@cornerstonejs/codec-charls" "^1.2.3"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2" "@cornerstonejs/codec-libjpeg-turbo-8bit" "^1.2.2"
"@cornerstonejs/codec-openjpeg" "^1.2.2" "@cornerstonejs/codec-openjpeg" "^1.2.2"
"@cornerstonejs/codec-openjph" "^2.4.5" "@cornerstonejs/codec-openjph" "^2.4.5"
comlink "^4.4.1" comlink "^4.4.1"
jpeg-lossless-decoder-js "^2.1.0"
pako "^2.0.4" pako "^2.0.4"
uuid "^9.0.0" uuid "^9.0.0"
"@cornerstonejs/tools@^2.1.17": "@cornerstonejs/tools@^2.2.1":
version "2.1.17" version "2.2.1"
resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.1.17.tgz#74e7a18845da779f5bc513902de6dbfe1880add0" resolved "https://registry.yarnpkg.com/@cornerstonejs/tools/-/tools-2.2.1.tgz#a6d34c39c330927c485e4af8b4c99026226249e4"
integrity sha512-vHdCVKV0kxYgvbS2ff9lDjd6AOKy6hP2Ohp96ac0enbZBNakUwDhqxWp94SezKqK7vn19fxRdSnPRLIchrqvJA== integrity sha512-cxTZAr+zA1atAJjspA4u4yD+mMZsV+H/fqf2Oa8yn3EZU5ONmFF37QbwLiXd3Zo47GYfEgTPDKQwFWG7wIAsxg==
dependencies: dependencies:
"@types/offscreencanvas" "2019.7.3" "@types/offscreencanvas" "2019.7.3"
comlink "^4.4.1" comlink "^4.4.1"
@ -5490,6 +5491,11 @@
estree-walker "^2.0.2" estree-walker "^2.0.2"
picomatch "^2.3.1" 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": "@rspack/binding-darwin-arm64@1.0.14":
version "1.0.14" version "1.0.14"
resolved "https://registry.yarnpkg.com/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.0.14.tgz#b9d99fb71e047f5300a851614f89cb9d7168db3e" 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" domutils "^2.5.2"
entities "^2.0.0" entities "^2.0.0"
htmlparser2@^8.0.0, htmlparser2@^8.0.1: htmlparser2@^8.0.1:
version "8.0.2" version "8.0.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21"
integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==
@ -15488,6 +15494,13 @@ joi@^17.9.2:
"@sideway/formula" "^3.0.1" "@sideway/formula" "^3.0.1"
"@sideway/pinpoint" "^2.0.0" "@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: "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -18990,11 +19003,6 @@ parse-path@^7.0.0:
dependencies: dependencies:
protocols "^2.0.0" 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: parse-url@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" 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" picocolors "^1.0.0"
source-map-js "^1.2.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" version "8.4.47"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== 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" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 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: sax@^1.2.4:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"