diff --git a/commit.txt b/commit.txt index 5fc676cc174..ba37da7e0b8 100644 --- a/commit.txt +++ b/commit.txt @@ -1 +1 @@ -b55518e08ec9c9ab23e74aa4987927d8d75ae909 \ No newline at end of file +5302e5b62be22eb503d4fe067b435f7934284c39 \ No newline at end of file diff --git a/extensions/cornerstone-dicom-rt/package.json b/extensions/cornerstone-dicom-rt/package.json index 767e49276bb..7001fdc7acb 100644 --- a/extensions/cornerstone-dicom-rt/package.json +++ b/extensions/cornerstone-dicom-rt/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-rt", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "DICOM RT read workflow", "author": "OHIF", "license": "MIT", @@ -31,10 +31,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/i18n": "3.7.0-beta.47", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index 3ab6693091b..314aa1793ad 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-seg", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "DICOM SEG read workflow", "author": "OHIF", "license": "MIT", @@ -31,10 +31,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/i18n": "3.7.0-beta.47", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 3b8d3b91086..eac0e31809e 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-sr", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension for an SR Cornerstone Viewport", "author": "OHIF", "license": "MIT", @@ -32,10 +32,10 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-measurement-tracking": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-measurement-tracking": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index d1d650996a4..bd54691b0ca 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", @@ -37,8 +37,8 @@ "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", "@cornerstonejs/dicom-image-loader": "^1.9.3", - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "dcmjs": "^0.29.6", "dicom-parser": "^1.8.21", "hammerjs": "^2.0.8", diff --git a/extensions/default/package.json b/extensions/default/package.json index 2c166f1127d..24ed0f91c18 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-default", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Common/default features and functionality for basic image viewing", "author": "OHIF Core Team", "license": "MIT", @@ -30,8 +30,8 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/i18n": "3.7.0-beta.47", "dcmjs": "^0.29.5", "dicomweb-client": "^0.10.2", "prop-types": "^15.6.2", diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx index 5ad698850f2..2e4a41999cb 100644 --- a/extensions/default/src/Panels/PanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx @@ -317,6 +317,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { numInstances: ds.numImageFrames, countIcon: ds.countIcon, StudyInstanceUID: ds.StudyInstanceUID, + messages: ds.messages, componentType, imageSrc, dragData: { diff --git a/extensions/default/src/getDisplaySetMessages.ts b/extensions/default/src/getDisplaySetMessages.ts new file mode 100644 index 00000000000..18710cd5513 --- /dev/null +++ b/extensions/default/src/getDisplaySetMessages.ts @@ -0,0 +1,50 @@ +import sortInstancesByPosition from '@ohif/core/src/utils/sortInstancesByPosition'; +import { constructableModalities } from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; +import checkMultiFrame from './utils/validations/checkMultiframe'; +import checkSingleFrames from './utils/validations/checkSingleFrames'; +/** + * Checks if a series is reconstructable to a 3D volume. + * + * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects. + */ +export default function getDisplaySetMessages( + instances: Array, + isReconstructable: boolean +): DisplaySetMessageList { + const messages = new DisplaySetMessageList(); + if (!instances.length) { + messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES); + } + + const firstInstance = instances[0]; + // Due to current requirements, LOCALIZER series doesn't have any messages + if (firstInstance.ImageType.includes('LOCALIZER')) { + return messages; + } + + const Modality = firstInstance.Modality; + if (!constructableModalities.includes(Modality)) { + return messages; + } + + const isMultiframe = firstInstance.NumberOfFrames > 1; + // Can't reconstruct if all instances don't have the ImagePositionPatient. + if ( + !isMultiframe && + !instances.every(instance => instance.ImagePositionPatient) + ) { + messages.addMessage(DisplaySetMessage.CODES.NO_POSITION_INFORMATION); + } + + const sortedInstances = sortInstancesByPosition(instances); + + isMultiframe + ? checkMultiFrame(sortedInstances[0], messages) + : checkSingleFrames(sortedInstances, messages); + + if (!isReconstructable) { + messages.addMessage(DisplaySetMessage.CODES.NOT_RECONSTRUCTABLE); + } + return messages; +} diff --git a/extensions/default/src/getSopClassHandlerModule.js b/extensions/default/src/getSopClassHandlerModule.js index df1900051e8..51aebb1ecf2 100644 --- a/extensions/default/src/getSopClassHandlerModule.js +++ b/extensions/default/src/getSopClassHandlerModule.js @@ -3,6 +3,7 @@ import sopClassDictionary from '@ohif/core/src/utils/sopClassDictionary'; import ImageSet from '@ohif/core/src/classes/ImageSet'; import isDisplaySetReconstructable from '@ohif/core/src/utils/isDisplaySetReconstructable'; import { id } from './id'; +import getDisplaySetMessages from './getDisplaySetMessages'; const sopClassHandlerName = 'stack'; @@ -18,8 +19,9 @@ const makeDisplaySet = instances => { value: isReconstructable, averageSpacingBetweenFrames, } = isDisplaySetReconstructable(instances); - // set appropriate attributes to image set... + const messages = getDisplaySetMessages(instances, isReconstructable); + imageSet.setAttributes({ displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID SeriesDate: instance.SeriesDate, @@ -36,6 +38,7 @@ const makeDisplaySet = instances => { numImageFrames: instances.length, SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, isReconstructable, + messages, averageSpacingBetweenFrames: averageSpacingBetweenFrames || null, }); diff --git a/extensions/default/src/utils/calculateScanAxisNormal.ts b/extensions/default/src/utils/calculateScanAxisNormal.ts new file mode 100644 index 00000000000..fe278f4b2b3 --- /dev/null +++ b/extensions/default/src/utils/calculateScanAxisNormal.ts @@ -0,0 +1,20 @@ +import { vec3 } from 'gl-matrix'; + +/** + * Calculates the scanAxisNormal based on a image orientation vector extract from a frame + * @param {*} imageOrientation + * @returns + */ +export default function calculateScanAxisNormal(imageOrientation) { + const rowCosineVec = vec3.fromValues( + imageOrientation[0], + imageOrientation[1], + imageOrientation[2] + ); + const colCosineVec = vec3.fromValues( + imageOrientation[3], + imageOrientation[4], + imageOrientation[5] + ); + return vec3.cross(vec3.create(), rowCosineVec, colCosineVec); +} diff --git a/extensions/default/src/utils/validations/areAllImageComponentsEqual.ts b/extensions/default/src/utils/validations/areAllImageComponentsEqual.ts new file mode 100644 index 00000000000..69c7c76d448 --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageComponentsEqual.ts @@ -0,0 +1,26 @@ +import toNumber from '@ohif/core/src/utils/toNumber'; + +/** + * Check if all voxels in series images has same number of components (samplesPerPixel) + * @param {*} instances + * @returns + */ +export default function areAllImageComponentsEqual( + instances: Array +): boolean { + if (!instances?.length) { + return false; + } + const firstImage = instances[0]; + const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel); + + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const { SamplesPerPixel } = instance; + + if (SamplesPerPixel !== firstImageSamplesPerPixel) { + return false; + } + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts b/extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts new file mode 100644 index 00000000000..b8fe7c17915 --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts @@ -0,0 +1,27 @@ +import toNumber from '@ohif/core/src/utils/toNumber'; + +/** + * Check if the frames in a series has different dimensions + * @param {*} instances + * @returns + */ +export default function areAllImageDimensionsEqual( + instances: Array +): boolean { + if (!instances?.length) { + return false; + } + const firstImage = instances[0]; + const firstImageRows = toNumber(firstImage.Rows); + const firstImageColumns = toNumber(firstImage.Columns); + + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const { Rows, Columns } = instance; + + if (Rows !== firstImageRows || Columns !== firstImageColumns) { + return false; + } + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts b/extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts new file mode 100644 index 00000000000..2e0af78a298 --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts @@ -0,0 +1,31 @@ +import toNumber from '@ohif/core/src/utils/toNumber'; +import { _isSameOrientation } from '@ohif/core/src/utils/isDisplaySetReconstructable'; + +/** + * Check is the series has frames with different orientations + * @param {*} instances + * @returns + */ +export default function areAllImageOrientationsEqual( + instances: Array +): boolean { + if (!instances?.length) { + return false; + } + const firstImage = instances[0]; + const firstImageOrientationPatient = toNumber( + firstImage.ImageOrientationPatient + ); + + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const imageOrientationPatient = toNumber(instance.ImageOrientationPatient); + + if ( + !_isSameOrientation(imageOrientationPatient, firstImageOrientationPatient) + ) { + return false; + } + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImagePositionsEqual.ts b/extensions/default/src/utils/validations/areAllImagePositionsEqual.ts new file mode 100644 index 00000000000..55df6cb5dee --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImagePositionsEqual.ts @@ -0,0 +1,76 @@ +import { vec3 } from 'gl-matrix'; +import toNumber from '@ohif/core/src/utils/toNumber'; +import { _getPerpendicularDistance } from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import calculateScanAxisNormal from '../calculateScanAxisNormal'; + +/** + * Checks if there is a position shift between consecutive frames + * @param {*} previousPosition + * @param {*} actualPosition + * @param {*} scanAxisNormal + * @param {*} averageSpacingBetweenFrames + * @returns + */ +function _checkSeriesPositionShift( + previousPosition, + actualPosition, + scanAxisNormal, + averageSpacingBetweenFrames +) { + // predicted position should be the previous position added by the multiplication + // of the scanAxisNormal and the average spacing between frames + const predictedPosition = vec3.scaleAndAdd( + vec3.create(), + previousPosition, + scanAxisNormal, + averageSpacingBetweenFrames + ); + return ( + vec3.distance(actualPosition, predictedPosition) > + averageSpacingBetweenFrames + ); +} + +/** + * Checks if a series has position shifts between consecutive frames + * @param {*} instances + * @returns + */ +export default function areAllImagePositionsEqual( + instances: Array +): boolean { + if (!instances?.length) { + return false; + } + const firstImageOrientationPatient = toNumber( + instances[0].ImageOrientationPatient + ); + const scanAxisNormal = calculateScanAxisNormal(firstImageOrientationPatient); + const firstImagePositionPatient = toNumber(instances[0].ImagePositionPatient); + const lastIpp = toNumber( + instances[instances.length - 1].ImagePositionPatient + ); + + const averageSpacingBetweenFrames = + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / + (instances.length - 1); + + let previousImagePositionPatient = firstImagePositionPatient; + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const imagePositionPatient = toNumber(instance.ImagePositionPatient); + + if ( + _checkSeriesPositionShift( + previousImagePositionPatient, + imagePositionPatient, + scanAxisNormal, + averageSpacingBetweenFrames + ) + ) { + return false; + } + previousImagePositionPatient = imagePositionPatient; + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImageSpacingEqual.ts b/extensions/default/src/utils/validations/areAllImageSpacingEqual.ts new file mode 100644 index 00000000000..254c258fea3 --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageSpacingEqual.ts @@ -0,0 +1,67 @@ +import { + _getPerpendicularDistance, + _getSpacingIssue, + reconstructionIssues, +} from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import { DisplaySetMessage } from '@ohif/core'; +import toNumber from '@ohif/core/src/utils/toNumber'; +import { DisplaySetMessageList } from '@ohif/core'; + +/** + * Checks if series has spacing issues + * @param {*} instances + * @param {*} warnings + */ +export default function areAllImageSpacingEqual( + instances: Array, + messages: DisplaySetMessageList +): void { + if (!instances?.length) { + return; + } + const firstImagePositionPatient = toNumber(instances[0].ImagePositionPatient); + const lastIpp = toNumber( + instances[instances.length - 1].ImagePositionPatient + ); + + const averageSpacingBetweenFrames = + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / + (instances.length - 1); + + let previousImagePositionPatient = firstImagePositionPatient; + + const issuesFound = []; + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const imagePositionPatient = toNumber(instance.ImagePositionPatient); + + const spacingBetweenFrames = _getPerpendicularDistance( + imagePositionPatient, + previousImagePositionPatient + ); + + const spacingIssue = _getSpacingIssue( + spacingBetweenFrames, + averageSpacingBetweenFrames + ); + + if (spacingIssue) { + const issue = spacingIssue.issue; + + // avoid multiple warning of the same thing + if (!issuesFound.includes(issue)) { + issuesFound.push(issue); + if (issue === reconstructionIssues.MISSING_FRAMES) { + messages.addMessage(DisplaySetMessage.CODES.MISSING_FRAMES); + } else if (issue === reconstructionIssues.IRREGULAR_SPACING) { + messages.addMessage(DisplaySetMessage.CODES.IRREGULAR_SPACING); + } + } + // we just want to find issues not how many + if (issuesFound.length > 1) { + break; + } + } + previousImagePositionPatient = imagePositionPatient; + } +} diff --git a/extensions/default/src/utils/validations/checkMultiframe.ts b/extensions/default/src/utils/validations/checkMultiframe.ts new file mode 100644 index 00000000000..60c71b4a682 --- /dev/null +++ b/extensions/default/src/utils/validations/checkMultiframe.ts @@ -0,0 +1,32 @@ +import { + hasPixelMeasurements, + hasOrientation, + hasPosition, +} from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; + +/** + * Check various multi frame issues. It calls OHIF core functions + * @param {*} multiFrameInstance + * @param {*} warnings + */ +export default function checkMultiFrame( + multiFrameInstance, + messages: DisplaySetMessageList +): void { + if (!hasPixelMeasurements(multiFrameInstance)) { + messages.addMessage( + DisplaySetMessage.CODES.MULTIFRAME_NO_PIXEL_MEASUREMENTS + ); + } + + if (!hasOrientation(multiFrameInstance)) { + messages.addMessage(DisplaySetMessage.CODES.MULTIFRAME_NO_ORIENTATION); + } + + if (!hasPosition(multiFrameInstance)) { + messages.addMessage( + DisplaySetMessage.CODES.MULTIFRAME_NO_POSITION_INFORMATION + ); + } +} diff --git a/extensions/default/src/utils/validations/checkSingleFrames.ts b/extensions/default/src/utils/validations/checkSingleFrames.ts new file mode 100644 index 00000000000..fe3f1e472f2 --- /dev/null +++ b/extensions/default/src/utils/validations/checkSingleFrames.ts @@ -0,0 +1,37 @@ +import areAllImageDimensionsEqual from './areAllImageDimensionsEqual'; +import areAllImageComponentsEqual from './areAllImageComponentsEqual'; +import areAllImageOrientationsEqual from './areAllImageOrientationsEqual'; +import areAllImagePositionsEqual from './areAllImagePositionsEqual'; +import areAllImageSpacingEqual from './areAllImageSpacingEqual'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; + +/** + * Runs various checks in a single frame series + * @param {*} instances + * @param {*} warnings + */ +export default function checkSingleFrames( + instances: Array, + messages: DisplaySetMessageList +): void { + if (instances.length > 2) { + if (!areAllImageDimensionsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_DIMENSIONS); + } + + if (!areAllImageComponentsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_COMPONENTS); + } + + if (!areAllImageOrientationsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_ORIENTATIONS); + } + + if (!areAllImagePositionsEqual(instances)) { + messages.addMessage( + DisplaySetMessage.CODES.INCONSISTENT_POSITION_INFORMATION + ); + } + areAllImageSpacingEqual(instances, messages); + } +} diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json index 9e8915abb59..a3adde78794 100644 --- a/extensions/dicom-microscopy/package.json +++ b/extensions/dicom-microscopy/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-microscopy", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension for DICOM microscopy", "author": "Bill Wallace, md-prog", "license": "MIT", @@ -28,10 +28,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/i18n": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json index f6a89f57f36..a17bcabbab7 100644 --- a/extensions/dicom-pdf/package.json +++ b/extensions/dicom-pdf/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-pdf", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension for PDF display", "author": "OHIF", "license": "MIT", @@ -28,8 +28,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json index 021018ca1bd..d113f7af1c2 100644 --- a/extensions/dicom-video/package.json +++ b/extensions/dicom-video/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-video", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension for video display", "author": "OHIF", "license": "MIT", @@ -28,8 +28,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", diff --git a/extensions/measurement-tracking/package.json b/extensions/measurement-tracking/package.json index d910d76f004..22b642c8769 100644 --- a/extensions/measurement-tracking/package.json +++ b/extensions/measurement-tracking/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-measurement-tracking", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Tracking features and functionality for basic image viewing", "author": "OHIF Core Team", "license": "MIT", @@ -32,9 +32,9 @@ "peerDependencies": { "@cornerstonejs/core": "^1.9.3", "@cornerstonejs/tools": "^1.9.3", - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "classnames": "^2.3.2", "dcmjs": "^0.29.5", "lodash.debounce": "^4.17.21", @@ -46,7 +46,7 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/ui": "3.7.0-beta.47", "@xstate/react": "^3.2.2", "xstate": "^4.10.0" } diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx index f31f3894760..960bcd573ac 100644 --- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx +++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx @@ -449,6 +449,7 @@ function _mapDisplaySets( seriesDate: formatDate(ds.SeriesDate), numInstances: ds.numImageFrames, countIcon: ds.countIcon, + messages: ds.messages, StudyInstanceUID: ds.StudyInstanceUID, componentType, imageSrc, diff --git a/extensions/test-extension/package.json b/extensions/test-extension/package.json index 004e493fd08..df5c48a5a1f 100644 --- a/extensions/test-extension/package.json +++ b/extensions/test-extension/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-test", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension used inside e2e testing", "author": "OHIF", "license": "MIT", @@ -28,8 +28,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "dcmjs": "0.29.4", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", diff --git a/extensions/tmtv/package.json b/extensions/tmtv/package.json index 9ab832c5271..cbd11ff6fde 100644 --- a/extensions/tmtv/package.json +++ b/extensions/tmtv/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-tmtv", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF extension for Total Metabolic Tumor Volume", "author": "OHIF", "license": "MIT", @@ -28,8 +28,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "dcmjs": "^0.29.5", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", diff --git a/lerna.json b/lerna.json index 3653514511e..bce77a477d9 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "packages": ["extensions/*", "platform/*", "modes/*"], "npmClient": "yarn", "useWorkspaces": true diff --git a/modes/basic-dev-mode/package.json b/modes/basic-dev-mode/package.json index 876b39b958e..9cfb233a903 100644 --- a/modes/basic-dev-mode/package.json +++ b/modes/basic-dev-mode/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-basic-dev-mode", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Basic OHIF Viewer Using Cornerstone", "author": "OHIF", "license": "MIT", @@ -29,12 +29,12 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/extension-dicom-pdf": "3.7.0-beta.46", - "@ohif/extension-dicom-video": "3.7.0-beta.46" + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/extension-dicom-pdf": "3.7.0-beta.47", + "@ohif/extension-dicom-video": "3.7.0-beta.47" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/basic-test-mode/package.json b/modes/basic-test-mode/package.json index e4e7d7f8d96..2f156df8470 100644 --- a/modes/basic-test-mode/package.json +++ b/modes/basic-test-mode/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-test", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Basic mode for testing", "author": "OHIF", "license": "MIT", @@ -32,14 +32,14 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/extension-dicom-pdf": "3.7.0-beta.46", - "@ohif/extension-dicom-video": "3.7.0-beta.46", - "@ohif/extension-measurement-tracking": "3.7.0-beta.46", - "@ohif/extension-test": "3.7.0-beta.46" + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/extension-dicom-pdf": "3.7.0-beta.47", + "@ohif/extension-dicom-video": "3.7.0-beta.47", + "@ohif/extension-measurement-tracking": "3.7.0-beta.47", + "@ohif/extension-test": "3.7.0-beta.47" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/longitudinal/package.json b/modes/longitudinal/package.json index f657069b1c3..6b9b63ca76e 100644 --- a/modes/longitudinal/package.json +++ b/modes/longitudinal/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-longitudinal", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Longitudinal Workflow", "author": "OHIF", "license": "MIT", @@ -32,15 +32,15 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/extension-dicom-pdf": "3.7.0-beta.46", - "@ohif/extension-dicom-video": "3.7.0-beta.46", - "@ohif/extension-measurement-tracking": "3.7.0-beta.46" + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/extension-dicom-pdf": "3.7.0-beta.47", + "@ohif/extension-dicom-video": "3.7.0-beta.47", + "@ohif/extension-measurement-tracking": "3.7.0-beta.47" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json index 95bdb3e44ea..e029517056b 100644 --- a/modes/microscopy/package.json +++ b/modes/microscopy/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-microscopy", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "OHIF mode for DICOM microscopy", "author": "OHIF", "license": "MIT", @@ -33,8 +33,8 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-dicom-microscopy": "3.7.0-beta.46" + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-dicom-microscopy": "3.7.0-beta.47" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/modes/tmtv/package.json b/modes/tmtv/package.json index 67cd4525d82..454c41ee9e1 100644 --- a/modes/tmtv/package.json +++ b/modes/tmtv/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/mode-tmtv", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Total Metabolic Tumor Volume Workflow", "author": "OHIF", "license": "MIT", @@ -32,13 +32,13 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/extension-dicom-pdf": "3.7.0-beta.46", - "@ohif/extension-dicom-video": "3.7.0-beta.46", - "@ohif/extension-measurement-tracking": "3.7.0-beta.46" + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/extension-dicom-pdf": "3.7.0-beta.47", + "@ohif/extension-dicom-video": "3.7.0-beta.47", + "@ohif/extension-measurement-tracking": "3.7.0-beta.47" }, "dependencies": { "@babel/runtime": "^7.20.13" diff --git a/platform/app/package.json b/platform/app/package.json index 6e0bf832a35..9ffa4827b6a 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/app", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "productVersion": "3.4.0", "description": "OHIF Viewer", "author": "OHIF Contributors", @@ -51,22 +51,22 @@ "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", "@cornerstonejs/dicom-image-loader": "^1.9.3", - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.46", - "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/extension-dicom-microscopy": "3.7.0-beta.46", - "@ohif/extension-dicom-pdf": "3.7.0-beta.46", - "@ohif/extension-dicom-video": "3.7.0-beta.46", - "@ohif/extension-test": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", - "@ohif/mode-basic-dev-mode": "3.7.0-beta.46", - "@ohif/mode-longitudinal": "3.7.0-beta.46", - "@ohif/mode-microscopy": "3.7.0-beta.46", - "@ohif/mode-test": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.7.0-beta.47", + "@ohif/extension-cornerstone": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.47", + "@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.47", + "@ohif/extension-default": "3.7.0-beta.47", + "@ohif/extension-dicom-microscopy": "3.7.0-beta.47", + "@ohif/extension-dicom-pdf": "3.7.0-beta.47", + "@ohif/extension-dicom-video": "3.7.0-beta.47", + "@ohif/extension-test": "3.7.0-beta.47", + "@ohif/i18n": "3.7.0-beta.47", + "@ohif/mode-basic-dev-mode": "3.7.0-beta.47", + "@ohif/mode-longitudinal": "3.7.0-beta.47", + "@ohif/mode-microscopy": "3.7.0-beta.47", + "@ohif/mode-test": "3.7.0-beta.47", + "@ohif/ui": "3.7.0-beta.47", "@types/react": "^17.0.38", "classnames": "^2.3.2", "core-js": "^3.16.1", diff --git a/platform/cli/package.json b/platform/cli/package.json index e1628aa0672..91cdfcaaa67 100644 --- a/platform/cli/package.json +++ b/platform/cli/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/cli", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "A CLI to bootstrap new OHIF extension or mode", "type": "module", "main": "src/index.js", diff --git a/platform/core/package.json b/platform/core/package.json index 95f62ab4c49..3fa2acd0ac2 100644 --- a/platform/core/package.json +++ b/platform/core/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/core", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Generic business logic for web-based medical imaging applications", "author": "OHIF Core Team", "license": "MIT", @@ -36,7 +36,7 @@ "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", "@cornerstonejs/dicom-image-loader": "^1.9.3", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/ui": "3.7.0-beta.47", "cornerstone-math": "0.1.9", "dicom-parser": "^1.8.21" }, diff --git a/platform/core/src/index.test.js b/platform/core/src/index.test.js index 3a62a5141de..85ee6817645 100644 --- a/platform/core/src/index.test.js +++ b/platform/core/src/index.test.js @@ -39,6 +39,8 @@ describe('Top level exports', () => { 'UserAuthenticationService', 'IWebApiDataSource', 'DicomMetadataStore', + 'DisplaySetMessage', + 'DisplaySetMessageList', 'pubSubServiceInterface', 'PubSubService', 'PanelService', diff --git a/platform/core/src/index.ts b/platform/core/src/index.ts index 458c7a3ab8c..f49423d07b5 100644 --- a/platform/core/src/index.ts +++ b/platform/core/src/index.ts @@ -33,6 +33,11 @@ import { PanelService, } from './services'; +import { + DisplaySetMessage, + DisplaySetMessageList, +} from './services/DisplaySetService'; + import IWebApiDataSource from './DataSources/IWebApiDataSource'; const hotkeys = { @@ -107,6 +112,8 @@ export { UINotificationService, UIViewportDialogService, DisplaySetService, + DisplaySetMessage, + DisplaySetMessageList, MeasurementService, ToolbarService, ViewportGridService, diff --git a/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts new file mode 100644 index 00000000000..a752acee35d --- /dev/null +++ b/platform/core/src/services/DisplaySetService/DisplaySetMessage.ts @@ -0,0 +1,49 @@ +/** + * Defines a displaySet message, that could be any pf the potential problems of a displaySet + */ +class DisplaySetMessage { + id: number; + static CODES = { + NO_VALID_INSTANCES: 1, + NO_POSITION_INFORMATION: 2, + NOT_RECONSTRUCTABLE: 3, + MULTIFRAME_NO_PIXEL_MEASUREMENTS: 4, + MULTIFRAME_NO_ORIENTATION: 5, + MULTIFRAME_NO_POSITION_INFORMATION: 6, + MISSING_FRAMES: 7, + IRREGULAR_SPACING: 8, + INCONSISTENT_DIMENSIONS: 9, + INCONSISTENT_COMPONENTS: 10, + INCONSISTENT_ORIENTATIONS: 11, + INCONSISTENT_POSITION_INFORMATION: 12, + }; + + constructor(id: number) { + this.id = id; + } +} +/** + * Defines a list of displaySet messages + */ +class DisplaySetMessageList { + messages = []; + + public addMessage(messageId: number): void { + const message = new DisplaySetMessage(messageId); + this.messages.push(message); + } + + public size(): number { + return this.messages.length; + } + + public includesMessage(messageId: number): boolean { + return this.messages.some(message => message.id === messageId); + } + + public includesAllMessages(messageIdList: number[]): boolean { + return messageIdList.every(messageId => this.include(messageId)); + } +} + +export { DisplaySetMessage, DisplaySetMessageList }; diff --git a/platform/core/src/services/DisplaySetService/index.ts b/platform/core/src/services/DisplaySetService/index.ts index 6039e648c37..5893d7beea5 100644 --- a/platform/core/src/services/DisplaySetService/index.ts +++ b/platform/core/src/services/DisplaySetService/index.ts @@ -1,3 +1,5 @@ import DisplaySetService from './DisplaySetService'; +import { DisplaySetMessage, DisplaySetMessageList } from './DisplaySetMessage'; export default DisplaySetService; +export { DisplaySetMessage, DisplaySetMessageList }; diff --git a/platform/core/src/utils/isDisplaySetReconstructable.js b/platform/core/src/utils/isDisplaySetReconstructable.js index 373da994afc..37aa36602b3 100644 --- a/platform/core/src/utils/isDisplaySetReconstructable.js +++ b/platform/core/src/utils/isDisplaySetReconstructable.js @@ -54,8 +54,8 @@ function hasPixelMeasurements(multiFrameInstance) { Boolean(sharedSequence?.PixelMeasuresSequence) || Boolean( multiFrameInstance.PixelSpacing && - (multiFrameInstance.SliceThickness || - multiFrameInstance.SpacingBetweenFrames) + (multiFrameInstance.SliceThickness || + multiFrameInstance.SpacingBetweenFrames) ) ); } @@ -70,8 +70,8 @@ function hasOrientation(multiFrameInstance) { Boolean(perFrameSequence?.PlaneOrientationSequence) || Boolean( multiFrameInstance.ImageOrientationPatient || - multiFrameInstance.DetectorInformationSequence?.[0] - ?.ImageOrientationPatient + multiFrameInstance.DetectorInformationSequence?.[0] + ?.ImageOrientationPatient ) ); } @@ -85,8 +85,8 @@ function hasPosition(multiFrameInstance) { Boolean(perFrameSequence?.CTPositionSequence) || Boolean( multiFrameInstance.ImagePositionPatient || - multiFrameInstance.DetectorInformationSequence?.[0] - ?.ImagePositionPatient + multiFrameInstance.DetectorInformationSequence?.[0] + ?.ImagePositionPatient ) ); } @@ -161,6 +161,7 @@ function processSingleframe(instances) { } let missingFrames = 0; + let averageSpacingBetweenFrames; // Check if frame spacing is approximately equal within a spacingTolerance. // If spacing is on a uniform grid but we are missing frames, @@ -175,7 +176,7 @@ function processSingleframe(instances) { return { value: false }; } - const averageSpacingBetweenFrames = + averageSpacingBetweenFrames = _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (instances.length - 1); @@ -209,7 +210,7 @@ function processSingleframe(instances) { } } - return { value: true, missingFrames }; + return { value: true, averageSpacingBetweenFrames }; } function _isSameOrientation(iop1, iop2) { @@ -220,7 +221,10 @@ function _isSameOrientation(iop1, iop2) { return ( Math.abs(iop1[0] - iop2[0]) < iopTolerance && Math.abs(iop1[1] - iop2[1]) < iopTolerance && - Math.abs(iop1[2] - iop2[2]) < iopTolerance + Math.abs(iop1[2] - iop2[2]) < iopTolerance && + Math.abs(iop1[3] - iop2[3]) < iopTolerance && + Math.abs(iop1[4] - iop2[4]) < iopTolerance && + Math.abs(iop1[5] - iop2[5]) < iopTolerance ); } @@ -260,8 +264,8 @@ function _getSpacingIssue(spacing, averageSpacing) { function _getPerpendicularDistance(a, b) { return Math.sqrt( Math.pow(a[0] - b[0], 2) + - Math.pow(a[1] - b[1], 2) + - Math.pow(a[2] - b[2], 2) + Math.pow(a[1] - b[1], 2) + + Math.pow(a[2] - b[2], 2) ); } @@ -270,3 +274,15 @@ const reconstructionIssues = { MISSING_FRAMES: 'missingframes', IRREGULAR_SPACING: 'irregularspacing', }; + +export { + hasPixelMeasurements, + hasOrientation, + hasPosition, + isNMReconstructable, + _isSameOrientation, + _getSpacingIssue, + _getPerpendicularDistance, + reconstructionIssues, + constructableModalities, +}; diff --git a/platform/docs/package.json b/platform/docs/package.json index 565653d1b01..1a27bd25a46 100644 --- a/platform/docs/package.json +++ b/platform/docs/package.json @@ -1,6 +1,6 @@ { "name": "ohif-docs", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "private": true, "workspaces": { "nohoist": [ diff --git a/platform/i18n/package.json b/platform/i18n/package.json index 9cbfebac2ac..8b711460cdd 100644 --- a/platform/i18n/package.json +++ b/platform/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/i18n", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "Internationalization library for The OHIF Viewer", "author": "OHIF", "license": "MIT", diff --git a/platform/i18n/src/locales/en-US/Messages.json b/platform/i18n/src/locales/en-US/Messages.json new file mode 100644 index 00000000000..4004aa01017 --- /dev/null +++ b/platform/i18n/src/locales/en-US/Messages.json @@ -0,0 +1,14 @@ +{ + "1": "No valid instances found in series.", + "2": "DisplaySet has missing position information.", + "3": "DisplaySet is not a reconstructable 3D volume.", + "4": "Multi frame displaySet don't have pixel measurement information.", + "5": "Multi frame displaySet don't have orientation information.", + "6": "Multi frame displaySet don't have position information.", + "7": "DisplaySet has missing frames.", + "8": "DisplaySet has irregular spacing.", + "9": "DisplaySet has inconsistent dimensions between frames.", + "10": "DisplaySet has frames with inconsistent number of components.", + "11": "DisplaySet has frames with inconsistent orientations.", + "12": "DisplaySet has inconsistent position information." +} diff --git a/platform/i18n/src/locales/en-US/index.js b/platform/i18n/src/locales/en-US/index.js index 279856d0ad4..037f807cd6b 100644 --- a/platform/i18n/src/locales/en-US/index.js +++ b/platform/i18n/src/locales/en-US/index.js @@ -10,6 +10,7 @@ import StudyBrowser from './StudyBrowser.json'; import StudyList from './StudyList.json'; import UserPreferencesModal from './UserPreferencesModal.json'; import ViewportDownloadForm from './ViewportDownloadForm.json'; +import Messages from './Messages.json'; export default { 'en-US': { @@ -25,5 +26,6 @@ export default { StudyList, UserPreferencesModal, ViewportDownloadForm, + Messages, }, }; diff --git a/platform/i18n/src/locales/pt-BR/Messages.json b/platform/i18n/src/locales/pt-BR/Messages.json new file mode 100644 index 00000000000..f0d12dce1cd --- /dev/null +++ b/platform/i18n/src/locales/pt-BR/Messages.json @@ -0,0 +1,14 @@ +{ + "1": "Série sem imagens.", + "2": "Série nao possui informação de posição.", + "3": "Serie não é reconstruível.", + "4": "Série nulti frame não possui informação de medidas.", + "5": "Série multi frame não possui informação de orientação.", + "6": "Série multi frame não possui informação de posição.", + "7": "Série não possui algumas imagens.", + "8": "Série possui espaçamento irregular.", + "9": "Série possui dimensões inconsistentes entre frames.", + "10": "Série possui frames com componentes inconsistentes.", + "11": "Série possui frames com orientações inconsistentes.", + "12": "Série possui informação de posição inconsistentes." +} diff --git a/platform/i18n/src/locales/pt-BR/index.js b/platform/i18n/src/locales/pt-BR/index.js index 0695b6869fe..2dcc04c2b17 100644 --- a/platform/i18n/src/locales/pt-BR/index.js +++ b/platform/i18n/src/locales/pt-BR/index.js @@ -6,6 +6,7 @@ import DatePicker from './DatePicker.json'; import Header from './Header.json'; import UserPreferencesModal from './UserPreferencesModal.json'; import MeasurementTable from './MeasurementTable.json'; +import Messages from './Messages.json'; export default { 'pt-BR': { @@ -17,5 +18,6 @@ export default { Header, UserPreferencesModal, MeasurementTable, + Messages, }, }; diff --git a/platform/ui/package.json b/platform/ui/package.json index 9ac77c772d3..7c3d0743d61 100644 --- a/platform/ui/package.json +++ b/platform/ui/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/ui", - "version": "3.7.0-beta.46", + "version": "3.7.0-beta.47", "description": "A set of React components for Medical Imaging Viewers", "author": "OHIF Contributors", "license": "MIT", diff --git a/platform/ui/src/assets/icons/status-alert-warning.svg b/platform/ui/src/assets/icons/status-alert-warning.svg new file mode 100644 index 00000000000..63397535101 --- /dev/null +++ b/platform/ui/src/assets/icons/status-alert-warning.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx b/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx new file mode 100644 index 00000000000..0f23ab49351 --- /dev/null +++ b/platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import PortalTooltip from '../Tooltip/PortalTooltip'; +import Icon from '../Icon'; +import { useTranslation } from 'react-i18next'; + +/** + * Displays a tooltip with a list of messages of a displaySet + * @param param0 + * @returns + */ +const DisplaySetMessageListTooltip = ({ messages, id }): React.ReactNode => { + const { t } = useTranslation('Messages'); + const [isOpen, setIsOpen] = useState(false); + if (messages.size()) { + return ( + <> + setIsOpen(true)} + onFocus={() => setIsOpen(true)} + onMouseOut={() => setIsOpen(false)} + onBlur={() => setIsOpen(false)} + name="status-alert-warning" + /> + +
+
+ DisplaySet Messages +
+
    + {messages.messages.map((message, index) => ( +
  1. + {index + 1}. {t(message.id)} +
  2. + ))} +
+
+
+ + ); + } + return <>; +}; + +DisplaySetMessageListTooltip.propTypes = { + messages: PropTypes.object, +}; + +export default DisplaySetMessageListTooltip; diff --git a/platform/ui/src/components/DisplaySetMessageListTooltip/index.js b/platform/ui/src/components/DisplaySetMessageListTooltip/index.js new file mode 100644 index 00000000000..f8e139c630c --- /dev/null +++ b/platform/ui/src/components/DisplaySetMessageListTooltip/index.js @@ -0,0 +1,2 @@ +import DisplaySetMessageListTooltip from './DisplaySetMessageListTooltip'; +export default DisplaySetMessageListTooltip; diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js index 18a80243527..0c973f051b4 100644 --- a/platform/ui/src/components/Icon/getIcon.js +++ b/platform/ui/src/components/Icon/getIcon.js @@ -42,6 +42,7 @@ import settings from './../../assets/icons/settings.svg'; import sorting from './../../assets/icons/sorting.svg'; import sortingActiveDown from './../../assets/icons/sorting-active-down.svg'; import sortingActiveUp from './../../assets/icons/sorting-active-up.svg'; +import statusAlertWarning from './../../assets/icons/status-alert-warning.svg'; import statusAlert from './../../assets/icons/status-alert.svg'; import statusLocked from './../../assets/icons/status-locked.svg'; import statusTracked from './../../assets/icons/status-tracked.svg'; @@ -181,6 +182,7 @@ const ICONS = { 'sorting-active-down': sortingActiveDown, 'sorting-active-up': sortingActiveUp, 'status-alert': statusAlert, + 'status-alert-warning': statusAlertWarning, 'status-locked': statusLocked, 'status-tracked': statusTracked, 'status-untracked': statusUntracked, diff --git a/platform/ui/src/components/Thumbnail/Thumbnail.tsx b/platform/ui/src/components/Thumbnail/Thumbnail.tsx index 7f82e4a27f5..f334ce49aca 100644 --- a/platform/ui/src/components/Thumbnail/Thumbnail.tsx +++ b/platform/ui/src/components/Thumbnail/Thumbnail.tsx @@ -4,6 +4,7 @@ import classnames from 'classnames'; import { useDrag } from 'react-dnd'; import Icon from '../Icon'; import { StringNumber } from '../../types'; +import DisplaySetMessageListTooltip from '../DisplaySetMessageListTooltip'; /** * Display a thumbnail for a display set. @@ -17,6 +18,7 @@ const Thumbnail = ({ seriesNumber, numInstances, countIcon, + messages, dragData, isActive, onClick, @@ -78,6 +80,10 @@ const Thumbnail = ({ {` ${numInstances}`} +
{description}
@@ -104,6 +110,7 @@ Thumbnail.propTypes = { description: PropTypes.string.isRequired, seriesNumber: StringNumber.isRequired, numInstances: PropTypes.number.isRequired, + messages: PropTypes.object, isActive: PropTypes.bool.isRequired, onClick: PropTypes.func.isRequired, onDoubleClick: PropTypes.func.isRequired, diff --git a/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx b/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx index 94dc4cb6b32..2de139ef17d 100644 --- a/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx +++ b/platform/ui/src/components/ThumbnailList/ThumbnailList.tsx @@ -34,6 +34,7 @@ const ThumbnailList = ({ canReject, onReject, imageSrc, + messages, imageAltText, }) => { const isActive = activeDisplaySetInstanceUIDs.includes( @@ -52,6 +53,7 @@ const ThumbnailList = ({ countIcon={countIcon} imageSrc={imageSrc} imageAltText={imageAltText} + messages={messages} viewportIdentificator={viewportIdentificator} isActive={isActive} onClick={() => onThumbnailClick(displaySetInstanceUID)} @@ -72,6 +74,7 @@ const ThumbnailList = ({ countIcon={countIcon} imageSrc={imageSrc} imageAltText={imageAltText} + messages={messages} viewportIdentificator={viewportIdentificator} isTracked={isTracked} isActive={isActive} @@ -91,6 +94,7 @@ const ThumbnailList = ({ dragData={dragData} modality={modality} modalityTooltip={_getModalityTooltip(modality)} + messages={messages} seriesDate={seriesDate} description={description} canReject={canReject} diff --git a/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx b/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx index c26ae6714fb..646a6836aee 100644 --- a/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx +++ b/platform/ui/src/components/ThumbnailNoImage/ThumbnailNoImage.tsx @@ -16,6 +16,7 @@ const ThumbnailNoImage = ({ onDoubleClick, canReject, onReject, + messages, dragData, isActive, }) => { @@ -56,6 +57,23 @@ const ThumbnailNoImage = ({ {seriesDate} + {messages?.size() ? ( +
+ + {messages.thumbnailContents()} +
+ } + > + + + + ) : ( + <> + )}
{canReject && ( @@ -96,6 +114,7 @@ ThumbnailNoImage.propTypes = { seriesDate: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, onDoubleClick: PropTypes.func.isRequired, + messages: PropTypes.object, isActive: PropTypes.bool.isRequired, }; diff --git a/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx b/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx index f921ee6cc4e..bef95e3fe0d 100644 --- a/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx +++ b/platform/ui/src/components/ThumbnailTracked/ThumbnailTracked.tsx @@ -16,6 +16,7 @@ const ThumbnailTracked = ({ seriesNumber, numInstances, countIcon, + messages, dragData, onClick, onDoubleClick, @@ -119,6 +120,7 @@ const ThumbnailTracked = ({ dragData={dragData} description={description} seriesNumber={seriesNumber} + messages={messages} numInstances={numInstances} countIcon={countIcon} isActive={isActive} @@ -153,6 +155,7 @@ ThumbnailTracked.propTypes = { onClickUntrack: PropTypes.func.isRequired, viewportIdentificator: PropTypes.array, isTracked: PropTypes.bool, + messages: PropTypes.object, isActive: PropTypes.bool.isRequired, }; diff --git a/platform/ui/src/components/Tooltip/PortalTooltip.tsx b/platform/ui/src/components/Tooltip/PortalTooltip.tsx new file mode 100644 index 00000000000..7047b0bc446 --- /dev/null +++ b/platform/ui/src/components/Tooltip/PortalTooltip.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; + +import Card from './PortalTooltipCard'; + +const portalNodes = {}; + +/** + * A portal based tooltip component. + * + * This component has been repurposed and modified + * for OHIF usage: https://github.com/romainberger/react-portal-tooltip + */ +export default class PortalTooltip extends React.Component { + static propTypes = { + parent: PropTypes.oneOfType([PropTypes.string, PropTypes.object]) + .isRequired, + active: PropTypes.bool, + group: PropTypes.string, + tooltipTimeout: PropTypes.number, + }; + + static defaultProps = { + active: false, + group: 'main', + tooltipTimeout: 0, + }; + + createPortal() { + portalNodes[this.props.group] = { + node: document.createElement('div'), + timeout: false, + }; + portalNodes[this.props.group].node.className = 'ToolTipPortal'; + document.body.appendChild(portalNodes[this.props.group].node); + } + + renderPortal(props) { + if (!portalNodes[this.props.group]) { + this.createPortal(); + } + let { parent, ...other } = props; + let parentEl = + typeof parent === 'string' ? document.querySelector(parent) : parent; + ReactDOM.render( + , + portalNodes[this.props.group].node + ); + } + + componentDidMount() { + if (!this.props.active) { + return; + } + + this.renderPortal(this.props); + } + + componentWillReceiveProps(nextProps) { + if ( + (!portalNodes[this.props.group] && !nextProps.active) || + (!this.props.active && !nextProps.active) + ) { + return; + } + + let props = { ...nextProps }; + let newProps = { ...nextProps }; + + if ( + portalNodes[this.props.group] && + portalNodes[this.props.group].timeout + ) { + clearTimeout(portalNodes[this.props.group].timeout); + } + + if (this.props.active && !props.active) { + newProps.active = true; + portalNodes[this.props.group].timeout = setTimeout(() => { + props.active = false; + this.renderPortal(props); + }, this.props.tooltipTimeout); + } + + this.renderPortal(newProps); + } + + componentWillUnmount() { + if (portalNodes[this.props.group]) { + ReactDOM.unmountComponentAtNode(portalNodes[this.props.group].node); + clearTimeout(portalNodes[this.props.group].timeout); + + try { + document.body.removeChild(portalNodes[this.props.group].node); + } catch (e) {} + + portalNodes[this.props.group] = null; + } + } + + render() { + return null; + } +} diff --git a/platform/ui/src/components/Tooltip/PortalTooltipCard.tsx b/platform/ui/src/components/Tooltip/PortalTooltipCard.tsx new file mode 100644 index 00000000000..706602af726 --- /dev/null +++ b/platform/ui/src/components/Tooltip/PortalTooltipCard.tsx @@ -0,0 +1,413 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +const FG_SIZE = 8; +const BG_SIZE = 9; + +/** + * A portal based tooltip card component. + * + * This component has been repurposed and modified + * for OHIF usage: https://github.com/romainberger/react-portal-tooltip + */ +export default class PortalTooltipCard extends Component { + static propTypes = { + active: PropTypes.bool, + position: PropTypes.oneOf(['top', 'right', 'bottom', 'left']), + arrow: PropTypes.oneOf([null, 'center', 'top', 'right', 'bottom', 'left']), + align: PropTypes.oneOf([null, 'center', 'right', 'left']), + style: PropTypes.object, + useHover: PropTypes.bool, + }; + + static defaultProps = { + active: false, + position: 'right', + arrow: null, + align: null, + style: { style: {}, arrowStyle: {} }, + useHover: true, + }; + + state = { + hover: false, + width: 0, + height: 0, + }; + + offscreenDifference = 0; + + margin = 15; + + defaultArrowStyle = { + color: '#090c29', // primary-dark + borderColor: 'rgba(58, 63, 153, 1)', // secondary-light + }; + + rootRef = React.createRef(); + + getGlobalStyle() { + if (!this.props.parentEl) { + return { display: 'none' }; + } + + const style = { + position: 'absolute', + //padding: '5px', + background: 'bg-primary-dark', + //boxShadow: '0 0 4px rgba(0,0,0,.3)', + borderRadius: '3px', + //opacity: this.state.hover || this.props.active ? 1 : 0, + visibility: this.state.hover || this.props.active ? 'visible' : 'hidden', + zIndex: 50, + ...this.getStyle(this.props.position, this.props.arrow), + }; + + return this.mergeStyle(style, this.props.style.style); + } + + getBaseArrowStyle() { + return { + position: 'absolute', + content: '""', + }; + } + + getArrowStyle() { + let fgStyle = this.getBaseArrowStyle(); + let bgStyle = this.getBaseArrowStyle(); + fgStyle.zIndex = 60; + bgStyle.zIndex = 55; + + let arrowStyle = { + ...this.defaultArrowStyle, + ...this.props.style.arrowStyle, + }; + let bgBorderColor = arrowStyle.borderColor + ? arrowStyle.borderColor + : 'transparent'; + + let fgColorBorder = `10px solid ${arrowStyle.color}`; + let fgTransBorder = `${FG_SIZE}px solid transparent`; + let bgColorBorder = `12px solid ${bgBorderColor}`; + let bgTransBorder = `${BG_SIZE}px solid transparent`; + + let { position, arrow } = this.props; + + if (position === 'left' || position === 'right') { + fgStyle.top = '50%'; + fgStyle.borderTop = fgTransBorder; + fgStyle.borderBottom = fgTransBorder; + fgStyle.marginTop = -7; + + bgStyle.borderTop = bgTransBorder; + bgStyle.borderBottom = bgTransBorder; + bgStyle.top = '50%'; + bgStyle.marginTop = -8; + + if (position === 'left') { + fgStyle.right = -10; + fgStyle.borderLeft = fgColorBorder; + bgStyle.right = -11; + bgStyle.borderLeft = bgColorBorder; + } else { + fgStyle.left = -9; + fgStyle.borderRight = fgColorBorder; + bgStyle.left = -11; + bgStyle.borderRight = bgColorBorder; + } + + if (arrow === 'top') { + fgStyle.top = this.margin; + bgStyle.top = this.margin; + } + if (arrow === 'bottom') { + fgStyle.top = null; + fgStyle.bottom = this.margin - 7; + bgStyle.top = null; + bgStyle.bottom = this.margin - 8; + } + } else { + fgStyle.left = Math.round(this.state.width / 2 - FG_SIZE); + fgStyle.borderLeft = fgTransBorder; + fgStyle.borderRight = fgTransBorder; + fgStyle.marginLeft = 0; + bgStyle.left = fgStyle.left - 1; + bgStyle.borderLeft = bgTransBorder; + bgStyle.borderRight = bgTransBorder; + bgStyle.marginLeft = 0; + + if (position === 'top') { + fgStyle.bottom = -10; + fgStyle.borderTop = fgColorBorder; + bgStyle.bottom = -11; + bgStyle.borderTop = bgColorBorder; + } else { + fgStyle.top = -10; + fgStyle.borderBottom = fgColorBorder; + bgStyle.top = -11; + bgStyle.borderBottom = bgColorBorder; + } + + if (arrow === 'right') { + fgStyle.left = null; + fgStyle.right = this.margin + 1 - FG_SIZE; + bgStyle.left = null; + bgStyle.right = this.margin - FG_SIZE; + } + if (arrow === 'left') { + fgStyle.left = this.margin + 1 - FG_SIZE; + bgStyle.left = this.margin - FG_SIZE; + } + } + + let { + color, + borderColor, + ...propsArrowStyle + } = this.props.style.arrowStyle; + + const state = { + fgStyle: this.mergeStyle(fgStyle, propsArrowStyle), + bgStyle: this.mergeStyle(bgStyle, propsArrowStyle), + }; + + if (this.offscreenDifference > 0) { + if (state.fgStyle.top >= 0 || state.fgStyle.top < 0) { + state.fgStyle.top += this.offscreenDifference; + } + if (state.bgStyle.top >= 0 || state.bgStyle.top < 0) { + state.bgStyle.top += this.offscreenDifference; + } + if (typeof state.fgStyle.top === 'string') { + state.fgStyle.top = `calc(${state.fgStyle.top} + ${this.offscreenDifference}px)`; + } + if (typeof state.bgStyle.top === 'string') { + state.bgStyle.top = `calc(${state.bgStyle.top} + ${this.offscreenDifference}px)`; + } + } + + return state; + } + + mergeStyle(style, theme) { + if (theme) { + let { + position, + top, + left, + right, + bottom, + marginLeft, + marginRight, + ...validTheme + } = theme; + + return { + ...style, + ...validTheme, + }; + } + + return style; + } + + getStyle(position, arrow) { + let alignOffset = 0; + let parent = this.props.parentEl; + let align = this.props.align; + let tooltipPosition = parent.getBoundingClientRect(); + let scrollY = + window.scrollY !== undefined ? window.scrollY : window.pageYOffset; + let scrollX = + window.scrollX !== undefined ? window.scrollX : window.pageXOffset; + let top = scrollY + tooltipPosition.top; + let left = scrollX + tooltipPosition.left; + let style = {}; + + if (this.rootRef.current) { + const newHeight = this.rootRef.current.offsetHeight / 2; + const bottomPosition = tooltipPosition.bottom + newHeight; + const isOffscreen = + tooltipPosition.bottom + newHeight > window.innerHeight; + const offscreenDifference = bottomPosition - window.innerHeight; + if (isOffscreen) { + const padding = 3; + top -= offscreenDifference; + this.offscreenDifference = Math.min( + Math.max(offscreenDifference, 0), + newHeight - parent.getBoundingClientRect().height / 2 - padding + ); + } else { + this.offscreenDifference = 0; + } + } + + const parentSize = { + width: parent.offsetWidth, + height: parent.offsetHeight, + }; + + // fix for svg + if (!parent.offsetHeight && parent.getBoundingClientRect) { + parentSize.width = parent.getBoundingClientRect().width; + parentSize.height = parent.getBoundingClientRect().height; + } + + if (align === 'left') { + alignOffset = -parentSize.width / 2 + FG_SIZE; + } else if (align === 'right') { + alignOffset = parentSize.width / 2 - FG_SIZE; + } + + const stylesFromPosition = { + left: () => { + style.top = top + parentSize.height / 2 - this.state.height / 2; + style.left = left - this.state.width - this.margin; + }, + right: () => { + style.top = top + parentSize.height / 2 - this.state.height / 2; + style.left = left + parentSize.width + this.margin; + }, + top: () => { + style.left = + left - this.state.width / 2 + parentSize.width / 2 + alignOffset; + style.top = top - this.state.height - this.margin; + }, + bottom: () => { + style.left = + left - this.state.width / 2 + parentSize.width / 2 + alignOffset; + style.top = top + parentSize.height + this.margin; + }, + }; + + const stylesFromArrow = { + left: () => { + style.left = left + parentSize.width / 2 - this.margin + alignOffset; + }, + right: () => { + style.left = + left - + this.state.width + + parentSize.width / 2 + + this.margin + + alignOffset; + }, + top: () => { + style.top = top + parentSize.height / 2 - this.margin; + }, + bottom: () => { + style.top = + top + parentSize.height / 2 - this.state.height + this.margin; + }, + }; + + executeFunctionIfExist(stylesFromPosition, position); + executeFunctionIfExist(stylesFromArrow, arrow); + + return style; + } + + checkWindowPosition(style, arrowStyle) { + if (this.props.position === 'top' || this.props.position === 'bottom') { + if (style.left < 0) { + const parent = this.props.parentEl; + if (parent) { + const tooltipWidth = this.state.width; + let bgStyleRight = arrowStyle.bgStyle.right; + // For arrow = center + if (!bgStyleRight) { + bgStyleRight = tooltipWidth / 2 - BG_SIZE; + } + const newBgRight = Math.round( + bgStyleRight - style.left + this.margin + ); + arrowStyle = { + ...arrowStyle, + bgStyle: { + ...arrowStyle.bgStyle, + right: newBgRight, + left: null, + }, + fgStyle: { + ...arrowStyle.fgStyle, + right: newBgRight + 1, + left: null, + }, + }; + } + style.left = this.margin; + } else { + let rightOffset = style.left + this.state.width - window.innerWidth; + if (rightOffset > 0) { + let originalLeft = style.left; + style.left = window.innerWidth - this.state.width - this.margin; + arrowStyle.fgStyle.marginLeft += originalLeft - style.left; + arrowStyle.bgStyle.marginLeft += originalLeft - style.left; + } + } + } + + return { style, arrowStyle }; + } + + handleMouseEnter = () => { + this.props.active && this.props.useHover && this.setState({ hover: true }); + }; + + handleMouseLeave = () => { + this.setState({ hover: false }); + }; + + componentDidMount() { + this.updateSize(); + } + + componentDidUpdate(prevProps, prevState) { + if (this.props !== prevProps) { + this.updateSize(); + } + } + + updateSize() { + const newWidth = this.rootRef.current.offsetWidth; + const newHeight = this.rootRef.current.offsetHeight; + + if (newWidth !== this.state.width || newHeight !== this.state.height) { + this.setState({ + width: newWidth, + height: newHeight, + }); + } + } + + render() { + let { style, arrowStyle } = this.checkWindowPosition( + this.getGlobalStyle(), + this.getArrowStyle() + ); + + return ( +
+ {this.props.arrow ? ( +
+ + +
+ ) : null} + {this.props.children} +
+ ); + } +} + +const executeFunctionIfExist = (object, key) => { + if (Object.prototype.hasOwnProperty.call(object, key)) { + object[key](); + } +}; diff --git a/version.json b/version.json index 80e59bf7072..6322787cf80 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,4 @@ { - "version": "3.7.0-beta.46", - "commit": "b55518e08ec9c9ab23e74aa4987927d8d75ae909" + "version": "3.7.0-beta.47", + "commit": "5302e5b62be22eb503d4fe067b435f7934284c39" } \ No newline at end of file diff --git a/version.txt b/version.txt index 72045c5ef75..3150495c99e 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.7.0-beta.46 \ No newline at end of file +3.7.0-beta.47 \ No newline at end of file