Files
ohif-viewer/platform/ui/src/components/studyBrowser/Thumbnail.js
T

253 lines
6.7 KiB
JavaScript
Raw Normal View History

import React, { useState, useEffect } from 'react';
2019-10-30 16:53:58 -03:00
import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd';
2021-09-09 04:58:24 -03:00
import { classes } from '@ohif/core';
2019-10-30 16:53:58 -03:00
import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames';
import { Icon } from './../../elements/Icon';
import { Tooltip } from './../tooltip';
import { OverlayTrigger } from './../overlayTrigger';
2019-10-30 16:53:58 -03:00
import './Thumbnail.styl';
2021-09-09 04:58:24 -03:00
const StudyLoadingListener = classes.StudyLoadingListener;
2019-10-30 16:53:58 -03:00
function ThumbnailFooter({
SeriesDescription,
SeriesNumber,
InstanceNumber,
2019-10-30 16:53:58 -03:00
numImageFrames,
2021-09-09 04:58:24 -03:00
hasWarnings,
2019-10-30 16:53:58 -03:00
}) {
const [inconsistencyWarnings, inconsistencyWarningsSet] = useState([]);
useEffect(() => {
2021-09-09 04:58:24 -03:00
let unmounted = false;
hasWarnings.then(response => {
if (!unmounted) {
2021-09-09 04:58:24 -03:00
inconsistencyWarningsSet(response);
}
2021-09-09 04:58:24 -03:00
});
return () => {
2021-09-09 04:58:24 -03:00
unmounted = true;
};
}, [hasWarnings]);
const infoOnly = !SeriesDescription;
2019-10-30 16:53:58 -03:00
const getInfo = (value, icon, className = '') => {
return (
<div className={classNames('item item-series', className)}>
<div className="icon">{icon}</div>
<div className="value">{value}</div>
</div>
);
};
2021-09-09 04:58:24 -03:00
const getWarningContent = inconsistencyWarnings => {
if (Array.isArray(inconsistencyWarnings)) {
const listedWarnings = inconsistencyWarnings.map((warn, index) => {
return <li key={index}>{warn}</li>;
});
return <ol>{listedWarnings}</ol>;
} else {
return <React.Fragment>{inconsistencyWarnings}</React.Fragment>;
}
};
const getWarningInfo = (SeriesNumber, inconsistencyWarnings) => {
2021-09-09 04:58:24 -03:00
return (
<React.Fragment>
{inconsistencyWarnings && inconsistencyWarnings.length != 0 ? (
<OverlayTrigger
key={SeriesNumber}
placement="left"
overlay={
<Tooltip
placement="left"
className="in tooltip-warning"
id="tooltip-left"
>
<div className="warningTitle">Series Inconsistencies</div>
2021-09-09 04:58:24 -03:00
<div className="warningContent">
{getWarningContent(inconsistencyWarnings)}
</div>
</Tooltip>
}
>
<div className={classNames('warning')}>
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
</OverlayTrigger>
) : (
<React.Fragment></React.Fragment>
2021-09-09 04:58:24 -03:00
)}
</React.Fragment>
2021-09-09 04:58:24 -03:00
);
};
2019-10-30 16:53:58 -03:00
const getSeriesInformation = (
SeriesNumber,
InstanceNumber,
numImageFrames,
inconsistencyWarnings
2019-10-30 16:53:58 -03:00
) => {
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
2019-10-30 16:53:58 -03:00
return;
}
2021-09-09 04:58:24 -03:00
const seriesInformation = (
2019-10-30 16:53:58 -03:00
<div className="series-information">
{getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')}
2019-10-30 16:53:58 -03:00
{getInfo(numImageFrames, '', 'image-frames')}
{getWarningInfo(SeriesNumber, inconsistencyWarnings)}
2019-10-30 16:53:58 -03:00
</div>
2021-09-09 04:58:24 -03:00
);
2021-09-09 04:58:24 -03:00
return seriesInformation;
2019-10-30 16:53:58 -03:00
};
return (
<div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div>
2021-09-09 04:58:24 -03:00
{getSeriesInformation(
SeriesNumber,
InstanceNumber,
numImageFrames,
inconsistencyWarnings
)}
2019-10-30 16:53:58 -03:00
</div>
);
}
function Thumbnail(props) {
const {
active,
altImageText,
error,
displaySetInstanceUID,
2019-10-30 16:53:58 -03:00
imageId,
imageSrc,
InstanceNumber,
2019-10-30 16:53:58 -03:00
numImageFrames,
SeriesDescription,
SeriesNumber,
hasWarnings,
StudyInstanceUID,
2019-10-30 16:53:58 -03:00
onClick,
onDoubleClick,
onMouseDown,
supportsDrag,
2021-09-09 04:58:24 -03:00
showProgressBar,
2019-10-30 16:53:58 -03:00
} = props;
2021-09-09 04:58:24 -03:00
const [stackPercentComplete, setStackPercentComplete] = useState(0);
useEffect(() => {
const onProgressChange = ({ detail }) => {
const { progressId, progressData } = detail;
if (`StackProgress:${displaySetInstanceUID}` === progressId) {
const percent = progressData ? progressData.percentComplete : 0;
setStackPercentComplete(percent);
}
};
document.addEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
return () => {
document.removeEventListener(
StudyLoadingListener.events.OnProgress,
onProgressChange
);
};
}, [displaySetInstanceUID]);
2019-10-30 16:53:58 -03:00
const [collectedProps, drag, dragPreview] = useDrag({
// `droppedItem` in `dropTarget`
// The only data it will have access to
item: {
StudyInstanceUID,
displaySetInstanceUID,
2019-10-30 16:53:58 -03:00
type: 'thumbnail', // Has to match `dropTarget`'s type
},
canDrag: function(monitor) {
return supportsDrag;
},
});
const hasImage = imageSrc || imageId;
const hasAltText = altImageText !== undefined;
return (
<div
ref={drag}
className={classNames('thumbnail', { active: active })}
onClick={onClick}
onDoubleClick={onDoubleClick}
onMouseDown={onMouseDown}
>
{/* SHOW IMAGE */}
{hasImage && (
<ImageThumbnail
active={active}
2019-10-30 16:53:58 -03:00
imageSrc={imageSrc}
imageId={imageId}
error={error}
stackPercentComplete={stackPercentComplete}
2021-09-09 04:58:24 -03:00
showProgressBar={showProgressBar}
2019-10-30 16:53:58 -03:00
/>
)}
{/* SHOW TEXT ALTERNATIVE */}
{!hasImage && hasAltText && (
<div className={'alt-image-text p-x-1'}>
<h1>{altImageText}</h1>
</div>
)}
{ThumbnailFooter(props)}
</div>
);
}
const noop = () => {};
Thumbnail.propTypes = {
supportsDrag: PropTypes.bool,
id: PropTypes.string.isRequired,
displaySetInstanceUID: PropTypes.string.isRequired,
StudyInstanceUID: PropTypes.string.isRequired,
2019-10-30 16:53:58 -03:00
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool,
active: PropTypes.bool,
stackPercentComplete: PropTypes.number,
/**
altImageText will be used when no imageId or imageSrc is provided.
It will be displayed inside the <div>. This is useful when it is difficult
2019-10-30 16:53:58 -03:00
to make a preview for a type of DICOM series (e.g. DICOM-SR)
*/
altImageText: PropTypes.string,
SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hasWarnings: PropTypes.instanceOf(Promise),
2019-10-30 16:53:58 -03:00
numImageFrames: PropTypes.number,
onDoubleClick: PropTypes.func,
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
2021-09-09 04:58:24 -03:00
showProgressBar: PropTypes.bool,
2019-10-30 16:53:58 -03:00
};
Thumbnail.defaultProps = {
supportsDrag: false,
active: false,
error: false,
stackPercentComplete: 0,
onDoubleClick: noop,
onClick: noop,
onMouseDown: noop,
};
export { Thumbnail };