Fixes after rebasing
This commit is contained in:
parent
70b7158c9b
commit
f3f50d11fe
@ -81,8 +81,12 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
|
|||||||
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||||
/* i18n */
|
/* i18n */
|
||||||
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
||||||
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
|
'process.env.LOCIZE_PROJECTID': JSON.stringify(
|
||||||
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
|
process.env.LOCIZE_PROJECTID || ''
|
||||||
|
),
|
||||||
|
'process.env.LOCIZE_API_KEY': JSON.stringify(
|
||||||
|
process.env.LOCIZE_API_KEY || ''
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
|
// Fix: https://github.com/webpack-contrib/css-loader/issues/447#issuecomment-285598881
|
||||||
|
|||||||
@ -180,7 +180,7 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
displaySet.displaySetInstanceUID !==
|
displaySet.displaySetInstanceUID !==
|
||||||
prevDisplaySet.displaySetInstanceUID ||
|
prevDisplaySet.displaySetInstanceUID ||
|
||||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||||
) {
|
) {
|
||||||
@ -216,18 +216,21 @@ class OHIFCornerstoneViewport extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedNewImageHandler = debounce(({ currentImageIdIndex, sopInstanceUid }) => {
|
const debouncedNewImageHandler = debounce(
|
||||||
const { displaySet } = this.props.viewportData;
|
({ currentImageIdIndex, sopInstanceUid }) => {
|
||||||
const { StudyInstanceUID } = displaySet;
|
const { displaySet } = this.props.viewportData;
|
||||||
if (currentImageIdIndex > 0) {
|
const { StudyInstanceUID } = displaySet;
|
||||||
this.props.onNewImage({
|
if (currentImageIdIndex > 0) {
|
||||||
StudyInstanceUID,
|
this.props.onNewImage({
|
||||||
SOPInstanceUID: sopInstanceUid,
|
StudyInstanceUID,
|
||||||
frameIndex: currentImageIdIndex,
|
SOPInstanceUID: sopInstanceUid,
|
||||||
activeViewportIndex: viewportIndex,
|
frameIndex: currentImageIdIndex,
|
||||||
});
|
activeViewportIndex: viewportIndex,
|
||||||
}
|
});
|
||||||
}, 700);
|
}
|
||||||
|
},
|
||||||
|
700
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -36,7 +36,10 @@ describe('measurementServiceMappings.js', () => {
|
|||||||
};
|
};
|
||||||
mappings = measurementServiceMappingsFactory(measurementServiceMock);
|
mappings = measurementServiceMappingsFactory(measurementServiceMock);
|
||||||
handles = { start: { x: 1, y: 2 }, end: { x: 1, y: 2 } };
|
handles = { start: { x: 1, y: 2 }, end: { x: 1, y: 2 } };
|
||||||
points = [{ x: 1, y: 2 }, { x: 1, y: 2 }];
|
points = [
|
||||||
|
{ x: 1, y: 2 },
|
||||||
|
{ x: 1, y: 2 },
|
||||||
|
];
|
||||||
csToolsAnnotation = {
|
csToolsAnnotation = {
|
||||||
toolName: definition,
|
toolName: definition,
|
||||||
measurementData: {
|
measurementData: {
|
||||||
|
|||||||
@ -8,9 +8,7 @@ import {
|
|||||||
import _downloadAndZip from './downloadAndZip';
|
import _downloadAndZip from './downloadAndZip';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
utils: {
|
utils: { Queue },
|
||||||
Queue,
|
|
||||||
},
|
|
||||||
} = OHIF;
|
} = OHIF;
|
||||||
|
|
||||||
export function getCommands(context) {
|
export function getCommands(context) {
|
||||||
|
|||||||
@ -52,7 +52,10 @@ class DicomPDFViewport extends Component {
|
|||||||
|
|
||||||
if (!this.props.useNative) {
|
if (!this.props.useNative) {
|
||||||
const pdf = await PDFJS.getDocument(fileURL).promise;
|
const pdf = await PDFJS.getDocument(fileURL).promise;
|
||||||
this.setState(state => ({ ...state, pdf }), () => this.updatePDFCanvas());
|
this.setState(
|
||||||
|
state => ({ ...state, pdf }),
|
||||||
|
() => this.updatePDFCanvas()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
|||||||
id: 'OHIFDicomRTStructSopClassHandler',
|
id: 'OHIFDicomRTStructSopClassHandler',
|
||||||
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
type: MODULE_TYPES.SOP_CLASS_HANDLER,
|
||||||
sopClassUIDs,
|
sopClassUIDs,
|
||||||
getDisplaySetFromSeries: function (
|
getDisplaySetFromSeries: function(
|
||||||
series,
|
series,
|
||||||
study,
|
study,
|
||||||
dicomWebClient,
|
dicomWebClient,
|
||||||
@ -67,7 +67,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
|
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
|
||||||
return loadRTStruct(
|
return loadRTStruct(
|
||||||
rtStructDisplaySet,
|
rtStructDisplaySet,
|
||||||
referencedDisplaySet,
|
referencedDisplaySet,
|
||||||
|
|||||||
@ -1,56 +1,262 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const LoadingIndicator = ({ height = "200px", width = "200px", expand = false }) => {
|
const LoadingIndicator = ({
|
||||||
|
height = '200px',
|
||||||
|
width = '200px',
|
||||||
|
expand = false,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div style={expand ? { height: "100%", width: "100%" } : {}}>
|
<div style={expand ? { height: '100%', width: '100%' } : {}}>
|
||||||
<svg style={{ margin: 'auto', background: 'none', display: 'block', shapeRendering: 'auto' }} width={width} height={height} viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
<svg
|
||||||
|
style={{
|
||||||
|
margin: 'auto',
|
||||||
|
background: 'none',
|
||||||
|
display: 'block',
|
||||||
|
shapeRendering: 'auto',
|
||||||
|
}}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
preserveAspectRatio="xMidYMid"
|
||||||
|
>
|
||||||
<g transform="rotate(0 50 50)">
|
<g transform="rotate(0 50 50)">
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<rect
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate>
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.9166666666666666s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(30 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(30 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.8333333333333334s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(60 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(60 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.75s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(90 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(90 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.6666666666666666s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(120 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(120 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.5833333333333334s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(150 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(150 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.5s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(180 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(180 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.4166666666666667s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(210 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(210 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.3333333333333333s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(240 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(240 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.25s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(270 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(270 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.16666666666666666s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(300 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(300 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="-0.08333333333333333s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g><g transform="rotate(330 50 50)">
|
</g>
|
||||||
<rect x="49" y="24" rx="0" ry="0" width="2" height="12" fill="#20a5d6">
|
<g transform="rotate(330 50 50)">
|
||||||
<animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
|
<rect
|
||||||
|
x="49"
|
||||||
|
y="24"
|
||||||
|
rx="0"
|
||||||
|
ry="0"
|
||||||
|
width="2"
|
||||||
|
height="12"
|
||||||
|
fill="#20a5d6"
|
||||||
|
>
|
||||||
|
<animate
|
||||||
|
attributeName="opacity"
|
||||||
|
values="1;0"
|
||||||
|
keyTimes="0;1"
|
||||||
|
dur="1s"
|
||||||
|
begin="0s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
></animate>
|
||||||
</rect>
|
</rect>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@ -12,8 +12,8 @@ const PanelSection = ({
|
|||||||
expanded = false,
|
expanded = false,
|
||||||
loading = false,
|
loading = false,
|
||||||
hideVisibleButton = false,
|
hideVisibleButton = false,
|
||||||
onVisibilityChange = () => { },
|
onVisibilityChange = () => {},
|
||||||
onExpandChange = () => { }
|
onExpandChange = () => {},
|
||||||
}) => {
|
}) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(expanded);
|
const [isExpanded, setIsExpanded] = useState(expanded);
|
||||||
const [isVisible, setIsVisible] = useState(visible);
|
const [isVisible, setIsVisible] = useState(visible);
|
||||||
@ -27,7 +27,7 @@ const PanelSection = ({
|
|||||||
className="dcmrt-panel-section"
|
className="dcmrt-panel-section"
|
||||||
style={{
|
style={{
|
||||||
marginBottom: isExpanded ? 0 : 2,
|
marginBottom: isExpanded ? 0 : 2,
|
||||||
height: isExpanded ? '100%' : 'unset'
|
height: isExpanded ? '100%' : 'unset',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="header">
|
<div className="header">
|
||||||
@ -36,7 +36,7 @@ const PanelSection = ({
|
|||||||
{!hideVisibleButton && (
|
{!hideVisibleButton && (
|
||||||
<Icon
|
<Icon
|
||||||
className={`eye-icon ${isVisible && 'expanded'}`}
|
className={`eye-icon ${isVisible && 'expanded'}`}
|
||||||
name={isVisible ? "eye" : "eye-closed"}
|
name={isVisible ? 'eye' : 'eye-closed'}
|
||||||
width="20px"
|
width="20px"
|
||||||
height="20px"
|
height="20px"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -47,7 +47,9 @@ const PanelSection = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Icon
|
<Icon
|
||||||
className={`angle-double-${isExpanded ? 'down' : 'up'} ${isExpanded && 'expanded'}`}
|
className={`angle-double-${
|
||||||
|
isExpanded ? 'down' : 'up'
|
||||||
|
} ${isExpanded && 'expanded'}`}
|
||||||
name={`angle-double-${isExpanded ? 'down' : 'up'}`}
|
name={`angle-double-${isExpanded ? 'down' : 'up'}`}
|
||||||
width="20px"
|
width="20px"
|
||||||
height="20px"
|
height="20px"
|
||||||
@ -59,7 +61,9 @@ const PanelSection = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{loading && isExpanded && <LoadingIndicator expand height="70px" width="70px" />}
|
{loading && isExpanded && (
|
||||||
|
<LoadingIndicator expand height="70px" width="70px" />
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -70,13 +74,13 @@ PanelSection.propTypes = {
|
|||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
|
||||||
visible: PropTypes.bool,
|
visible: PropTypes.bool,
|
||||||
expanded: PropTypes.bool,
|
expanded: PropTypes.bool,
|
||||||
onVisibilityChange: PropTypes.func
|
onVisibilityChange: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
PanelSection.defaultProps = {
|
PanelSection.defaultProps = {
|
||||||
visible: false,
|
visible: false,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
onVisibilityChange: () => { }
|
onVisibilityChange: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PanelSection;
|
export default PanelSection;
|
||||||
|
|||||||
@ -31,7 +31,13 @@ const refreshViewport = () => {
|
|||||||
* @param {number} props.isOpen - isOpen
|
* @param {number} props.isOpen - isOpen
|
||||||
* @returns component
|
* @returns component
|
||||||
*/
|
*/
|
||||||
const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }) => {
|
const RTPanel = ({
|
||||||
|
studies,
|
||||||
|
viewports,
|
||||||
|
activeIndex,
|
||||||
|
isOpen,
|
||||||
|
onContourItemClick,
|
||||||
|
}) => {
|
||||||
const [selectedContour, setSelectedContour] = useState();
|
const [selectedContour, setSelectedContour] = useState();
|
||||||
const DEFAULT_SET_INDEX = 0;
|
const DEFAULT_SET_INDEX = 0;
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
@ -65,7 +71,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
|
document.removeEventListener(
|
||||||
|
'extensiondicomrtrtloaded',
|
||||||
|
updateStructureSets
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -78,7 +87,9 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
activeViewport.SeriesInstanceUID
|
activeViewport.SeriesInstanceUID
|
||||||
);
|
);
|
||||||
|
|
||||||
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
|
const studyMetadata = studyMetadataManager.get(
|
||||||
|
activeViewport.StudyInstanceUID
|
||||||
|
);
|
||||||
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
|
||||||
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
|
||||||
Modality: 'RTSTRUCT',
|
Modality: 'RTSTRUCT',
|
||||||
@ -89,7 +100,7 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
setState({
|
setState({
|
||||||
referencedDisplaysets,
|
referencedDisplaysets,
|
||||||
selectedSet: defaultSet,
|
selectedSet: defaultSet,
|
||||||
sets: viewportSets
|
sets: viewportSets,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(DEFAULT_STATE);
|
setState(DEFAULT_STATE);
|
||||||
@ -105,7 +116,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
setShowSettings(showSettings && !isOpen);
|
setShowSettings(showSettings && !isOpen);
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const toContourItem = ({ ROINumber, ROIName, RTROIObservations, colorArray, visible }, loadedSet) => {
|
const toContourItem = (
|
||||||
|
{ ROINumber, ROIName, RTROIObservations, colorArray, visible },
|
||||||
|
loadedSet
|
||||||
|
) => {
|
||||||
let interpretedType = '';
|
let interpretedType = '';
|
||||||
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
|
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
|
||||||
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
|
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
|
||||||
@ -137,14 +151,20 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
);
|
);
|
||||||
|
|
||||||
const frameIndex = imageIds.indexOf(imageId);
|
const frameIndex = imageIds.indexOf(imageId);
|
||||||
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
|
const SOPInstanceUID = cornerstone.metaData.get(
|
||||||
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
|
'SOPInstanceUID',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
const StudyInstanceUID = cornerstone.metaData.get(
|
||||||
|
'StudyInstanceUID',
|
||||||
|
imageId
|
||||||
|
);
|
||||||
|
|
||||||
onContourItemClick({
|
onContourItemClick({
|
||||||
StudyInstanceUID,
|
StudyInstanceUID,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
activeViewportIndex: activeIndex
|
activeViewportIndex: activeIndex,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
label={`${ROIName} ${interpretedType}`}
|
label={`${ROIName} ${interpretedType}`}
|
||||||
@ -153,7 +173,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
visible={visible}
|
visible={visible}
|
||||||
onVisibilityChange={() => {
|
onVisibilityChange={() => {
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
module.setters.toggleROIContour(state.selectedSet.SeriesInstanceUID, ROINumber);
|
module.setters.toggleROIContour(
|
||||||
|
state.selectedSet.SeriesInstanceUID,
|
||||||
|
ROINumber
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -189,49 +212,70 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
|
|||||||
onClick={() => setShowSettings(true)}
|
onClick={() => setShowSettings(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
|
{!state.referencedDisplaysets.length && (
|
||||||
{state.sets && state.referencedDisplaysets.map(displaySet => {
|
<LoadingIndicator expand height="70px" width="70px" />
|
||||||
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
)}
|
||||||
|
{state.sets &&
|
||||||
|
state.referencedDisplaysets.map(displaySet => {
|
||||||
|
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
|
||||||
|
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||||
|
viewports[activeIndex].SeriesInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
const loadedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
|
const loadedSet = sets.find(
|
||||||
return (
|
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||||
<PanelSection
|
);
|
||||||
key={SeriesInstanceUID}
|
return (
|
||||||
title={metadata.StructureSetLabel}
|
<PanelSection
|
||||||
loading={!isLoaded || !loadedSet}
|
key={SeriesInstanceUID}
|
||||||
visible={isLoaded && loadedSet.visible}
|
title={metadata.StructureSetLabel}
|
||||||
hideVisibleButton={!isLoaded}
|
loading={!isLoaded || !loadedSet}
|
||||||
expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID}
|
visible={isLoaded && loadedSet.visible}
|
||||||
onVisibilityChange={newVisibility => {
|
hideVisibleButton={!isLoaded}
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
expanded={
|
||||||
loadedSet.ROIContours.forEach(({ ROINumber }) => {
|
isLoaded &&
|
||||||
module.setters.toggleROIContour(loadedSet.SeriesInstanceUID, ROINumber);
|
loadedSet.SeriesInstanceUID ===
|
||||||
});
|
state.selectedSet.SeriesInstanceUID
|
||||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
|
||||||
setState(state => ({ ...state, sets }));
|
|
||||||
refreshViewport();
|
|
||||||
}}
|
|
||||||
onExpandChange={async () => {
|
|
||||||
if (!isLoaded) {
|
|
||||||
await displaySet.load(viewports[activeIndex], studies);
|
|
||||||
const module = cornerstoneTools.getModule('rtstruct');
|
|
||||||
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
|
|
||||||
const selectedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
|
|
||||||
setState(state => ({ ...state, selectedSet, sets }));
|
|
||||||
}
|
}
|
||||||
}}
|
onVisibilityChange={newVisibility => {
|
||||||
>
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
<ScrollableArea>
|
loadedSet.ROIContours.forEach(({ ROINumber }) => {
|
||||||
<TableList headless>
|
module.setters.toggleROIContour(
|
||||||
{isLoaded && loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
loadedSet.SeriesInstanceUID,
|
||||||
</TableList>
|
ROINumber
|
||||||
</ScrollableArea>
|
);
|
||||||
</PanelSection>
|
});
|
||||||
);
|
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||||
})}
|
viewports[activeIndex].SeriesInstanceUID
|
||||||
|
);
|
||||||
|
setState(state => ({ ...state, sets }));
|
||||||
|
refreshViewport();
|
||||||
|
}}
|
||||||
|
onExpandChange={async () => {
|
||||||
|
if (!isLoaded) {
|
||||||
|
await displaySet.load(viewports[activeIndex], studies);
|
||||||
|
const module = cornerstoneTools.getModule('rtstruct');
|
||||||
|
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
|
||||||
|
viewports[activeIndex].SeriesInstanceUID
|
||||||
|
);
|
||||||
|
const selectedSet = sets.find(
|
||||||
|
set => set.SeriesInstanceUID === SeriesInstanceUID
|
||||||
|
);
|
||||||
|
setState(state => ({ ...state, selectedSet, sets }));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ScrollableArea>
|
||||||
|
<TableList headless>
|
||||||
|
{isLoaded &&
|
||||||
|
loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
|
||||||
|
</TableList>
|
||||||
|
</ScrollableArea>
|
||||||
|
</PanelSection>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,9 +15,7 @@ const RTSettings = ({ configuration, onBack, onChange }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="settings-section">
|
<div className="settings-section">
|
||||||
<div className="header">{title}</div>
|
<div className="header">{title}</div>
|
||||||
<div className="content">
|
<div className="content">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -61,8 +59,10 @@ const RTSettings = ({ configuration, onBack, onChange }) => {
|
|||||||
|
|
||||||
RTSettings.propTypes = {
|
RTSettings.propTypes = {
|
||||||
configuration: PropTypes.shape({
|
configuration: PropTypes.shape({
|
||||||
lineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
lineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||||
opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
.isRequired,
|
||||||
|
opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||||
|
.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
onBack: PropTypes.func.isRequired,
|
onBack: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const StructureSetItem = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsVisible(visible);
|
setIsVisible(visible);
|
||||||
}, [visible])
|
}, [visible]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
|
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
|
||||||
@ -99,7 +99,7 @@ StructureSetItem.propTypes = {
|
|||||||
|
|
||||||
StructureSetItem.defaultProps = {
|
StructureSetItem.defaultProps = {
|
||||||
itemClass: '',
|
itemClass: '',
|
||||||
onClick: () => { },
|
onClick: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StructureSetItem;
|
export default StructureSetItem;
|
||||||
|
|||||||
@ -68,7 +68,6 @@ export default async function loadRTStruct(
|
|||||||
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
|
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
|
||||||
|
|
||||||
for (let i = 0; i < ROIContourSequence.length; i++) {
|
for (let i = 0; i < ROIContourSequence.length; i++) {
|
||||||
|
|
||||||
const ROIContour = ROIContourSequence[i];
|
const ROIContour = ROIContourSequence[i];
|
||||||
const { ReferencedROINumber, ContourSequence } = ROIContour;
|
const { ReferencedROINumber, ContourSequence } = ROIContour;
|
||||||
|
|
||||||
|
|||||||
@ -70,9 +70,7 @@ export default class RTStructDisplayTool extends BaseTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const colorArray = ROIContourData.colorArray;
|
const colorArray = ROIContourData.colorArray;
|
||||||
const color = `rgba(${colorArray[0]},${colorArray[1]},${
|
const color = `rgba(${colorArray[0]},${colorArray[1]},${colorArray[2]},${opacity})`;
|
||||||
colorArray[2]
|
|
||||||
},${opacity})`;
|
|
||||||
|
|
||||||
lineWidth;
|
lineWidth;
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,6 @@ export default function structureSetReferencesSeriesInstanceUid(
|
|||||||
) {
|
) {
|
||||||
const { referencedSeriesSequence } = StructureSet;
|
const { referencedSeriesSequence } = StructureSet;
|
||||||
return referencedSeriesSequence.some(
|
return referencedSeriesSequence.some(
|
||||||
referencedSeries =>
|
referencedSeries => referencedSeries.SeriesInstanceUID === SeriesInstanceUID
|
||||||
referencedSeries.SeriesInstanceUID ===
|
|
||||||
SeriesInstanceUID
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -154,15 +154,10 @@ function setToggleROIContour(SeriesInstanceUID, ROINumber) {
|
|||||||
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
|
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
|
||||||
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
|
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
|
||||||
*/
|
*/
|
||||||
function getStructuresSetsWhichReferenceSeriesInstanceUid(
|
function getStructuresSetsWhichReferenceSeriesInstanceUid(SeriesInstanceUID) {
|
||||||
SeriesInstanceUID
|
|
||||||
) {
|
|
||||||
const { StructureSets } = state;
|
const { StructureSets } = state;
|
||||||
return StructureSets.filter(StructureSet =>
|
return StructureSets.filter(StructureSet =>
|
||||||
structureSetReferencesSeriesInstanceUid(
|
structureSetReferencesSeriesInstanceUid(StructureSet, SeriesInstanceUID)
|
||||||
StructureSet,
|
|
||||||
SeriesInstanceUID
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,12 +12,8 @@ const BrushColorSelector = ({ defaultColor, index, onNext, onPrev }) => (
|
|||||||
{index}
|
{index}
|
||||||
</div>
|
</div>
|
||||||
<div className="selector-buttons">
|
<div className="selector-buttons">
|
||||||
<button onClick={onPrev}>
|
<button onClick={onPrev}>Previous</button>
|
||||||
Previous
|
<button onClick={onNext}>Next</button>
|
||||||
</button>
|
|
||||||
<button onClick={onNext}>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ BrushRadius.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
BrushRadius.defaultProps = {
|
BrushRadius.defaultProps = {
|
||||||
onChange: () => { },
|
onChange: () => {},
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 50,
|
max: 50,
|
||||||
step: 1,
|
step: 1,
|
||||||
|
|||||||
@ -17,7 +17,15 @@ ColoredCircle.propTypes = {
|
|||||||
color: PropTypes.array.isRequired,
|
color: PropTypes.array.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true, onVisibilityChange }) => {
|
const SegmentItem = ({
|
||||||
|
index,
|
||||||
|
label,
|
||||||
|
onClick,
|
||||||
|
itemClass,
|
||||||
|
color,
|
||||||
|
visible = true,
|
||||||
|
onVisibilityChange,
|
||||||
|
}) => {
|
||||||
const [isVisible, setIsVisible] = useState(visible);
|
const [isVisible, setIsVisible] = useState(visible);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -61,7 +69,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
|
|||||||
<span style={{ marginRight: '4px' }}>
|
<span style={{ marginRight: '4px' }}>
|
||||||
<Icon name="edit" width="14px" height="14px" />
|
<Icon name="edit" width="14px" height="14px" />
|
||||||
</span>
|
</span>
|
||||||
Relabel
|
Relabel
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btnAction"
|
className="btnAction"
|
||||||
@ -70,7 +78,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
|
|||||||
<span style={{ marginRight: '4px' }}>
|
<span style={{ marginRight: '4px' }}>
|
||||||
<Icon name="edit" width="14px" height="14px" />
|
<Icon name="edit" width="14px" height="14px" />
|
||||||
</span>
|
</span>
|
||||||
Description
|
Description
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -90,7 +98,7 @@ SegmentItem.propTypes = {
|
|||||||
|
|
||||||
SegmentItem.defaultProps = {
|
SegmentItem.defaultProps = {
|
||||||
itemClass: '',
|
itemClass: '',
|
||||||
onClick: () => { }
|
onClick: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SegmentItem;
|
export default SegmentItem;
|
||||||
|
|||||||
@ -21,7 +21,7 @@ SegmentationItem.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SegmentationItem.defaultProps = {
|
SegmentationItem.defaultProps = {
|
||||||
description: ''
|
description: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SegmentationItem;
|
export default SegmentationItem;
|
||||||
|
|||||||
@ -68,8 +68,7 @@ const SegmentationPanel = ({
|
|||||||
*/
|
*/
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
brushRadius: DEFAULT_BRUSH_RADIUS,
|
brushRadius: DEFAULT_BRUSH_RADIUS,
|
||||||
brushColor:
|
brushColor: 'rgba(221, 85, 85, 1)',
|
||||||
'rgba(221, 85, 85, 1)',
|
|
||||||
selectedSegment: null,
|
selectedSegment: null,
|
||||||
selectedSegmentation: null,
|
selectedSegmentation: null,
|
||||||
showSegmentationSettings: false,
|
showSegmentationSettings: false,
|
||||||
@ -77,7 +76,7 @@ const SegmentationPanel = ({
|
|||||||
labelmapList: [],
|
labelmapList: [],
|
||||||
segmentList: [],
|
segmentList: [],
|
||||||
cachedSegmentsProperties: [],
|
cachedSegmentsProperties: [],
|
||||||
isLoading: false
|
isLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -111,7 +110,10 @@ const SegmentationPanel = ({
|
|||||||
* allows us to easily watch the module or the segmentations loading process in any other component
|
* allows us to easily watch the module or the segmentations loading process in any other component
|
||||||
* without subscribing to external events.
|
* without subscribing to external events.
|
||||||
*/
|
*/
|
||||||
document.addEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations);
|
document.addEventListener(
|
||||||
|
'extensiondicomsegmentationsegloaded',
|
||||||
|
refreshSegmentations
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These are specific to each element;
|
* These are specific to each element;
|
||||||
@ -126,7 +128,10 @@ const SegmentationPanel = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations);
|
document.removeEventListener(
|
||||||
|
'extensiondicomsegmentationsegloaded',
|
||||||
|
refreshSegmentations
|
||||||
|
);
|
||||||
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
|
||||||
enabledElement.removeEventListener(
|
enabledElement.removeEventListener(
|
||||||
'cornerstonetoolslabelmapmodified',
|
'cornerstonetoolslabelmapmodified',
|
||||||
@ -173,15 +178,17 @@ const SegmentationPanel = ({
|
|||||||
segmentList: [],
|
segmentList: [],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}, [
|
}, [viewports, activeIndex, state.isLoading]);
|
||||||
viewports,
|
|
||||||
activeIndex,
|
|
||||||
state.isLoading
|
|
||||||
]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
refreshSegmentations();
|
refreshSegmentations();
|
||||||
}, [viewports, activeIndex, state.selectedSegmentation, activeContexts, state.isLoading]);
|
}, [
|
||||||
|
viewports,
|
||||||
|
activeIndex,
|
||||||
|
state.selectedSegmentation,
|
||||||
|
activeContexts,
|
||||||
|
state.isLoading,
|
||||||
|
]);
|
||||||
|
|
||||||
/* Handle open/closed panel behaviour */
|
/* Handle open/closed panel behaviour */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -369,9 +376,13 @@ const SegmentationPanel = ({
|
|||||||
return !segmentsHidden[segmentIndex];
|
return !segmentsHidden[segmentIndex];
|
||||||
};
|
};
|
||||||
|
|
||||||
const cachedSegmentProperties = state.cachedSegmentsProperties[segmentNumber];
|
const cachedSegmentProperties =
|
||||||
|
state.cachedSegmentsProperties[segmentNumber];
|
||||||
let visible = isSegmentVisible();
|
let visible = isSegmentVisible();
|
||||||
if (cachedSegmentProperties && cachedSegmentProperties.visible !== visible) {
|
if (
|
||||||
|
cachedSegmentProperties &&
|
||||||
|
cachedSegmentProperties.visible !== visible
|
||||||
|
) {
|
||||||
toggleSegmentVisibility();
|
toggleSegmentVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +410,9 @@ const SegmentationPanel = ({
|
|||||||
onSegmentVisibilityChange(segmentNumber, newVisibility);
|
onSegmentVisibilityChange(segmentNumber, newVisibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCachedSegmentsProperties(segmentNumber, { visible: newVisibility });
|
updateCachedSegmentsProperties(segmentNumber, {
|
||||||
|
visible: newVisibility,
|
||||||
|
});
|
||||||
refreshViewport();
|
refreshViewport();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -422,10 +435,9 @@ const SegmentationPanel = ({
|
|||||||
const segmentsProperties = state.cachedSegmentsProperties;
|
const segmentsProperties = state.cachedSegmentsProperties;
|
||||||
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
|
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
|
||||||
|
|
||||||
segmentsProperties[segmentNumber] =
|
segmentsProperties[segmentNumber] = segmentProperties
|
||||||
segmentProperties ?
|
? { ...segmentProperties, ...properties }
|
||||||
{ ...segmentProperties, ...properties } :
|
: properties;
|
||||||
properties;
|
|
||||||
|
|
||||||
updateState('cachedSegmentsProperties', segmentsProperties);
|
updateState('cachedSegmentsProperties', segmentsProperties);
|
||||||
};
|
};
|
||||||
@ -495,7 +507,10 @@ const SegmentationPanel = ({
|
|||||||
refreshViewport();
|
refreshViewport();
|
||||||
};
|
};
|
||||||
|
|
||||||
const disabledConfigurationFields = ['outlineAlpha', 'shouldRenderInactiveLabelmaps'];
|
const disabledConfigurationFields = [
|
||||||
|
'outlineAlpha',
|
||||||
|
'shouldRenderInactiveLabelmaps',
|
||||||
|
];
|
||||||
if (state.showSegmentationSettings) {
|
if (state.showSegmentationSettings) {
|
||||||
return (
|
return (
|
||||||
<SegmentationSettings
|
<SegmentationSettings
|
||||||
@ -622,7 +637,7 @@ const _setActiveLabelmap = async (
|
|||||||
displaySet,
|
displaySet,
|
||||||
firstImageId,
|
firstImageId,
|
||||||
activeLabelmapIndex,
|
activeLabelmapIndex,
|
||||||
callback = () => { },
|
callback = () => {},
|
||||||
onDisplaySetLoadFailure
|
onDisplaySetLoadFailure
|
||||||
) => {
|
) => {
|
||||||
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
if (displaySet.labelmapIndex === activeLabelmapIndex) {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
|
|||||||
const segmentationSelectStyles = {
|
const segmentationSelectStyles = {
|
||||||
singleValue: (base, state) => ({
|
singleValue: (base, state) => ({
|
||||||
...base,
|
...base,
|
||||||
width: '100%'
|
width: '100%',
|
||||||
}),
|
}),
|
||||||
control: (base, state) => ({
|
control: (base, state) => ({
|
||||||
...base,
|
...base,
|
||||||
|
|||||||
@ -4,7 +4,12 @@ import { Range } from '@ohif/ui';
|
|||||||
|
|
||||||
import './SegmentationSettings.css';
|
import './SegmentationSettings.css';
|
||||||
|
|
||||||
const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => {
|
const SegmentationSettings = ({
|
||||||
|
configuration,
|
||||||
|
onBack,
|
||||||
|
onChange,
|
||||||
|
disabledFields = [],
|
||||||
|
}) => {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
renderFill: configuration.renderFill,
|
renderFill: configuration.renderFill,
|
||||||
renderOutline: configuration.renderOutline,
|
renderOutline: configuration.renderOutline,
|
||||||
@ -13,7 +18,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields
|
|||||||
outlineAlpha: configuration.outlineAlpha,
|
outlineAlpha: configuration.outlineAlpha,
|
||||||
outlineWidth: configuration.outlineWidth,
|
outlineWidth: configuration.outlineWidth,
|
||||||
fillAlphaInactive: configuration.fillAlphaInactive,
|
fillAlphaInactive: configuration.fillAlphaInactive,
|
||||||
outlineAlphaInactive: configuration.outlineAlphaInactive
|
outlineAlphaInactive: configuration.outlineAlphaInactive,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -78,7 +83,9 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields
|
|||||||
step={1}
|
step={1}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
|
onChange={event =>
|
||||||
|
save('outlineAlpha', toFloat(event.target.value))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!disabledFields.includes('outlineWidth') && (
|
{!disabledFields.includes('outlineWidth') && (
|
||||||
@ -89,50 +96,64 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields
|
|||||||
step={1}
|
step={1}
|
||||||
min={0}
|
min={0}
|
||||||
max={5}
|
max={5}
|
||||||
onChange={event => save('outlineWidth', parseInt(event.target.value))}
|
onChange={event =>
|
||||||
|
save('outlineWidth', parseInt(event.target.value))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{(state.renderFill || state.renderOutline) && !disabledFields.includes('shouldRenderInactiveLabelmaps') && (
|
{(state.renderFill || state.renderOutline) &&
|
||||||
<div
|
!disabledFields.includes('shouldRenderInactiveLabelmaps') && (
|
||||||
className="settings-group"
|
<div
|
||||||
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
|
className="settings-group"
|
||||||
>
|
style={{
|
||||||
<CustomCheck
|
marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0,
|
||||||
label="Render inactive segmentations"
|
}}
|
||||||
checked={state.shouldRenderInactiveLabelmaps}
|
>
|
||||||
onChange={() => check('shouldRenderInactiveLabelmaps')}
|
<CustomCheck
|
||||||
/>
|
label="Render inactive segmentations"
|
||||||
{state.shouldRenderInactiveLabelmaps && (
|
checked={state.shouldRenderInactiveLabelmaps}
|
||||||
<>
|
onChange={() => check('shouldRenderInactiveLabelmaps')}
|
||||||
{state.renderFill && !disabledFields.includes('fillAlphaInactive') && (
|
/>
|
||||||
<CustomRange
|
{state.shouldRenderInactiveLabelmaps && (
|
||||||
label="Fill Opacity"
|
<>
|
||||||
showPercentage
|
{state.renderFill &&
|
||||||
step={1}
|
!disabledFields.includes('fillAlphaInactive') && (
|
||||||
min={0}
|
<CustomRange
|
||||||
max={100}
|
label="Fill Opacity"
|
||||||
value={state.fillAlphaInactive * 100}
|
showPercentage
|
||||||
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
|
step={1}
|
||||||
/>
|
min={0}
|
||||||
)}
|
max={100}
|
||||||
{state.renderOutline && !disabledFields.includes('outlineAlphaInactive') && (
|
value={state.fillAlphaInactive * 100}
|
||||||
<CustomRange
|
onChange={event =>
|
||||||
label="Outline Opacity"
|
save('fillAlphaInactive', toFloat(event.target.value))
|
||||||
showPercentage
|
}
|
||||||
step={1}
|
/>
|
||||||
min={0}
|
)}
|
||||||
max={100}
|
{state.renderOutline &&
|
||||||
value={state.outlineAlphaInactive * 100}
|
!disabledFields.includes('outlineAlphaInactive') && (
|
||||||
onChange={event => save('outlineAlphaInactive', toFloat(event.target.value))}
|
<CustomRange
|
||||||
/>
|
label="Outline Opacity"
|
||||||
)}
|
showPercentage
|
||||||
</>
|
step={1}
|
||||||
)}
|
min={0}
|
||||||
</div>
|
max={100}
|
||||||
)}
|
value={state.outlineAlphaInactive * 100}
|
||||||
|
onChange={event =>
|
||||||
|
save(
|
||||||
|
'outlineAlphaInactive',
|
||||||
|
toFloat(event.target.value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -169,8 +190,10 @@ SegmentationSettings.propTypes = {
|
|||||||
renderFill: PropTypes.bool.isRequired,
|
renderFill: PropTypes.bool.isRequired,
|
||||||
renderOutline: PropTypes.bool.isRequired,
|
renderOutline: PropTypes.bool.isRequired,
|
||||||
shouldRenderInactiveLabelmaps: PropTypes.bool.isRequired,
|
shouldRenderInactiveLabelmaps: PropTypes.bool.isRequired,
|
||||||
fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
|
fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||||
outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
|
.isRequired /* TODO: why fillAlpha is string? */,
|
||||||
|
outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||||
|
.isRequired /* TODO: why fillAlpha is string? */,
|
||||||
outlineWidth: PropTypes.number.isRequired,
|
outlineWidth: PropTypes.number.isRequired,
|
||||||
fillAlphaInactive: PropTypes.number.isRequired,
|
fillAlphaInactive: PropTypes.number.isRequired,
|
||||||
outlineAlphaInactive: PropTypes.number.isRequired,
|
outlineAlphaInactive: PropTypes.number.isRequired,
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export default {
|
|||||||
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
|
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
|
||||||
commandsManager.runCommand('setSegmentConfiguration', {
|
commandsManager.runCommand('setSegmentConfiguration', {
|
||||||
segmentNumber,
|
segmentNumber,
|
||||||
visible
|
visible,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ export default {
|
|||||||
globalOpacity: configuration.fillAlpha,
|
globalOpacity: configuration.fillAlpha,
|
||||||
outlineThickness: configuration.outlineWidth,
|
outlineThickness: configuration.outlineWidth,
|
||||||
renderOutline: configuration.renderOutline,
|
renderOutline: configuration.renderOutline,
|
||||||
visible: configuration.renderFill
|
visible: configuration.renderFill,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -32,16 +32,16 @@ class LoadingIndicator extends PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="imageViewerLoadingIndicator loadingIndicator">
|
<div className="imageViewerLoadingIndicator loadingIndicator">
|
||||||
<div className="indicatorContents">
|
<div className="indicatorContents">
|
||||||
<p>
|
<p>
|
||||||
{this.props.t('Loading...')}
|
{this.props.t('Loading...')}
|
||||||
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
|
||||||
{percComplete}
|
{percComplete}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
state = {
|
state = {
|
||||||
volumes: null,
|
volumes: null,
|
||||||
paintFilterLabelMapImageData: null,
|
paintFilterLabelMapImageData: null,
|
||||||
paintFilterBackgroundImageData: null
|
paintFilterBackgroundImageData: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
onScroll: () => { },
|
onScroll: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
static id = 'OHIFVTKViewport';
|
static id = 'OHIFVTKViewport';
|
||||||
@ -156,9 +156,11 @@ class OHIFVTKViewport extends Component {
|
|||||||
const { activeLabelmapIndex } = brushStackState;
|
const { activeLabelmapIndex } = brushStackState;
|
||||||
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
|
||||||
|
|
||||||
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => {
|
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
|
||||||
return { visible: !isHidden };
|
isHidden => {
|
||||||
});
|
return { visible: !isHidden };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
|
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
|
||||||
|
|
||||||
@ -349,7 +351,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
displaySet.displaySetInstanceUID !==
|
displaySet.displaySetInstanceUID !==
|
||||||
prevDisplaySet.displaySetInstanceUID ||
|
prevDisplaySet.displaySetInstanceUID ||
|
||||||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
|
||||||
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
displaySet.frameIndex !== prevDisplaySet.frameIndex
|
||||||
) {
|
) {
|
||||||
@ -435,7 +437,7 @@ class OHIFVTKViewport extends Component {
|
|||||||
segmentsDefaultProperties: this.segmentsDefaultProperties,
|
segmentsDefaultProperties: this.segmentsDefaultProperties,
|
||||||
onNewSegmentationRequested: () => {
|
onNewSegmentationRequested: () => {
|
||||||
this.setStateFromProps();
|
this.setStateFromProps();
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
onScroll={this.props.onScroll}
|
onScroll={this.props.onScroll}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -165,7 +165,7 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
segmentNumber,
|
segmentNumber,
|
||||||
frameIndex,
|
frameIndex,
|
||||||
frame,
|
frame,
|
||||||
done = () => { }
|
done = () => {},
|
||||||
}) => {
|
}) => {
|
||||||
let api = apis[viewports.activeViewportIndex];
|
let api = apis[viewports.activeViewportIndex];
|
||||||
|
|
||||||
@ -180,10 +180,13 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
displaySetInstanceUID,
|
displaySetInstanceUID,
|
||||||
SOPClassUID,
|
SOPClassUID,
|
||||||
SOPInstanceUID,
|
SOPInstanceUID,
|
||||||
frameIndex,
|
frameIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
|
const imageDataObject = getImageData(
|
||||||
|
stack.imageIds,
|
||||||
|
displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
let pixelIndex = 0;
|
let pixelIndex = 0;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
@ -209,7 +212,10 @@ const commandsModule = ({ commandsManager }) => {
|
|||||||
y /= count;
|
y /= count;
|
||||||
|
|
||||||
const position = [x, y, frameIndex];
|
const position = [x, y, frameIndex];
|
||||||
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
|
const worldPos = _convertModelToWorldSpace(
|
||||||
|
position,
|
||||||
|
imageDataObject.vtkImageData
|
||||||
|
);
|
||||||
|
|
||||||
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
|
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
|
||||||
done();
|
done();
|
||||||
|
|||||||
@ -8,10 +8,13 @@ const { studyMetadataManager } = utils;
|
|||||||
|
|
||||||
let isVisible = true;
|
let isVisible = true;
|
||||||
|
|
||||||
const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
|
const _isDisplaySetReconstructable = (
|
||||||
|
viewportSpecificData = {},
|
||||||
|
activeViewportIndex
|
||||||
|
) => {
|
||||||
if (!viewportSpecificData[activeViewportIndex]) {
|
if (!viewportSpecificData[activeViewportIndex]) {
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
|
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
|
||||||
activeViewportIndex
|
activeViewportIndex
|
||||||
@ -27,11 +30,13 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID);
|
const displaySet = study._displaySets.find(
|
||||||
|
set => set.displaySetInstanceUID === displaySetInstanceUID
|
||||||
|
);
|
||||||
|
|
||||||
if (!displaySet) {
|
if (!displaySet) {
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
return displaySet.isReconstructable;
|
return displaySet.isReconstructable;
|
||||||
};
|
};
|
||||||
@ -52,12 +57,12 @@ function VTKMPRToolbarButton({
|
|||||||
return {
|
return {
|
||||||
viewportSpecificData,
|
viewportSpecificData,
|
||||||
activeViewportIndex,
|
activeViewportIndex,
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
isVisible = _isDisplaySetReconstructable(
|
isVisible = _isDisplaySetReconstructable(
|
||||||
viewportSpecificData,
|
viewportSpecificData,
|
||||||
activeViewportIndex,
|
activeViewportIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -131,4 +131,4 @@ const definitions = [
|
|||||||
export default {
|
export default {
|
||||||
definitions,
|
definitions,
|
||||||
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
defaultContext: 'ACTIVE_VIEWPORT::VTK',
|
||||||
}
|
};
|
||||||
|
|||||||
@ -39,7 +39,8 @@
|
|||||||
"@babel/runtime": "7.7.6",
|
"@babel/runtime": "7.7.6",
|
||||||
"ajv": "^6.10.0",
|
"ajv": "^6.10.0",
|
||||||
"dcmjs": "^0.12.4",
|
"dcmjs": "^0.12.4",
|
||||||
"dicomweb-client": "^0.5.2",
|
"dicomweb-client": "^0.6.0",
|
||||||
|
"immer": "6.0.2",
|
||||||
"isomorphic-base64": "^1.0.2",
|
"isomorphic-base64": "^1.0.2",
|
||||||
"lodash.clonedeep": "^4.5.0",
|
"lodash.clonedeep": "^4.5.0",
|
||||||
"lodash.merge": "^4.6.1",
|
"lodash.merge": "^4.6.1",
|
||||||
|
|||||||
@ -79,7 +79,7 @@ export class StudyMetadata extends Metadata {
|
|||||||
Object.defineProperty(this, 'studyInstanceUID', {
|
Object.defineProperty(this, 'studyInstanceUID', {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
get: function () {
|
get: function() {
|
||||||
return this.getStudyInstanceUID();
|
return this.getStudyInstanceUID();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -182,7 +182,7 @@ export default class ExtensionManager {
|
|||||||
commandsManager: this._commandsManager,
|
commandsManager: this._commandsManager,
|
||||||
appConfig: this._appConfig,
|
appConfig: this._appConfig,
|
||||||
configuration,
|
configuration,
|
||||||
api: this._api
|
api: this._api,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!extensionModule) {
|
if (!extensionModule) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import classes, { CommandsManager, HotkeysManager } from './classes/';
|
|||||||
import DICOMWeb from './DICOMWeb';
|
import DICOMWeb from './DICOMWeb';
|
||||||
import DICOMSR from './DICOMSR';
|
import DICOMSR from './DICOMSR';
|
||||||
import cornerstone from './cornerstone.js';
|
import cornerstone from './cornerstone.js';
|
||||||
|
import errorHandler from './errorHandler.js';
|
||||||
import hangingProtocols from './hanging-protocols';
|
import hangingProtocols from './hanging-protocols';
|
||||||
import header from './header.js';
|
import header from './header.js';
|
||||||
import log from './log.js';
|
import log from './log.js';
|
||||||
|
|||||||
@ -2,7 +2,12 @@ import redux from './index.js';
|
|||||||
|
|
||||||
describe('redux exports', () => {
|
describe('redux exports', () => {
|
||||||
test('have not changed', () => {
|
test('have not changed', () => {
|
||||||
const expectedExports = ['actions', 'reducers', 'localStorage', 'sessionStorage'].sort();
|
const expectedExports = [
|
||||||
|
'actions',
|
||||||
|
'reducers',
|
||||||
|
'localStorage',
|
||||||
|
'sessionStorage',
|
||||||
|
].sort();
|
||||||
|
|
||||||
const exports = Object.keys(redux).sort();
|
const exports = Object.keys(redux).sort();
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,10 @@ describe('MeasurementService.js', () => {
|
|||||||
unit: 'mm',
|
unit: 'mm',
|
||||||
area: 123,
|
area: 123,
|
||||||
type: measurementService.VALUE_TYPES.POLYLINE,
|
type: measurementService.VALUE_TYPES.POLYLINE,
|
||||||
points: [{ x: 1, y: 2 }, { x: 1, y: 2 }],
|
points: [
|
||||||
|
{ x: 1, y: 2 },
|
||||||
|
{ x: 1, y: 2 },
|
||||||
|
],
|
||||||
source: source,
|
source: source,
|
||||||
};
|
};
|
||||||
toAnnotation = () => annotation;
|
toAnnotation = () => annotation;
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
|
|
||||||
export default class Queue {
|
export default class Queue {
|
||||||
|
|
||||||
constructor(limit) {
|
constructor(limit) {
|
||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
this.size = 0;
|
this.size = 0;
|
||||||
|
|||||||
@ -167,8 +167,8 @@ function _getSpacingIssue(spacing, averageSpacing) {
|
|||||||
function _getPerpendicularDistance(a, b) {
|
function _getPerpendicularDistance(a, b) {
|
||||||
return Math.sqrt(
|
return Math.sqrt(
|
||||||
Math.pow(a[0] - b[0], 2) +
|
Math.pow(a[0] - b[0], 2) +
|
||||||
Math.pow(a[1] - b[1], 2) +
|
Math.pow(a[1] - b[1], 2) +
|
||||||
Math.pow(a[2] - b[2], 2)
|
Math.pow(a[2] - b[2], 2)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
export default function makeDeferred() {
|
export default function makeDeferred() {
|
||||||
let reject, resolve, promise = new Promise(function (res, rej) {
|
let reject,
|
||||||
resolve = res;
|
resolve,
|
||||||
reject = rej;
|
promise = new Promise(function(res, rej) {
|
||||||
});
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
return Object.freeze({ promise, resolve, reject });
|
return Object.freeze({ promise, resolve, reject });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,9 +116,8 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
|
|||||||
const bits = lutDescriptor[2];
|
const bits = lutDescriptor[2];
|
||||||
|
|
||||||
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
const arrayBufferToPaletteColorLUT = arraybuffer => {
|
||||||
const byteArray = bits === 16 ?
|
const byteArray =
|
||||||
new Uint16Array(arraybuffer) :
|
bits === 16 ? new Uint16Array(arraybuffer) : new Uint8Array(arraybuffer);
|
||||||
new Uint8Array(arraybuffer);
|
|
||||||
const lut = [];
|
const lut = [];
|
||||||
|
|
||||||
for (let i = 0; i < numLutEntries; i++) {
|
for (let i = 0; i < numLutEntries; i++) {
|
||||||
|
|||||||
@ -124,7 +124,7 @@ export default function getPixelSpacingInformation(instance) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(
|
log.info(
|
||||||
'Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.'
|
'Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'ar': {
|
ar: {
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import StudyList from './StudyList.json';
|
|||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
import ViewportDownloadForm from './ViewportDownloadForm.json';
|
import ViewportDownloadForm from './ViewportDownloadForm.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'en-US': {
|
'en-US': {
|
||||||
AboutModal,
|
AboutModal,
|
||||||
Buttons,
|
Buttons,
|
||||||
@ -21,5 +21,5 @@ export default {
|
|||||||
StudyList,
|
StudyList,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
ViewportDownloadForm,
|
ViewportDownloadForm,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import MeasurementTable from './MeasurementTable.json';
|
|||||||
import StudyList from './StudyList.json';
|
import StudyList from './StudyList.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'es': {
|
es: {
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
@ -15,5 +15,5 @@ export default {
|
|||||||
MeasurementTable,
|
MeasurementTable,
|
||||||
StudyList,
|
StudyList,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import Common from './Common.json';
|
|||||||
import Header from './Header.json';
|
import Header from './Header.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'fr': {
|
fr: {
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
Header,
|
Header,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import Common from './Common.json';
|
|||||||
import Header from './Header.json';
|
import Header from './Header.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'ja-JP': {
|
'ja-JP': {
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
Header,
|
Header,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import Buttons from './Buttons.json';
|
|||||||
import Common from './Common.json';
|
import Common from './Common.json';
|
||||||
import Header from './Header.json';
|
import Header from './Header.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'nl': {
|
nl: {
|
||||||
Buttons,
|
Buttons,
|
||||||
Common,
|
Common,
|
||||||
Header,
|
Header,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import DatePicker from './DatePicker.json';
|
|||||||
import Header from './Header.json';
|
import Header from './Header.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'pt-BR': {
|
'pt-BR': {
|
||||||
AboutModal,
|
AboutModal,
|
||||||
Buttons,
|
Buttons,
|
||||||
@ -15,5 +15,5 @@ export default {
|
|||||||
DatePicker,
|
DatePicker,
|
||||||
Header,
|
Header,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,13 +5,13 @@ import Header from './Header.json';
|
|||||||
import StudyList from './StudyList.json';
|
import StudyList from './StudyList.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'vi': {
|
vi: {
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
Header,
|
Header,
|
||||||
StudyList,
|
StudyList,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import MeasurementTable from './MeasurementTable.json';
|
|||||||
import StudyList from './StudyList.json';
|
import StudyList from './StudyList.json';
|
||||||
import UserPreferencesModal from './UserPreferencesModal.json';
|
import UserPreferencesModal from './UserPreferencesModal.json';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'zh': {
|
zh: {
|
||||||
Buttons,
|
Buttons,
|
||||||
CineDialog,
|
CineDialog,
|
||||||
Common,
|
Common,
|
||||||
@ -15,5 +15,5 @@ export default {
|
|||||||
MeasurementTable,
|
MeasurementTable,
|
||||||
StudyList,
|
StudyList,
|
||||||
UserPreferencesModal,
|
UserPreferencesModal,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,46 +1,3 @@
|
|||||||
<<<<<<< HEAD
|
|
||||||
# Basic Docz example
|
|
||||||
|
|
||||||
## Using `create-docz-app`
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npx create-docz-app docz-app-basic
|
|
||||||
# or
|
|
||||||
yarn create docz-app docz-app-basic
|
|
||||||
```
|
|
||||||
|
|
||||||
## Download manually
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl https://codeload.github.com/doczjs/docz/tar.gz/master | tar -xz --strip=2 docz-master/examples/basic
|
|
||||||
mv basic docz-basic-example
|
|
||||||
cd docz-basic-example
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn # npm i
|
|
||||||
```
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn dev # npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Build
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn build # npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Serve built app
|
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn serve # npm run serve
|
|
||||||
```
|
|
||||||
=======
|
|
||||||
# @ohif/ui
|
# @ohif/ui
|
||||||
|
|
||||||
React component library powered by `docz`
|
React component library powered by `docz`
|
||||||
@ -60,4 +17,3 @@ yarn build # npm run build
|
|||||||
# Serve (the "build" output)
|
# Serve (the "build" output)
|
||||||
yarn serve # npm run serve
|
yarn serve # npm run serve
|
||||||
```
|
```
|
||||||
>>>>>>> 4a8675655... feat: nuke UI project; start new with updated tooling BREAKING_CHANGE (#1468)
|
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
// ~~ Plugins
|
||||||
|
const PnpWebpackPlugin = require(`pnp-webpack-plugin`); // Required until Webpack@5
|
||||||
|
|
||||||
exports.onCreateWebpackConfig = args => {
|
exports.onCreateWebpackConfig = args => {
|
||||||
args.actions.setWebpackConfig({
|
args.actions.setWebpackConfig({
|
||||||
resolve: {
|
resolve: {
|
||||||
|
plugins: [PnpWebpackPlugin],
|
||||||
// Note the '..' in the path because docz gatsby project lives in the '.docz' directory
|
// Note the '..' in the path because docz gatsby project lives in the '.docz' directory
|
||||||
modules: [
|
modules: [
|
||||||
// monorepo root
|
// monorepo root
|
||||||
@ -16,5 +19,8 @@ exports.onCreateWebpackConfig = args => {
|
|||||||
// symlinks: true,
|
// symlinks: true,
|
||||||
// },
|
// },
|
||||||
},
|
},
|
||||||
|
resolveLoader: {
|
||||||
|
plugins: [PnpWebpackPlugin.moduleLoader(module)],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,10 +19,12 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "yarn run dev",
|
"start": "yarn run dev",
|
||||||
"build:styles": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css",
|
|
||||||
"dev": "docz dev",
|
|
||||||
"build": "docz build",
|
"build": "docz build",
|
||||||
"serve": "docz serve"
|
"build:ui:ci": "docz build",
|
||||||
|
"build:ui:deploy-preview": "docz build --base=/ui/",
|
||||||
|
"dev": "docz dev",
|
||||||
|
"serve": "docz serve",
|
||||||
|
"docs:preview": "yarn run dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
@ -6,17 +6,18 @@ const baseClasses =
|
|||||||
'leading-none font-sans text-center justify-center items-center outline-none transition duration-300 ease-in-out focus:outline-none';
|
'leading-none font-sans text-center justify-center items-center outline-none transition duration-300 ease-in-out focus:outline-none';
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
variant: 'contained',
|
|
||||||
color: 'default',
|
color: 'default',
|
||||||
size: 'medium',
|
|
||||||
radius: 'medium',
|
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
fullWidth: false,
|
||||||
|
rounded: 'medium',
|
||||||
|
size: 'medium',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
|
variant: 'contained',
|
||||||
};
|
};
|
||||||
|
|
||||||
const radiusClasses = {
|
const roundedClasses = {
|
||||||
none: '',
|
none: '',
|
||||||
small: 'rounded-sm',
|
small: 'rounded',
|
||||||
medium: 'rounded-md',
|
medium: 'rounded-md',
|
||||||
large: 'rounded-lg',
|
large: 'rounded-lg',
|
||||||
full: 'rounded-full',
|
full: 'rounded-full',
|
||||||
@ -77,7 +78,7 @@ const Button = ({
|
|||||||
variant = defaults.variant,
|
variant = defaults.variant,
|
||||||
color = defaults.color,
|
color = defaults.color,
|
||||||
size = defaults.size,
|
size = defaults.size,
|
||||||
radius = defaults.radius,
|
rounded = defaults.rounded,
|
||||||
disabled = defaults.disabled,
|
disabled = defaults.disabled,
|
||||||
type = defaults.type,
|
type = defaults.type,
|
||||||
fullWidth = defaults.fullWidth,
|
fullWidth = defaults.fullWidth,
|
||||||
@ -86,14 +87,24 @@ const Button = ({
|
|||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const baseClasses =
|
|
||||||
'inline-flex items-center outline-none transition duration-300 ease-in-out font-bold focus:outline-none';
|
|
||||||
|
|
||||||
const startIcon = startIconProp && (
|
const startIcon = startIconProp && (
|
||||||
<div className="mr-2">{startIconProp}</div>
|
<div className="mr-2">
|
||||||
|
{React.cloneElement(startIconProp, {
|
||||||
|
className: classnames('w-4 h-4 fill-current'),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnClick = e => {
|
const endIcon = endIconProp && (
|
||||||
|
<div className="ml-2">
|
||||||
|
{React.cloneElement(endIconProp, {
|
||||||
|
className: classnames('w-4 h-4 fill-current'),
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const buttonElement = useRef(null);
|
||||||
|
|
||||||
|
const handleOnClick = (e) => {
|
||||||
buttonElement.current.blur();
|
buttonElement.current.blur();
|
||||||
if (rest.onClick) {
|
if (rest.onClick) {
|
||||||
rest.onClick(e);
|
rest.onClick(e);
|
||||||
@ -106,11 +117,13 @@ const Button = ({
|
|||||||
className,
|
className,
|
||||||
baseClasses,
|
baseClasses,
|
||||||
variantClasses[variant][color],
|
variantClasses[variant][color],
|
||||||
radiusClasses[radius],
|
roundedClasses[rounded],
|
||||||
sizeClasses[size],
|
sizeClasses[size],
|
||||||
fullWidthClasses[fullWidth],
|
fullWidthClasses[fullWidth],
|
||||||
disabledClasses[disabled]
|
disabledClasses[disabled]
|
||||||
)}
|
)}
|
||||||
|
ref={buttonElement}
|
||||||
|
onClick={handleOnClick}
|
||||||
type={type}
|
type={type}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -24,10 +24,10 @@ import { Button } from '@ohif/ui';
|
|||||||
<Button variant="outlined" className="m-1">
|
<Button variant="outlined" className="m-1">
|
||||||
Default
|
Default
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" color="primary" className="m-1">
|
<Button variant="outlined" color="primary" className="m-1">
|
||||||
Primary
|
Primary
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" color="secondary" className="m-1">
|
<Button variant="outlined" color="secondary" className="m-1">
|
||||||
Secondary
|
Secondary
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" color="white" className="m-1">
|
<Button variant="outlined" color="white" className="m-1">
|
||||||
@ -36,17 +36,17 @@ import { Button } from '@ohif/ui';
|
|||||||
</div>
|
</div>
|
||||||
</Playground>
|
</Playground>
|
||||||
|
|
||||||
## Outlined Buttons
|
## Contained Buttons
|
||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Button variant="contained" className="m-1">
|
<Button variant="contained" className="m-1">
|
||||||
Default
|
Default
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" color="primary" className="m-1">
|
<Button variant="contained" color="primary" className="m-1">
|
||||||
Primary
|
Primary
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" color="secondary" className="m-1">
|
<Button variant="contained" color="secondary" className="m-1">
|
||||||
Secondary
|
Secondary
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" color="white" className="m-1">
|
<Button variant="contained" color="white" className="m-1">
|
||||||
@ -209,19 +209,19 @@ import { Button } from '@ohif/ui';
|
|||||||
<Playground>
|
<Playground>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div>
|
<div>
|
||||||
<Button variant="text" size="small" className="m-1">
|
<Button variant="contained" size="small" className="m-1">
|
||||||
Small
|
Small
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" size="medium" className="m-1">
|
<Button variant="contained" size="medium" className="m-1">
|
||||||
Medium
|
Medium
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outlined" size="large" className="m-1">
|
<Button variant="contained" size="large" className="m-1">
|
||||||
Large
|
Large
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
className="m-1"
|
className="m-1"
|
||||||
@ -237,39 +237,65 @@ import { Button } from '@ohif/ui';
|
|||||||
Medium
|
Medium
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="contained"
|
||||||
size="large"
|
size="large"
|
||||||
color="primary"
|
color="primary"
|
||||||
className="m-1"
|
className="m-1"
|
||||||
>
|
>
|
||||||
large
|
Large
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
className="m-1"
|
className="m-1"
|
||||||
>
|
>
|
||||||
Small
|
Small
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="medium"
|
size="medium"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
className="m-1"
|
className="m-1"
|
||||||
>
|
>
|
||||||
Medium
|
Medium
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="contained"
|
||||||
size="large"
|
size="large"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
className="m-1"
|
className="m-1"
|
||||||
>
|
>
|
||||||
Large
|
Large
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
color="white"
|
||||||
|
className="m-1"
|
||||||
|
>
|
||||||
|
Small
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="medium"
|
||||||
|
color="white"
|
||||||
|
className="m-1"
|
||||||
|
>
|
||||||
|
Medium
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
color="white"
|
||||||
|
className="m-1"
|
||||||
|
>
|
||||||
|
Large
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const renderYearsOptions = () => {
|
|||||||
return options;
|
return options;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DateRange = props => {
|
const DateRange = (props) => {
|
||||||
const { onChange, startDate, endDate } = props;
|
const { onChange, startDate, endDate } = props;
|
||||||
const [focusedInput, setFocusedInput] = useState(null);
|
const [focusedInput, setFocusedInput] = useState(null);
|
||||||
const renderYearsOptionsCallback = useCallback(renderYearsOptions, []);
|
const renderYearsOptionsCallback = useCallback(renderYearsOptions, []);
|
||||||
@ -82,11 +82,11 @@ const DateRange = props => {
|
|||||||
onYearSelect: PropTypes.func,
|
onYearSelect: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMonthChange = event => {
|
const handleMonthChange = (event) => {
|
||||||
onMonthSelect(month, event.target.value);
|
onMonthSelect(month, event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleYearChange = event => {
|
const handleYearChange = (event) => {
|
||||||
onYearSelect(month, event.target.value);
|
onYearSelect(month, event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ const DateRange = props => {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
focusedInput={focusedInput}
|
focusedInput={focusedInput}
|
||||||
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
|
onFocusChange={(updatedVal) => setFocusedInput(updatedVal)}
|
||||||
/** OPTIONAL */
|
/** OPTIONAL */
|
||||||
renderCalendarInfo={renderDatePresets}
|
renderCalendarInfo={renderDatePresets}
|
||||||
renderMonthElement={renderMonthElement}
|
renderMonthElement={renderMonthElement}
|
||||||
@ -150,7 +150,7 @@ const DateRange = props => {
|
|||||||
closeDatePicker: 'Close',
|
closeDatePicker: 'Close',
|
||||||
clearDates: 'Clear dates',
|
clearDates: 'Clear dates',
|
||||||
}}
|
}}
|
||||||
isOutsideRange={day => !isInclusivelyBeforeDay(day, moment())}
|
isOutsideRange={(day) => !isInclusivelyBeforeDay(day, moment())}
|
||||||
hideKeyboardShortcutsPanel={true}
|
hideKeyboardShortcutsPanel={true}
|
||||||
numberOfMonths={1}
|
numberOfMonths={1}
|
||||||
showClearDates={false}
|
showClearDates={false}
|
||||||
|
|||||||
@ -7,13 +7,13 @@ import { ButtonGroup, Button, StudyItem, ThumbnailList } from '@ohif/ui';
|
|||||||
const buttonClasses = 'text-white text-base border-none bg-black p-2 min-w-18';
|
const buttonClasses = 'text-white text-base border-none bg-black p-2 min-w-18';
|
||||||
const activeButtonClasses = 'bg-primary-main';
|
const activeButtonClasses = 'bg-primary-main';
|
||||||
|
|
||||||
const getInitialActiveTab = tabs => {
|
const getInitialActiveTab = (tabs) => {
|
||||||
return tabs && tabs[0] && tabs[0].name;
|
return tabs && tabs[0] && tabs[0].name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTrackedSeries = displaySets => {
|
const getTrackedSeries = (displaySets) => {
|
||||||
let trackedSeries = 0;
|
let trackedSeries = 0;
|
||||||
displaySets.forEach(displaySet => {
|
displaySets.forEach((displaySet) => {
|
||||||
if (displaySet.isTracked) {
|
if (displaySet.isTracked) {
|
||||||
trackedSeries++;
|
trackedSeries++;
|
||||||
}
|
}
|
||||||
@ -25,9 +25,10 @@ const getTrackedSeries = displaySets => {
|
|||||||
const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
|
const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
|
||||||
const [tabActive, setTabActive] = useState(getInitialActiveTab(tabs));
|
const [tabActive, setTabActive] = useState(getInitialActiveTab(tabs));
|
||||||
const [studyActive, setStudyActive] = useState(null);
|
const [studyActive, setStudyActive] = useState(null);
|
||||||
|
const [thumbnailActive, setThumbnailActive] = useState(null);
|
||||||
|
|
||||||
const getTabContent = () => {
|
const getTabContent = () => {
|
||||||
const tabData = tabs.find(tab => tab.name === tabActive);
|
const tabData = tabs.find((tab) => tab.name === tabActive);
|
||||||
|
|
||||||
if (!tabData || !tabData.studies || !Array.isArray(tabData.studies)) {
|
if (!tabData || !tabData.studies || !Array.isArray(tabData.studies)) {
|
||||||
return;
|
return;
|
||||||
@ -36,9 +37,9 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
|
|||||||
return tabData.studies.map(
|
return tabData.studies.map(
|
||||||
({
|
({
|
||||||
studyInstanceUid,
|
studyInstanceUid,
|
||||||
studyDate,
|
date,
|
||||||
studyDescription,
|
description,
|
||||||
instances,
|
numInstances,
|
||||||
modalities,
|
modalities,
|
||||||
displaySets,
|
displaySets,
|
||||||
}) => {
|
}) => {
|
||||||
@ -46,9 +47,9 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment key={studyInstanceUid}>
|
<React.Fragment key={studyInstanceUid}>
|
||||||
<StudyItem
|
<StudyItem
|
||||||
studyDate={studyDate}
|
date={date}
|
||||||
studyDescription={studyDescription}
|
description={description}
|
||||||
instances={instances}
|
numInstances={numInstances}
|
||||||
modalities={modalities}
|
modalities={modalities}
|
||||||
trackedSeries={getTrackedSeries(displaySets)}
|
trackedSeries={getTrackedSeries(displaySets)}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
@ -64,16 +65,17 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
|
|||||||
<ThumbnailList
|
<ThumbnailList
|
||||||
thumbnails={displaySets}
|
thumbnails={displaySets}
|
||||||
thumbnailActive={thumbnailActive}
|
thumbnailActive={thumbnailActive}
|
||||||
onThumbnailClick={thumbnailId => {
|
onThumbnailClick={(thumbnailId) => {
|
||||||
setThumbnailActive(
|
setThumbnailActive(
|
||||||
thumbnailId === thumbnailActive ? null : thumbnailId
|
thumbnailId === thumbnailActive ? null : thumbnailId
|
||||||
);
|
)
|
||||||
|
|
||||||
if (onClickThumbnail) {
|
if (onClickThumbnail) {
|
||||||
// TODO: what is thumbnailId? Should pass display set instead
|
// TODO: what is thumbnailId? Should pass display set instead
|
||||||
onClickThumbnail(thumbnailId);
|
onClickThumbnail(thumbnailId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@ -90,7 +92,7 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
|
|||||||
color="inherit"
|
color="inherit"
|
||||||
className="border border-secondary-light rounded-md"
|
className="border border-secondary-light rounded-md"
|
||||||
>
|
>
|
||||||
{tabs.map(tab => {
|
{tabs.map((tab) => {
|
||||||
const { name, label } = tab;
|
const { name, label } = tab;
|
||||||
const isActive = tabActive === name;
|
const isActive = tabActive === name;
|
||||||
return (
|
return (
|
||||||
@ -131,7 +133,7 @@ StudyBrowser.propTypes = {
|
|||||||
studyInstanceUid: PropTypes.string.isRequired,
|
studyInstanceUid: PropTypes.string.isRequired,
|
||||||
date: PropTypes.string,
|
date: PropTypes.string,
|
||||||
numInstances: PropTypes.number,
|
numInstances: PropTypes.number,
|
||||||
modality: PropTypes.string,
|
modalities: PropTypes.string,
|
||||||
description: PropTypes.string,
|
description: PropTypes.string,
|
||||||
displaySets: PropTypes.arrayOf(
|
displaySets: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
|
|||||||
@ -18,7 +18,88 @@ import { StudyBrowser } from '@ohif/ui';
|
|||||||
## Basic usage
|
## Basic usage
|
||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<StudyBrowser />
|
{() => {
|
||||||
|
const studyWithSR = {
|
||||||
|
studyInstanceUid: '1',
|
||||||
|
date: '07-Sept-2010',
|
||||||
|
description: 'CHEST/ABD/PELVIS W/CONTRAST',
|
||||||
|
numInstances: 902,
|
||||||
|
modalities: 'CT,SR',
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f6asdasd48c-223e-db7f-c4af-b8906641a66e',
|
||||||
|
description: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
numInstances: 68,
|
||||||
|
componentType: 'thumbnailTracked',
|
||||||
|
viewportIdentificator: 'A',
|
||||||
|
isTracked: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-db7f-c4asdasdaf-b8906641a66e',
|
||||||
|
description: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
numInstances: 68,
|
||||||
|
componentType: 'thumbnailTracked',
|
||||||
|
viewportIdentificator: 'B',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-dasdasdb7f-c4af-b8906641a66e',
|
||||||
|
description: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
numInstances: 68,
|
||||||
|
componentType: 'thumbnailTracked',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-db7f-c4afas-b8906asd641a66e',
|
||||||
|
description: 'Multiple line description lorem ipsum dolor sit amet',
|
||||||
|
modality: 'SR',
|
||||||
|
componentType: 'thumbnailNoImage',
|
||||||
|
seriesDate: '07-Sept-2010',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const studySimple = {
|
||||||
|
studyInstanceUid: '2',
|
||||||
|
date: '07-Sept-2010',
|
||||||
|
description: 'CHEST/ABD/PELVIS W/CONTRAST',
|
||||||
|
numInstances: 902,
|
||||||
|
modalities: 'CT',
|
||||||
|
displaySets: [
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-db7f-c4af-b8906641a66e',
|
||||||
|
description: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
numInstances: 68,
|
||||||
|
componentType: 'thumbnailTracked',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
name: 'primary',
|
||||||
|
label: 'Primary',
|
||||||
|
studies: [studySimple],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'recent',
|
||||||
|
label: 'Recent',
|
||||||
|
studies: [studyWithSR, studySimple],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'all',
|
||||||
|
label: 'All',
|
||||||
|
studies: [studySimple, studyWithSR],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div className="flex flex-1" style={{height: '400px'}}>
|
||||||
|
<div className="overflow-hidden w-64">
|
||||||
|
<StudyBrowser tabs={tabs} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</Playground>
|
</Playground>
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|||||||
@ -8,9 +8,9 @@ const baseClasses =
|
|||||||
'first:border-0 border-t border-secondary-light cursor-pointer outline-none';
|
'first:border-0 border-t border-secondary-light cursor-pointer outline-none';
|
||||||
|
|
||||||
const StudyItem = ({
|
const StudyItem = ({
|
||||||
studyDate,
|
date,
|
||||||
studyDescription,
|
description,
|
||||||
instances,
|
numInstances,
|
||||||
modalities,
|
modalities,
|
||||||
trackedSeries,
|
trackedSeries,
|
||||||
isActive,
|
isActive,
|
||||||
@ -29,15 +29,15 @@ const StudyItem = ({
|
|||||||
>
|
>
|
||||||
<div className="flex flex-col flex-1 px-4 pb-2">
|
<div className="flex flex-col flex-1 px-4 pb-2">
|
||||||
<div className="flex flex-row items-center justify-between pb-2 pt-2 pb-2">
|
<div className="flex flex-row items-center justify-between pb-2 pt-2 pb-2">
|
||||||
<div className="text-white text-base">{studyDate}</div>
|
<div className="text-white text-base">{date}</div>
|
||||||
<div className="flex flex-row items-center text-blue-300 text-base">
|
<div className="flex flex-row items-center text-blue-300 text-base">
|
||||||
<Icon name="group-layers" className="text-blue-300 mx-2 w-4" />
|
<Icon name="group-layers" className="text-blue-300 mx-2 w-4" />
|
||||||
{instances}
|
{numInstances}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row py-1">
|
<div className="flex flex-row py-1">
|
||||||
<div className="text-blue-300 pr-5 text-xl">{modalities}</div>
|
<div className="text-blue-300 pr-5 text-xl">{modalities}</div>
|
||||||
<div className="text-blue-300 text-base">{studyDescription}</div>
|
<div className="text-blue-300 text-base">{description}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{!!trackedSeries && (
|
{!!trackedSeries && (
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
|
|
||||||
import { Thumbnail, ThumbnailNoImage, ThumbnailTracked } from '@ohif/ui';
|
import { Thumbnail, ThumbnailNoImage, ThumbnailTracked } from '@ohif/ui';
|
||||||
|
|
||||||
const ThumbnailList = ({ thumbnails }) => {
|
const ThumbnailList = ({ thumbnails, thumbnailActive, onThumbnailClick }) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-black py-3">
|
<div className="bg-black py-3">
|
||||||
{thumbnails.map(
|
{thumbnails.map(
|
||||||
@ -17,10 +17,11 @@ const ThumbnailList = ({ thumbnails }) => {
|
|||||||
seriesDate,
|
seriesDate,
|
||||||
viewportIdentificator,
|
viewportIdentificator,
|
||||||
isTracked,
|
isTracked,
|
||||||
isActive,
|
|
||||||
imageSrc,
|
imageSrc,
|
||||||
imageAltText,
|
imageAltText,
|
||||||
}) => {
|
}) => {
|
||||||
|
const isActive = thumbnailActive === displaySetInstanceUid;
|
||||||
|
|
||||||
switch (componentType) {
|
switch (componentType) {
|
||||||
case 'thumbnail':
|
case 'thumbnail':
|
||||||
return (
|
return (
|
||||||
@ -33,7 +34,7 @@ const ThumbnailList = ({ thumbnails }) => {
|
|||||||
imageAltText={imageAltText}
|
imageAltText={imageAltText}
|
||||||
viewportIdentificator={viewportIdentificator}
|
viewportIdentificator={viewportIdentificator}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
onClick={() => {}}
|
onClick={() => onThumbnailClick(displaySetInstanceUid)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'thumbnailNoImage':
|
case 'thumbnailNoImage':
|
||||||
@ -43,7 +44,7 @@ const ThumbnailList = ({ thumbnails }) => {
|
|||||||
modality={modality}
|
modality={modality}
|
||||||
seriesDate={seriesDate}
|
seriesDate={seriesDate}
|
||||||
description={description}
|
description={description}
|
||||||
onClick={() => {}}
|
onClick={() => onThumbnailClick(displaySetInstanceUid)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'thumbnailTracked':
|
case 'thumbnailTracked':
|
||||||
@ -58,7 +59,7 @@ const ThumbnailList = ({ thumbnails }) => {
|
|||||||
viewportIdentificator={viewportIdentificator}
|
viewportIdentificator={viewportIdentificator}
|
||||||
isTracked={isTracked}
|
isTracked={isTracked}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
onClick={() => {}}
|
onClick={() => onThumbnailClick(displaySetInstanceUid)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
@ -87,9 +88,10 @@ ThumbnailList.propTypes = {
|
|||||||
]).isRequired,
|
]).isRequired,
|
||||||
viewportIdentificator: PropTypes.string,
|
viewportIdentificator: PropTypes.string,
|
||||||
isTracked: PropTypes.bool,
|
isTracked: PropTypes.bool,
|
||||||
isActive: PropTypes.bool,
|
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
thumbnailActive: PropTypes.string,
|
||||||
|
onThumbnailClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ThumbnailList;
|
export default ThumbnailList;
|
||||||
|
|||||||
@ -8,18 +8,19 @@ const ThumbnailNoImage = ({ description, seriesDate, modality, onClick }) => {
|
|||||||
<div
|
<div
|
||||||
className="flex flex-row flex-1 px-4 py-3 cursor-pointer"
|
className="flex flex-row flex-1 px-4 py-3 cursor-pointer"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
onKeyDown={onClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
>
|
>
|
||||||
<div className="flex flex-2 pl-2 pr-3">
|
|
||||||
<Icon name="list-bullets" className="text-secondary-light" />
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<div className="flex flex-row flex-1 items-center mb-2">
|
<div className="flex flex-row flex-1 items-center mb-2">
|
||||||
<div className="px-4 bg-primary-main rounded-sm mr-4 text-xl text-white">
|
<Icon name="list-bullets" className="text-secondary-light w-12" />
|
||||||
|
<div className="px-3 bg-primary-main rounded-sm mr-4 text-lg text-white">
|
||||||
{modality}
|
{modality}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-blue-300 text-base">{seriesDate}</span>
|
<span className="text-blue-300 text-base">{seriesDate}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-white text-base">{description}</div>
|
<div className="text-white text-base ml-12">{description}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -21,14 +21,12 @@ import { Thumbnail } from '@ohif/ui';
|
|||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<div className="flex flex-1 items-center justify-center p-4">
|
<div className="flex flex-1 items-center justify-center p-4">
|
||||||
<div className="w-56">
|
<div className="w-64">
|
||||||
<Thumbnail
|
<ThumbnailNoImage
|
||||||
imageAltText="DisplaySet thumbnail image"
|
modality={'SR'}
|
||||||
|
seriesDate="13-Mar-1999"
|
||||||
description="Multiple line image series description lorem sit"
|
description="Multiple line image series description lorem sit"
|
||||||
seriesNumber={4}
|
onClick={() => alert('ThumbnailNoImage was clicked!')}
|
||||||
numInstances={902}
|
|
||||||
onClick={() => alert('Thumbnail was clicked!')}
|
|
||||||
isActive={false}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -36,4 +34,4 @@ import { Thumbnail } from '@ohif/ui';
|
|||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
<Props of={Thumbnail} />
|
<Props of={ThumbnailNoImage} />
|
||||||
|
|||||||
@ -1,26 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import './ContextMenu.css';
|
|
||||||
|
|
||||||
const ContextMenu = ({ items, onClick }) => {
|
|
||||||
return (
|
|
||||||
<div className="ContextMenu" onContextMenu={e => e.preventDefault()}>
|
|
||||||
<ul>
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<li key={index}>
|
|
||||||
<button className="form-action" onClick={() => onClick(item)}>
|
|
||||||
<span key={index}>{item.label}</span>
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ContextMenu.propTypes = {
|
|
||||||
items: PropTypes.array.isRequired,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ContextMenu;
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import './LayoutChooser.styl';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the 'hover' class to cells above and to the left of the current cell
|
|
||||||
* This is used to "fill in" the grid that the user will change the layout to,
|
|
||||||
* if they click on a specific table cell.
|
|
||||||
**/
|
|
||||||
|
|
||||||
class LayoutChooser extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
Rows: PropTypes.number.isRequired,
|
|
||||||
Columns: PropTypes.number.isRequired,
|
|
||||||
visible: PropTypes.bool.isRequired,
|
|
||||||
selectedCell: PropTypes.object,
|
|
||||||
boxSize: PropTypes.number.isRequired,
|
|
||||||
cellBorder: PropTypes.number.isRequired,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
Rows: 3,
|
|
||||||
Columns: 3,
|
|
||||||
visible: true,
|
|
||||||
boxSize: 20,
|
|
||||||
cellBorder: 1,
|
|
||||||
selectedCell: {
|
|
||||||
row: -1,
|
|
||||||
col: -1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.emptyCell = {
|
|
||||||
row: -1,
|
|
||||||
column: -1,
|
|
||||||
};
|
|
||||||
this.state = {
|
|
||||||
table: [[]],
|
|
||||||
selectedCell: this.props.selectedCell,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
componentDidMount() {
|
|
||||||
this.highlightCells(this.emptyCell);
|
|
||||||
}
|
|
||||||
onClick(currentCell) {
|
|
||||||
this.setState({
|
|
||||||
selectedCell: currentCell,
|
|
||||||
});
|
|
||||||
this.highlightCells(currentCell);
|
|
||||||
if (this.props.onClick) {
|
|
||||||
this.props.onClick(currentCell);
|
|
||||||
}
|
|
||||||
if (this.props.onChange) {
|
|
||||||
this.props.onChange(currentCell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isRange = (cell, parentCell) => {
|
|
||||||
return cell.row <= parentCell.row && cell.col <= parentCell.col;
|
|
||||||
};
|
|
||||||
highlightCells = currentCell => {
|
|
||||||
let table = [];
|
|
||||||
for (let row = 0; row < this.props.Rows; row++) {
|
|
||||||
let newRow = [];
|
|
||||||
for (let col = 0; col < this.props.Columns; col++) {
|
|
||||||
let cell = { row: row, col: col };
|
|
||||||
if (this.isRange(cell, currentCell)) {
|
|
||||||
cell.className = 'hover';
|
|
||||||
} else if (
|
|
||||||
this.isRange(currentCell, this.emptyCell) &&
|
|
||||||
this.isRange(cell, this.state.selectedCell)
|
|
||||||
) {
|
|
||||||
cell.className = 'selectedBefore';
|
|
||||||
}
|
|
||||||
newRow.push(cell);
|
|
||||||
}
|
|
||||||
table.push(newRow);
|
|
||||||
}
|
|
||||||
this.setState({ table: table });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
let Columns = this.props.Columns;
|
|
||||||
const style = {
|
|
||||||
display: this.props.visible ? 'block' : 'none',
|
|
||||||
minWidth:
|
|
||||||
Columns * this.props.boxSize + (Columns + 5) * this.props.cellBorder,
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="layoutChooser layoutChooser-dropdown-menu"
|
|
||||||
role="menu"
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
{this.state.table.map((row, i) => {
|
|
||||||
return (
|
|
||||||
<tr key={i}>
|
|
||||||
{row.map((cell, j) => {
|
|
||||||
return (
|
|
||||||
<td
|
|
||||||
className={cell.className}
|
|
||||||
style={{
|
|
||||||
width: this.props.boxSize,
|
|
||||||
height: this.props.boxSize,
|
|
||||||
border: 'solid 1px black',
|
|
||||||
}}
|
|
||||||
key={j}
|
|
||||||
onMouseEnter={() => this.highlightCells(cell)}
|
|
||||||
onMouseLeave={() => this.highlightCells(this.emptyCell)}
|
|
||||||
onClick={() => this.onClick(cell)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { LayoutChooser };
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
.measurementItem
|
|
||||||
.rowActions
|
|
||||||
margin-left: -1px;
|
|
||||||
background-color: var(--ui-gray-darker)
|
|
||||||
height: 0
|
|
||||||
overflow: hidden
|
|
||||||
transition: all 0.3s ease
|
|
||||||
visibility: hidden
|
|
||||||
padding-left: 14px
|
|
||||||
|
|
||||||
.btnAction
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--default-color)
|
|
||||||
cursor: pointer
|
|
||||||
line-height: 35px
|
|
||||||
height: 35px
|
|
||||||
transition: all 0.3s ease
|
|
||||||
|
|
||||||
&:hover, &:active
|
|
||||||
color: var(--text-primary-color)
|
|
||||||
|
|
||||||
i
|
|
||||||
margin-right: 4px;
|
|
||||||
|
|
||||||
&.selected
|
|
||||||
.rowActions
|
|
||||||
height: auto;
|
|
||||||
visibility: visible
|
|
||||||
|
|
||||||
.measurementLocation
|
|
||||||
margin-top: 9px;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: calc(100% - 50px);
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow-wrap: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
.measurementDisplayText
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 9px;
|
|
||||||
padding-left: 9px;
|
|
||||||
width: 90px;
|
|
||||||
border-left: 1px solid var(--text-secondary-color);
|
|
||||||
color: var(--text-primary-color);
|
|
||||||
|
|
||||||
.itemIndex
|
|
||||||
-webkit-box-sizing: initial;
|
|
||||||
-moz-box-sizing: initial;
|
|
||||||
box-sizing: initial;
|
|
||||||
|
|
||||||
&.hasWarnings
|
|
||||||
.itemIndex
|
|
||||||
opacity: 1
|
|
||||||
background-color: #e29e4a;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.warning-icon
|
|
||||||
display: block;
|
|
||||||
margin: 7px auto 0;
|
|
||||||
|
|
||||||
svg
|
|
||||||
width: 22px;
|
|
||||||
height: 20px;
|
|
||||||
pointer-events: inherit;
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { StudiesList } from './StudiesList.js';
|
|
||||||
import { ScrollableArea } from './../../ScrollableArea/ScrollableArea.js';
|
|
||||||
import { SeriesList } from './SeriesList.js';
|
|
||||||
|
|
||||||
import './QuickSwitch.styl';
|
|
||||||
|
|
||||||
export class QuickSwitch extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
side: PropTypes.string,
|
|
||||||
studyListData: PropTypes.array.isRequired,
|
|
||||||
onSeriesSelected: PropTypes.func.isRequired,
|
|
||||||
seriesData: PropTypes.array,
|
|
||||||
onStudySelected: PropTypes.func,
|
|
||||||
activeStudyInstanceUID: PropTypes.string,
|
|
||||||
activeDisplaySetInstanceUID: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
seriesQuickSwitchOpen: false,
|
|
||||||
sideClass: this.props.side || '',
|
|
||||||
activeStudyInstanceUID: this.props.activeStudyInstanceUID,
|
|
||||||
activeDisplaySetInstanceUID: this.props.activeDisplaySetInstanceUID,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const props = this.props;
|
|
||||||
|
|
||||||
if (props.activeStudyInstanceUID !== prevProps.activeStudyInstanceUID) {
|
|
||||||
this.setState({
|
|
||||||
activeStudyInstanceUID: props.activeStudyInstanceUID,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
props.activeDisplaySetInstanceUID !==
|
|
||||||
prevProps.activeDisplaySetInstanceUID
|
|
||||||
) {
|
|
||||||
this.setState({
|
|
||||||
activeDisplaySetInstanceUID: props.activeDisplaySetInstanceUID,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const quickSwitchClass = this.state.seriesQuickSwitchOpen
|
|
||||||
? 'series-triggered'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`series-quick-switch clearfix noselect ${this.state.sideClass} ${quickSwitchClass}`}
|
|
||||||
onMouseLeave={this.hideSeriesSwitch}
|
|
||||||
>
|
|
||||||
<div className="series-switch" onMouseEnter={this.showSeriesSwitch}>
|
|
||||||
<div className="title-label">Series</div>
|
|
||||||
<div className="series-box">
|
|
||||||
{this.getSmallListItems()}
|
|
||||||
<ScrollableArea scrollStep={201} class="series-browser">
|
|
||||||
<SeriesList
|
|
||||||
seriesItems={this.getSeriesItems()}
|
|
||||||
onClick={this.onSeriesClick}
|
|
||||||
activeDisplaySetInstanceUID={
|
|
||||||
this.state.activeDisplaySetInstanceUID
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</ScrollableArea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="study-switch">
|
|
||||||
<div className="title-label">Study</div>
|
|
||||||
<div className="study-box">
|
|
||||||
<ScrollableArea scrollStep={91} class="study-browser">
|
|
||||||
<StudiesList
|
|
||||||
studyListData={this.props.studyListData}
|
|
||||||
onClick={this.onStudyClick}
|
|
||||||
activeStudyInstanceUID={this.state.activeStudyInstanceUID}
|
|
||||||
/>
|
|
||||||
</ScrollableArea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSeriesItems = () => {
|
|
||||||
let seriesData;
|
|
||||||
|
|
||||||
if (this.props.seriesData) {
|
|
||||||
seriesData = this.props.seriesData;
|
|
||||||
} else if (this.state.activeStudyInstanceUID) {
|
|
||||||
const study = this.props.studyListData.find(
|
|
||||||
study => study.StudyInstanceUID === this.state.activeStudyInstanceUID
|
|
||||||
);
|
|
||||||
|
|
||||||
seriesData = study.thumbnails;
|
|
||||||
} else {
|
|
||||||
seriesData = this.props.studyListData[0].thumbnails;
|
|
||||||
}
|
|
||||||
|
|
||||||
return seriesData || [];
|
|
||||||
};
|
|
||||||
|
|
||||||
getSmallListItems = () => {
|
|
||||||
const seriesItems = this.getSeriesItems() || [];
|
|
||||||
return seriesItems.map((seriesData, index) => {
|
|
||||||
const active =
|
|
||||||
seriesData.displaySetInstanceUID ===
|
|
||||||
this.state.activeDisplaySetInstanceUID;
|
|
||||||
return (
|
|
||||||
<div key={index} className={`series-item ${active ? 'active' : ''}`} />
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onStudyClick = studyDataSelected => {
|
|
||||||
if (this.props.onStudySelected) {
|
|
||||||
this.props.onStudySelected(studyDataSelected);
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
activeStudyInstanceUID: studyDataSelected.StudyInstanceUID,
|
|
||||||
seriesQuickSwitchOpen: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onSeriesClick = seriesDataSelected => {
|
|
||||||
this.setState({
|
|
||||||
activeDisplaySetInstanceUID: seriesDataSelected.displaySetInstanceUID,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onSeriesSelected(seriesDataSelected);
|
|
||||||
};
|
|
||||||
|
|
||||||
showSeriesSwitch = () => {
|
|
||||||
this.setState({
|
|
||||||
seriesQuickSwitchOpen: true,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
hideSeriesSwitch = () => {
|
|
||||||
this.setState({
|
|
||||||
seriesQuickSwitchOpen: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { Thumbnail } from './../studyBrowser';
|
|
||||||
import './SeriesList.styl';
|
|
||||||
|
|
||||||
export class SeriesList extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
seriesItems: PropTypes.array.isRequired,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
activeDisplaySetInstanceUID: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className="study-browser-series clearfix thumbnails-wrapper">
|
|
||||||
<div className="study-series-container">{this.getSeriesItems()}</div>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSeriesItems = () => {
|
|
||||||
return this.props.seriesItems.map((seriesData, index) => {
|
|
||||||
return (
|
|
||||||
<Thumbnail
|
|
||||||
key={seriesData.displaySetInstanceUID}
|
|
||||||
id={`series_thumb_${index}`}
|
|
||||||
{...seriesData}
|
|
||||||
active={
|
|
||||||
seriesData.displaySetInstanceUID ===
|
|
||||||
this.props.activeDisplaySetInstanceUID
|
|
||||||
}
|
|
||||||
onClick={() => this.props.onClick(seriesData)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import './StudiesItem.styl';
|
|
||||||
|
|
||||||
export class StudiesItem extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
studyData: PropTypes.object.isRequired,
|
|
||||||
active: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
StudyDate,
|
|
||||||
StudyDescription,
|
|
||||||
modalities,
|
|
||||||
studyAvailable,
|
|
||||||
} = this.props.studyData;
|
|
||||||
const activeClass = this.props.active ? ' active' : '';
|
|
||||||
const hasDescriptionAndDate = StudyDate && StudyDescription;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`studyBrowseItem${activeClass}`}
|
|
||||||
onClick={this.props.onClick}
|
|
||||||
>
|
|
||||||
<div className="studyItemBox">
|
|
||||||
<div className="studyModality">
|
|
||||||
<div
|
|
||||||
className="studyModalityText"
|
|
||||||
style={this.getModalitiesStyle()}
|
|
||||||
>
|
|
||||||
{modalities}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="studyText">
|
|
||||||
{hasDescriptionAndDate ? (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className="studyDate">{StudyDate}</div>
|
|
||||||
<div className="studyDescription">{StudyDescription}</div>
|
|
||||||
</React.Fragment>
|
|
||||||
) : (
|
|
||||||
<div className="studyAvailability">
|
|
||||||
{studyAvailable ? (
|
|
||||||
<React.Fragment>N/A</React.Fragment>
|
|
||||||
) : (
|
|
||||||
<React.Fragment>Click to load</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getModalitiesStyle = () => {
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,133 +0,0 @@
|
|||||||
$boxHoverBackgroundColor = #14191E
|
|
||||||
$boxHoverBorderColor = #2d4660
|
|
||||||
$boxActiveBorderColor = #131D29
|
|
||||||
$boxWidth = 50px
|
|
||||||
$borderThickness = 2px
|
|
||||||
$boxBorderRadius = 8px
|
|
||||||
$nestingMargin = 6px
|
|
||||||
$spacerX = 7px
|
|
||||||
$spacerY = 12px
|
|
||||||
|
|
||||||
.studyBrowseItem
|
|
||||||
margin-bottom: 10px;
|
|
||||||
position: relative
|
|
||||||
// required transformation to make inner fixed elements relative to this one
|
|
||||||
transform: scale(1)
|
|
||||||
|
|
||||||
&.active
|
|
||||||
.studyItemBox
|
|
||||||
border-color: var(--active-color)
|
|
||||||
|
|
||||||
.studyModality
|
|
||||||
color: var(--primary-background-color)
|
|
||||||
&
|
|
||||||
&:before
|
|
||||||
&:after
|
|
||||||
background-color: var(--active-color)
|
|
||||||
border-color: $boxActiveBorderColor
|
|
||||||
|
|
||||||
&:not(.active) .studyBrowserSeries
|
|
||||||
max-height: 0 !important
|
|
||||||
|
|
||||||
.studySeriesContainer
|
|
||||||
opacity: 0
|
|
||||||
transform: translateY(-100%)
|
|
||||||
|
|
||||||
.studyBrowserSeries
|
|
||||||
overflow: hidden
|
|
||||||
transition: max-height 0.3s ease
|
|
||||||
|
|
||||||
.studySeriesContainer
|
|
||||||
opacity: 1
|
|
||||||
transition: opacity 0.3s ease, transform 0.3s ease
|
|
||||||
transform: translateY(0)
|
|
||||||
transform-origin: 50% 0%
|
|
||||||
|
|
||||||
.studyItemBox
|
|
||||||
border: 3px solid var(--ui-border-color-dark)
|
|
||||||
border-radius: 12px
|
|
||||||
cursor: pointer
|
|
||||||
padding: $spacerY $spacerX ($spacerY - 1)
|
|
||||||
position: relative
|
|
||||||
transition: $sidebarTransition
|
|
||||||
background-color: $boxHoverBackgroundColor
|
|
||||||
z-index: 0
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
border-color: var(--active-color)
|
|
||||||
|
|
||||||
&.additional
|
|
||||||
color: var(--text-secondary-color)
|
|
||||||
|
|
||||||
.studyModality
|
|
||||||
color: var(--primary-background-color)
|
|
||||||
&
|
|
||||||
&:before
|
|
||||||
&:after
|
|
||||||
background-color: var(--active-color)
|
|
||||||
|
|
||||||
&.additional
|
|
||||||
color: var(--text-primary-color)
|
|
||||||
font-size: 16px
|
|
||||||
font-weight: normal
|
|
||||||
height: 91px
|
|
||||||
line-height: 91px
|
|
||||||
padding: 0
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
.studyText
|
|
||||||
font-size: 13px
|
|
||||||
left: ($spacerX * 3) + $boxWidth + ($nestingMargin * 3)
|
|
||||||
line-height: 14px
|
|
||||||
position: absolute
|
|
||||||
right: $spacerX
|
|
||||||
top: $spacerY
|
|
||||||
.studyDate
|
|
||||||
margin-top: 8px
|
|
||||||
color: var(--text-secondary-color)
|
|
||||||
.studyDescription
|
|
||||||
margin-top: 8px
|
|
||||||
color: var(--text-primary-color)
|
|
||||||
.studyAvailability
|
|
||||||
margin-top: 24px
|
|
||||||
color: var(--text-primary-color)
|
|
||||||
|
|
||||||
.studyModality
|
|
||||||
color: var(--text-secondary-color)
|
|
||||||
font-size: 20px
|
|
||||||
line-height: $boxWidth
|
|
||||||
margin-left: $nestingMargin * 2
|
|
||||||
margin-top: $nestingMargin * 2
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
.studyModalityText
|
|
||||||
height: 100%
|
|
||||||
overflow: hidden
|
|
||||||
text-align: center
|
|
||||||
text-transform: uppercase
|
|
||||||
|
|
||||||
&
|
|
||||||
&:before
|
|
||||||
&:after
|
|
||||||
border: $borderThickness solid var(--primary-background-color)
|
|
||||||
background-color: var(--box-background-color)
|
|
||||||
border-radius: $boxBorderRadius
|
|
||||||
height: $boxWidth + ($borderThickness * 2)
|
|
||||||
transition($sidebarTransition)
|
|
||||||
width: $boxWidth + ($borderThickness * 2)
|
|
||||||
|
|
||||||
&:before
|
|
||||||
&:after
|
|
||||||
display: block
|
|
||||||
content: ''
|
|
||||||
position: absolute
|
|
||||||
|
|
||||||
&:before
|
|
||||||
top: -($borderThickness + $nestingMargin)
|
|
||||||
left: -($borderThickness + $nestingMargin)
|
|
||||||
z-index: -1
|
|
||||||
|
|
||||||
&:after
|
|
||||||
top: -($borderThickness + ($nestingMargin * 2))
|
|
||||||
left: -($borderThickness + ($nestingMargin * 2))
|
|
||||||
z-index: -2
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { StudiesItem } from './StudiesItem.js';
|
|
||||||
import './StudiesList.styl';
|
|
||||||
|
|
||||||
export class StudiesList extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
class: PropTypes.string,
|
|
||||||
studyListData: PropTypes.array.isRequired,
|
|
||||||
onClick: PropTypes.func.isRequired,
|
|
||||||
activeStudyInstanceUID: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={`studiesList ${this.props.class}`}>
|
|
||||||
{this.getBrowserItems()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBrowserItems = () => {
|
|
||||||
return this.props.studyListData.map((studyData, index) => {
|
|
||||||
return (
|
|
||||||
<StudiesItem
|
|
||||||
key={index}
|
|
||||||
studyData={studyData}
|
|
||||||
active={
|
|
||||||
studyData.StudyInstanceUID === this.props.activeStudyInstanceUID
|
|
||||||
}
|
|
||||||
onClick={event => this.props.onClick(studyData)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
---
|
|
||||||
name: Quick Switch
|
|
||||||
menu: Components
|
|
||||||
route: /components/quick-switch
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Playground, Props } from 'docz'
|
|
||||||
import { State } from 'react-powerplug'
|
|
||||||
import { QuickSwitch } from './../index.js'
|
|
||||||
//
|
|
||||||
import studies from './studies.js'
|
|
||||||
|
|
||||||
# Quick Switch
|
|
||||||
|
|
||||||
## Basic usage
|
|
||||||
|
|
||||||
<Playground>
|
|
||||||
<State initial={{
|
|
||||||
studyListData: studies,
|
|
||||||
}}>
|
|
||||||
{({ state, setState }) => (
|
|
||||||
<QuickSwitch
|
|
||||||
studyListData={state.studyListData}
|
|
||||||
// If this is always activeStudy's thumbnails,
|
|
||||||
// Why do we need to specify this?
|
|
||||||
seriesData={
|
|
||||||
state.studyListData.find((study) => {
|
|
||||||
return !!study.active;
|
|
||||||
}).thumbnails}
|
|
||||||
onSeriesSelected={(selectedSeries) => {
|
|
||||||
const { studyListData } = state;
|
|
||||||
const selectedId = selectedSeries.displaySetInstanceUID;
|
|
||||||
|
|
||||||
studyListData.forEach(study => {
|
|
||||||
study.thumbnails.forEach(series => {
|
|
||||||
series.active = series.displaySetInstanceUID === selectedId;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
setState({ studyListData });
|
|
||||||
}}
|
|
||||||
onStudySelected={(selectedStudy) => {
|
|
||||||
const { studyListData } = state;
|
|
||||||
const selectedId = selectedStudy.StudyInstanceUID;
|
|
||||||
|
|
||||||
studyListData.forEach(study => {
|
|
||||||
study.active = study.StudyInstanceUID === selectedId;
|
|
||||||
});
|
|
||||||
|
|
||||||
setState({ studyListData });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</State>
|
|
||||||
|
|
||||||
</Playground>
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
<Props of={QuickSwitch} />
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
export default [
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '10001',
|
|
||||||
StudyDescription: 'Anti-PD-1',
|
|
||||||
modalities: 'CT',
|
|
||||||
StudyDate: '18-nov-2018',
|
|
||||||
active: false,
|
|
||||||
thumbnails: [
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg',
|
|
||||||
SeriesDescription: 'Anti-PD-1_Lung',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
numImageFrames: 512,
|
|
||||||
displaySetInstanceUID: '10001-1',
|
|
||||||
stackPercentComplete: 30,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg',
|
|
||||||
SeriesDescription: 'Anti-PD-1_MELANOMA',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
numImageFrames: 256,
|
|
||||||
displaySetInstanceUID: '10001-2',
|
|
||||||
stackPercentComplete: 70,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '10002',
|
|
||||||
StudyDescription: 'CPTAC',
|
|
||||||
modalities: 'CT',
|
|
||||||
StudyDate: '16-aug-2017',
|
|
||||||
active: true,
|
|
||||||
thumbnails: [
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-GBM',
|
|
||||||
active: true,
|
|
||||||
SeriesNumber: '2',
|
|
||||||
numImageFrames: 512,
|
|
||||||
displaySetInstanceUID: '10002-1',
|
|
||||||
stackPercentComplete: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-CM',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
displaySetInstanceUID: '10002-2',
|
|
||||||
numImageFrames: 256,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-HNSCC',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
displaySetInstanceUID: '10002-3',
|
|
||||||
numImageFrames: 256,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-LSCC',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
displaySetInstanceUID: '10002-4',
|
|
||||||
numImageFrames: 256,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
.RoundedButtonGroup {
|
|
||||||
--height: 25px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper {
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
float: left;
|
|
||||||
margin-left: -2px;
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.RoundedButtonGroup .roundedButtonWrapper .roundedButton {
|
|
||||||
align-items: center;
|
|
||||||
background-color: var(--ui-gray-dark);
|
|
||||||
border: 2px solid var(--ui-border-color-dark);
|
|
||||||
color: var(--text-secondary-color);
|
|
||||||
display: flex;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
justify-content: center;
|
|
||||||
height: var(--height);
|
|
||||||
line-height: var(--height);
|
|
||||||
padding: 0 22px;
|
|
||||||
position: relative;
|
|
||||||
text-transform: uppercase;
|
|
||||||
transition: var(--sidebar-transition);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper .roundedButton svg .roundedButtonWrapper .roundedButton span {
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper .roundedButton i {
|
|
||||||
line-height: 15px;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper .bottomLabel {
|
|
||||||
padding: 0 10px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottomLabel {
|
|
||||||
color: var(--text-secondary-color);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper:first-child {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.RoundedButtonGroup .roundedButtonWrapper:first-child .roundedButton {
|
|
||||||
border-bottom-left-radius: var(--height);
|
|
||||||
border-top-left-radius: var(--height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.RoundedButtonGroup .roundedButtonWrapper:last-child .roundedButton {
|
|
||||||
border-bottom-right-radius: var(--height);
|
|
||||||
border-top-right-radius: var(--height);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper:hover .roundedButton {
|
|
||||||
background-color: var(--box-background-color);
|
|
||||||
color: var(--ui-gray-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.roundedButtonWrapper.active .roundedButton {
|
|
||||||
background-color: var(--active-color);
|
|
||||||
border-color: var(--ui-border-color-active);
|
|
||||||
color: var(--ui-gray-dark);
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
.sb-container {
|
|
||||||
position: fixed;
|
|
||||||
width: var(--snackbar-size);
|
|
||||||
padding: 20px;
|
|
||||||
z-index: var(--snackbar-zIndex);
|
|
||||||
box-sizing: border-box;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-topLeft {
|
|
||||||
top: 0;
|
|
||||||
bottom: auto;
|
|
||||||
left: 0;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-topCenter {
|
|
||||||
left: 50%;
|
|
||||||
top: 0;
|
|
||||||
bottom: auto;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-topRight {
|
|
||||||
top: 0;
|
|
||||||
bottom: auto;
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-bottomLeft {
|
|
||||||
top: auto;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-bottomCenter {
|
|
||||||
left: 50%;
|
|
||||||
bottom: 0;
|
|
||||||
top: auto;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-bottomRight {
|
|
||||||
top: auto;
|
|
||||||
bottom: 0px;
|
|
||||||
left: auto;
|
|
||||||
right: 0px;
|
|
||||||
margin: 10px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-topLeft .sb-item,
|
|
||||||
.sb-topCenter .sb-item,
|
|
||||||
.sb-topRight .sb-item {
|
|
||||||
margin: 10px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-bottomLeft .sb-item,
|
|
||||||
.sb-bottomCenter .sb-item,
|
|
||||||
.sb-bottomRight .sb-item {
|
|
||||||
margin: 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-closeBtn {
|
|
||||||
height: 20px;
|
|
||||||
opacity: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 2px;
|
|
||||||
text-align: center;
|
|
||||||
text-shadow: none;
|
|
||||||
width: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
right: 5px;
|
|
||||||
top: 5px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background: rgba(255, 255, 255, 0.6);
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-closeBtn:hover {
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-closeIcon {
|
|
||||||
display: block;
|
|
||||||
font-size: 0;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-closeIcon:after,
|
|
||||||
.sb-closeIcon:before {
|
|
||||||
content: ' ';
|
|
||||||
display: block;
|
|
||||||
height: 2px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
width: 12px;
|
|
||||||
background-color: #222;
|
|
||||||
opacity: 1;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-closeIcon:before {
|
|
||||||
left: 4px;
|
|
||||||
top: 3px;
|
|
||||||
|
|
||||||
transform: rotate(45deg);
|
|
||||||
transform-origin: 0px 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-closeIcon:after {
|
|
||||||
right: 3px;
|
|
||||||
top: 5px;
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
transform-origin: calc(100% - 3px) 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-message {
|
|
||||||
font-size: 14px;
|
|
||||||
word-break: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-item {
|
|
||||||
position: relative;
|
|
||||||
transition: height 300ms ease;
|
|
||||||
animation: fadein 1s;
|
|
||||||
padding: 20px;
|
|
||||||
color: white;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
|
|
||||||
0 3px 5px -1px rgba(0, 0, 0, 0.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-item a {
|
|
||||||
color: white;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-container .sb-hidden {
|
|
||||||
padding-top: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
transition: all 300ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-success {
|
|
||||||
background-color: var(--snackbar-success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-error {
|
|
||||||
background-color: var(--snackbar-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-warning {
|
|
||||||
background-color: var(--snackbar-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sb-info {
|
|
||||||
background-color: var(--snackbar-info);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
top: 30px;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Internet Explorer */
|
|
||||||
@-ms-keyframes fadein {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
top: 30px;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
/* global cornerstone */
|
|
||||||
import './ImageThumbnail.styl';
|
|
||||||
|
|
||||||
import { utils } from '@ohif/core';
|
|
||||||
import React, { useState, useEffect, createRef } from 'react';
|
|
||||||
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
|
|
||||||
import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
|
|
||||||
|
|
||||||
// TODO: How should we have this component depend on Cornerstone?
|
|
||||||
// - Passed in as a prop?
|
|
||||||
// - Set as external dependency?
|
|
||||||
// - Pass in the entire load and render function as a prop?
|
|
||||||
//import cornerstone from 'cornerstone-core';
|
|
||||||
function ImageThumbnail(props) {
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
imageSrc,
|
|
||||||
imageId,
|
|
||||||
stackPercentComplete,
|
|
||||||
error: propsError,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [isLoading, setLoading] = useState(false);
|
|
||||||
const [error, setError] = useState(false);
|
|
||||||
const [image, setImage] = useState({});
|
|
||||||
const canvasRef = createRef();
|
|
||||||
|
|
||||||
let loadingOrError;
|
|
||||||
let cancelablePromise;
|
|
||||||
|
|
||||||
if (propsError || error) {
|
|
||||||
loadingOrError = <ViewportErrorIndicator />;
|
|
||||||
} else if (isLoading) {
|
|
||||||
loadingOrError = <ViewportLoadingIndicator />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showStackLoadingProgressBar = stackPercentComplete !== undefined;
|
|
||||||
|
|
||||||
const shouldRenderToCanvas = () => {
|
|
||||||
return imageId && !imageSrc;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchImagePromise = () => {
|
|
||||||
if (!cancelablePromise) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
cancelablePromise
|
|
||||||
.then(response => {
|
|
||||||
setImage(response);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (error.isCanceled) return;
|
|
||||||
setLoading(false);
|
|
||||||
setError(true);
|
|
||||||
throw new Error(error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setImagePromise = () => {
|
|
||||||
if (shouldRenderToCanvas()) {
|
|
||||||
cancelablePromise = utils.makeCancelable(
|
|
||||||
cornerstone.loadAndCacheImage(imageId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const purgeCancelablePromise = () => {
|
|
||||||
if (cancelablePromise) {
|
|
||||||
cancelablePromise.cancel();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
purgeCancelablePromise();
|
|
||||||
};
|
|
||||||
}, [purgeCancelablePromise]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (image.imageId) {
|
|
||||||
cornerstone.renderToCanvas(canvasRef.current, image);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [canvasRef, image, image.imageId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!image.imageId || image.imageId !== imageId) {
|
|
||||||
purgeCancelablePromise();
|
|
||||||
setImagePromise();
|
|
||||||
fetchImagePromise();
|
|
||||||
}
|
|
||||||
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="ImageThumbnail">
|
|
||||||
<div className="image-thumbnail-canvas">
|
|
||||||
{shouldRenderToCanvas() ? (
|
|
||||||
<canvas ref={canvasRef} width={width} height={height} />
|
|
||||||
) : (
|
|
||||||
<img
|
|
||||||
className="static-image"
|
|
||||||
src={imageSrc}
|
|
||||||
//width={this.props.width}
|
|
||||||
height={height}
|
|
||||||
alt={''}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{loadingOrError}
|
|
||||||
{showStackLoadingProgressBar && (
|
|
||||||
<div className="image-thumbnail-progress-bar">
|
|
||||||
<div
|
|
||||||
className="image-thumbnail-progress-bar-inner"
|
|
||||||
style={{ width: `${stackPercentComplete}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{isLoading && <div className="image-thumbnail-loading-indicator"></div>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageThumbnail.propTypes = {
|
|
||||||
imageSrc: PropTypes.string,
|
|
||||||
imageId: PropTypes.string,
|
|
||||||
error: PropTypes.bool,
|
|
||||||
width: PropTypes.number,
|
|
||||||
height: PropTypes.number,
|
|
||||||
stackPercentComplete: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
ImageThumbnail.defaultProps = {
|
|
||||||
error: false,
|
|
||||||
stackPercentComplete: 0,
|
|
||||||
width: 217,
|
|
||||||
height: 123,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImageThumbnail;
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Thumbnail } from './Thumbnail.js';
|
|
||||||
import './StudyBrowser.styl';
|
|
||||||
|
|
||||||
function StudyBrowser(props) {
|
|
||||||
const {
|
|
||||||
studies,
|
|
||||||
onThumbnailClick,
|
|
||||||
onThumbnailDoubleClick,
|
|
||||||
supportsDrag,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="study-browser">
|
|
||||||
<div className="scrollable-study-thumbnails">
|
|
||||||
{studies
|
|
||||||
.map((study, studyIndex) => {
|
|
||||||
const { StudyInstanceUID } = study;
|
|
||||||
return study.thumbnails.map((thumb, thumbIndex) => {
|
|
||||||
// TODO: Thumb has more props than we care about?
|
|
||||||
const {
|
|
||||||
altImageText,
|
|
||||||
displaySetInstanceUID,
|
|
||||||
imageId,
|
|
||||||
InstanceNumber,
|
|
||||||
numImageFrames,
|
|
||||||
SeriesDescription,
|
|
||||||
SeriesNumber,
|
|
||||||
stackPercentComplete,
|
|
||||||
} = thumb;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={thumb.displaySetInstanceUID}
|
|
||||||
className="thumbnail-container"
|
|
||||||
data-cy="thumbnail-list"
|
|
||||||
>
|
|
||||||
<Thumbnail
|
|
||||||
supportsDrag={supportsDrag}
|
|
||||||
key={`${studyIndex}_${thumbIndex}`}
|
|
||||||
id={`${studyIndex}_${thumbIndex}`} // Unused?
|
|
||||||
// Study
|
|
||||||
StudyInstanceUID={StudyInstanceUID} // used by drop
|
|
||||||
// Thumb
|
|
||||||
altImageText={altImageText}
|
|
||||||
imageId={imageId}
|
|
||||||
InstanceNumber={InstanceNumber}
|
|
||||||
displaySetInstanceUID={displaySetInstanceUID} // used by drop
|
|
||||||
numImageFrames={numImageFrames}
|
|
||||||
SeriesDescription={SeriesDescription}
|
|
||||||
SeriesNumber={SeriesNumber}
|
|
||||||
stackPercentComplete={stackPercentComplete}
|
|
||||||
// Events
|
|
||||||
onClick={onThumbnailClick.bind(
|
|
||||||
undefined,
|
|
||||||
displaySetInstanceUID
|
|
||||||
)}
|
|
||||||
onDoubleClick={onThumbnailDoubleClick}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.flat()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const noop = () => {};
|
|
||||||
|
|
||||||
StudyBrowser.propTypes = {
|
|
||||||
studies: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
StudyInstanceUID: PropTypes.string.isRequired,
|
|
||||||
thumbnails: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
altImageText: PropTypes.string,
|
|
||||||
displaySetInstanceUID: PropTypes.string.isRequired,
|
|
||||||
imageId: PropTypes.string,
|
|
||||||
InstanceNumber: PropTypes.number,
|
|
||||||
numImageFrames: PropTypes.number,
|
|
||||||
SeriesDescription: PropTypes.string,
|
|
||||||
SeriesNumber: PropTypes.number,
|
|
||||||
stackPercentComplete: PropTypes.number,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
})
|
|
||||||
).isRequired,
|
|
||||||
supportsDrag: PropTypes.bool,
|
|
||||||
onThumbnailClick: PropTypes.func,
|
|
||||||
onThumbnailDoubleClick: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
StudyBrowser.defaultProps = {
|
|
||||||
studies: [],
|
|
||||||
supportsDrag: true,
|
|
||||||
onThumbnailClick: noop,
|
|
||||||
onThumbnailDoubleClick: noop,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { StudyBrowser };
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useDrag } from 'react-dnd';
|
|
||||||
import ImageThumbnail from './ImageThumbnail';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import './Thumbnail.styl';
|
|
||||||
|
|
||||||
function ThumbnailFooter({
|
|
||||||
SeriesDescription,
|
|
||||||
SeriesNumber,
|
|
||||||
InstanceNumber,
|
|
||||||
numImageFrames,
|
|
||||||
}) {
|
|
||||||
const infoOnly = !SeriesDescription;
|
|
||||||
|
|
||||||
const getInfo = (value, icon, className = '') => {
|
|
||||||
return (
|
|
||||||
<div className={classNames('item item-series', className)}>
|
|
||||||
<div className="icon">{icon}</div>
|
|
||||||
<div className="value">{value}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const getSeriesInformation = (
|
|
||||||
SeriesNumber,
|
|
||||||
InstanceNumber,
|
|
||||||
numImageFrames
|
|
||||||
) => {
|
|
||||||
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="series-information">
|
|
||||||
{getInfo(SeriesNumber, 'S:')}
|
|
||||||
{getInfo(InstanceNumber, 'I:')}
|
|
||||||
{getInfo(numImageFrames, '', 'image-frames')}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames('series-details', { 'info-only': infoOnly })}>
|
|
||||||
<div className="series-description">{SeriesDescription}</div>
|
|
||||||
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Thumbnail(props) {
|
|
||||||
const {
|
|
||||||
active,
|
|
||||||
altImageText,
|
|
||||||
error,
|
|
||||||
displaySetInstanceUID,
|
|
||||||
imageId,
|
|
||||||
imageSrc,
|
|
||||||
InstanceNumber,
|
|
||||||
numImageFrames,
|
|
||||||
SeriesDescription,
|
|
||||||
SeriesNumber,
|
|
||||||
stackPercentComplete,
|
|
||||||
StudyInstanceUID,
|
|
||||||
onClick,
|
|
||||||
onDoubleClick,
|
|
||||||
onMouseDown,
|
|
||||||
supportsDrag,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [collectedProps, drag, dragPreview] = useDrag({
|
|
||||||
// `droppedItem` in `dropTarget`
|
|
||||||
// The only data it will have access to
|
|
||||||
item: {
|
|
||||||
StudyInstanceUID,
|
|
||||||
displaySetInstanceUID,
|
|
||||||
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
|
|
||||||
imageSrc={imageSrc}
|
|
||||||
imageId={imageId}
|
|
||||||
error={error}
|
|
||||||
stackPercentComplete={stackPercentComplete}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* 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,
|
|
||||||
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
|
|
||||||
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]),
|
|
||||||
numImageFrames: PropTypes.number,
|
|
||||||
onDoubleClick: PropTypes.func,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
onMouseDown: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
Thumbnail.defaultProps = {
|
|
||||||
supportsDrag: false,
|
|
||||||
active: false,
|
|
||||||
error: false,
|
|
||||||
stackPercentComplete: 0,
|
|
||||||
onDoubleClick: noop,
|
|
||||||
onClick: noop,
|
|
||||||
onMouseDown: noop,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Thumbnail };
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { DropTarget } from 'react-dnd';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import './ExampleDropTarget.css';
|
|
||||||
|
|
||||||
// Drag sources and drop targets only interact
|
|
||||||
// if they have the same string type.
|
|
||||||
const Types = {
|
|
||||||
THUMBNAIL: 'thumbnail',
|
|
||||||
};
|
|
||||||
|
|
||||||
const divTarget = {
|
|
||||||
drop(props, monitor, component) {
|
|
||||||
// Note: For this example we use setState, but in
|
|
||||||
// OHIF we will update the redux store instead
|
|
||||||
const item = monitor.getItem();
|
|
||||||
|
|
||||||
component.setState({
|
|
||||||
item: {
|
|
||||||
id: item.id,
|
|
||||||
SeriesDescription: item.SeriesDescription,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return { id: 'ExampleDropTarget' };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Find out why we can't move this into the Example app instead.
|
|
||||||
// It looks like the context isn't properly shared.
|
|
||||||
class CustomDropTarget extends Component {
|
|
||||||
static className = 'ExampleDropTarget';
|
|
||||||
|
|
||||||
state = {
|
|
||||||
item: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
isOver: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
connectDropTarget: PropTypes.func.isRequired,
|
|
||||||
canDrop: PropTypes.bool.isRequired,
|
|
||||||
isOver: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { canDrop, isOver, connectDropTarget } = this.props;
|
|
||||||
const isActive = canDrop && isOver;
|
|
||||||
|
|
||||||
let className = ExampleDropTarget.className;
|
|
||||||
|
|
||||||
if (isActive) {
|
|
||||||
className += ' hovered';
|
|
||||||
} else if (canDrop) {
|
|
||||||
className += ' can-drop';
|
|
||||||
}
|
|
||||||
|
|
||||||
return connectDropTarget(
|
|
||||||
<div className={className}>
|
|
||||||
<h4>
|
|
||||||
{isActive
|
|
||||||
? 'Release to drop'
|
|
||||||
: 'Drag / Drop something from the Study Browser here'}
|
|
||||||
</h4>
|
|
||||||
<p className="study-drop-results">
|
|
||||||
{this.state.item && JSON.stringify(this.state.item)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const collect = (connect, monitor) => ({
|
|
||||||
connectDropTarget: connect.dropTarget(),
|
|
||||||
isOver: monitor.isOver(),
|
|
||||||
canDrop: monitor.canDrop(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ExampleDropTarget = DropTarget(Types.THUMBNAIL, divTarget, collect)(
|
|
||||||
CustomDropTarget
|
|
||||||
);
|
|
||||||
|
|
||||||
export { ExampleDropTarget };
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
export const studies = [
|
|
||||||
{
|
|
||||||
thumbnails: [
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg',
|
|
||||||
SeriesDescription: 'Anti-PD-1_Lung',
|
|
||||||
active: true,
|
|
||||||
SeriesNumber: '2',
|
|
||||||
numImageFrames: 512,
|
|
||||||
stackPercentComplete: 30,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg',
|
|
||||||
SeriesDescription: 'Anti-PD-1_MELANOMA',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
numImageFrames: 256,
|
|
||||||
stackPercentComplete: 70,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
altImageText: 'SR',
|
|
||||||
SeriesDescription: 'Imaging Measurement Report',
|
|
||||||
SeriesNumber: '3',
|
|
||||||
stackPercentComplete: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
thumbnails: [
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-GBM',
|
|
||||||
active: true,
|
|
||||||
SeriesNumber: '2',
|
|
||||||
numImageFrames: 512,
|
|
||||||
stackPercentComplete: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-CM',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
numImageFrames: 256,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-HNSCC',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
numImageFrames: 256,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
imageSrc:
|
|
||||||
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg',
|
|
||||||
SeriesDescription: 'CPTAC-LSCC',
|
|
||||||
SeriesNumber: '2',
|
|
||||||
InstanceNumber: '1',
|
|
||||||
numImageFrames: 256,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function onThumbnailClick() {
|
|
||||||
console.warn('onThumbnailClick');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function onThumbnailDoubleClick() {
|
|
||||||
console.warn('onThumbnailDoubleClick');
|
|
||||||
}
|
|
||||||
@ -1,409 +0,0 @@
|
|||||||
import './StudyList.styl';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import TableSearchFilter from './TableSearchFilter.js';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { StudyListLoadingText } from './StudyListLoadingText.js';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const getContentFromUseMediaValue = (
|
|
||||||
displaySize,
|
|
||||||
contentArrayMap,
|
|
||||||
defaultContent
|
|
||||||
) => {
|
|
||||||
const content =
|
|
||||||
displaySize in contentArrayMap
|
|
||||||
? contentArrayMap[displaySize]
|
|
||||||
: defaultContent;
|
|
||||||
|
|
||||||
return content;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param {*} props
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function StudyList(props) {
|
|
||||||
const {
|
|
||||||
isLoading,
|
|
||||||
hasError,
|
|
||||||
studies,
|
|
||||||
sort,
|
|
||||||
onSort: handleSort,
|
|
||||||
filterValues,
|
|
||||||
onFilterChange: handleFilterChange,
|
|
||||||
onSelectItem: handleSelectItem,
|
|
||||||
studyListDateFilterNumDays,
|
|
||||||
displaySize,
|
|
||||||
} = props;
|
|
||||||
const { t, ready: translationsAreReady } = useTranslation('StudyList');
|
|
||||||
|
|
||||||
const largeTableMeta = [
|
|
||||||
{
|
|
||||||
displayText: t('PatientName'),
|
|
||||||
fieldName: 'PatientName',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 330,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('MRN'),
|
|
||||||
fieldName: 'PatientID',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 378,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('AccessionNumber'),
|
|
||||||
fieldName: 'AccessionNumber',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 180,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('StudyDate'),
|
|
||||||
fieldName: 'StudyDate',
|
|
||||||
inputType: 'date-range',
|
|
||||||
size: 300,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('Modality'),
|
|
||||||
fieldName: 'modalities',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 114,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('StudyDescription'),
|
|
||||||
fieldName: 'StudyDescription',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 335,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const mediumTableMeta = [
|
|
||||||
{
|
|
||||||
displayText: `${t('PatientName')} / ${t('MRN')}`,
|
|
||||||
fieldName: 'patientNameOrId',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 250,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('Description'),
|
|
||||||
fieldName: 'accessionOrModalityOrDescription',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 350,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayText: t('StudyDate'),
|
|
||||||
fieldName: 'StudyDate',
|
|
||||||
inputType: 'date-range',
|
|
||||||
size: 300,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const smallTableMeta = [
|
|
||||||
{
|
|
||||||
displayText: t('Search'),
|
|
||||||
fieldName: 'allFields',
|
|
||||||
inputType: 'text',
|
|
||||||
size: 100,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const tableMeta = getContentFromUseMediaValue(
|
|
||||||
displaySize,
|
|
||||||
{ large: largeTableMeta, medium: mediumTableMeta, small: smallTableMeta },
|
|
||||||
smallTableMeta
|
|
||||||
);
|
|
||||||
|
|
||||||
const totalSize = tableMeta
|
|
||||||
.map(field => field.size)
|
|
||||||
.reduce((prev, next) => prev + next);
|
|
||||||
|
|
||||||
return translationsAreReady ? (
|
|
||||||
<table className="table table--striped table--hoverable">
|
|
||||||
<colgroup>
|
|
||||||
{tableMeta.map((field, i) => {
|
|
||||||
const size = field.size;
|
|
||||||
const percentWidth = (size / totalSize) * 100.0;
|
|
||||||
|
|
||||||
return <col key={i} style={{ width: `${percentWidth}%` }} />;
|
|
||||||
})}
|
|
||||||
</colgroup>
|
|
||||||
<thead className="table-head">
|
|
||||||
<tr className="filters">
|
|
||||||
<TableSearchFilter
|
|
||||||
meta={tableMeta}
|
|
||||||
values={filterValues}
|
|
||||||
onSort={handleSort}
|
|
||||||
onValueChange={handleFilterChange}
|
|
||||||
sortFieldName={sort.fieldName}
|
|
||||||
sortDirection={sort.direction}
|
|
||||||
studyListDateFilterNumDays={studyListDateFilterNumDays}
|
|
||||||
/>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="table-body" data-cy="study-list-results">
|
|
||||||
{/* I'm not in love with this approach, but it's the quickest way for now
|
|
||||||
*
|
|
||||||
* - Display different content based on loading, empty, results state
|
|
||||||
*
|
|
||||||
* This is not ideal because it create a jump in focus. For loading especially,
|
|
||||||
* We should keep our current results visible while we load the new ones.
|
|
||||||
*/}
|
|
||||||
{/* LOADING */}
|
|
||||||
{isLoading && (
|
|
||||||
<tr className="no-hover">
|
|
||||||
<td colSpan={tableMeta.length}>
|
|
||||||
<StudyListLoadingText />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!isLoading && hasError && (
|
|
||||||
<tr className="no-hover">
|
|
||||||
<td colSpan={tableMeta.length}>
|
|
||||||
<div className="notFound">
|
|
||||||
{t('There was an error fetching studies')}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{/* EMPTY */}
|
|
||||||
{!isLoading && !studies.length && (
|
|
||||||
<tr className="no-hover">
|
|
||||||
<td colSpan={tableMeta.length}>
|
|
||||||
<div className="notFound">{t('No matching results')}</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
{!isLoading &&
|
|
||||||
studies.map((study, index) => (
|
|
||||||
<TableRow
|
|
||||||
key={`${study.StudyInstanceUID}-${index}`}
|
|
||||||
onClick={StudyInstanceUID => handleSelectItem(StudyInstanceUID)}
|
|
||||||
AccessionNumber={study.AccessionNumber || ''}
|
|
||||||
modalities={study.modalities}
|
|
||||||
PatientID={study.PatientID || ''}
|
|
||||||
PatientName={study.PatientName || ''}
|
|
||||||
StudyDate={study.StudyDate}
|
|
||||||
StudyDescription={study.StudyDescription || ''}
|
|
||||||
StudyInstanceUID={study.StudyInstanceUID}
|
|
||||||
displaySize={displaySize}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
StudyList.propTypes = {
|
|
||||||
isLoading: PropTypes.bool.isRequired,
|
|
||||||
hasError: PropTypes.bool.isRequired,
|
|
||||||
studies: PropTypes.array.isRequired,
|
|
||||||
onSelectItem: PropTypes.func.isRequired,
|
|
||||||
// ~~ SORT
|
|
||||||
sort: PropTypes.shape({
|
|
||||||
fieldName: PropTypes.string,
|
|
||||||
direction: PropTypes.oneOf(['desc', 'asc', null]),
|
|
||||||
}).isRequired,
|
|
||||||
onSort: PropTypes.func.isRequired,
|
|
||||||
// ~~ FILTERS
|
|
||||||
filterValues: PropTypes.shape({
|
|
||||||
PatientName: PropTypes.string.isRequired,
|
|
||||||
PatientID: PropTypes.string.isRequired,
|
|
||||||
AccessionNumber: PropTypes.string.isRequired,
|
|
||||||
StudyDate: PropTypes.string.isRequired,
|
|
||||||
modalities: PropTypes.string.isRequired,
|
|
||||||
StudyDescription: PropTypes.string.isRequired,
|
|
||||||
patientNameOrId: PropTypes.string.isRequired,
|
|
||||||
accessionOrModalityOrDescription: PropTypes.string.isRequired,
|
|
||||||
allFields: PropTypes.string.isRequired,
|
|
||||||
studyDateTo: PropTypes.any,
|
|
||||||
studyDateFrom: PropTypes.any,
|
|
||||||
}).isRequired,
|
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
|
||||||
studyListDateFilterNumDays: PropTypes.number,
|
|
||||||
displaySize: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
StudyList.defaultProps = {};
|
|
||||||
|
|
||||||
function TableRow(props) {
|
|
||||||
const {
|
|
||||||
AccessionNumber,
|
|
||||||
isHighlighted,
|
|
||||||
modalities,
|
|
||||||
PatientID,
|
|
||||||
PatientName,
|
|
||||||
StudyDate,
|
|
||||||
StudyDescription,
|
|
||||||
StudyInstanceUID,
|
|
||||||
onClick: handleClick,
|
|
||||||
displaySize,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const { t } = useTranslation('StudyList');
|
|
||||||
|
|
||||||
const largeRowTemplate = (
|
|
||||||
<tr
|
|
||||||
onClick={() => handleClick(StudyInstanceUID)}
|
|
||||||
className={classNames({ active: isHighlighted })}
|
|
||||||
>
|
|
||||||
<td className={classNames({ 'empty-value': !PatientName })}>
|
|
||||||
{PatientName || `(${t('Empty')})`}
|
|
||||||
</td>
|
|
||||||
<td>{PatientID}</td>
|
|
||||||
<td>{AccessionNumber}</td>
|
|
||||||
<td>{StudyDate}</td>
|
|
||||||
<td className={classNames({ 'empty-value': !modalities })}>
|
|
||||||
{modalities || `(${t('Empty')})`}
|
|
||||||
</td>
|
|
||||||
<td>{StudyDescription}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
|
|
||||||
const mediumRowTemplate = (
|
|
||||||
<tr
|
|
||||||
onClick={() => handleClick(StudyInstanceUID)}
|
|
||||||
className={classNames({ active: isHighlighted })}
|
|
||||||
>
|
|
||||||
<td className={classNames({ 'empty-value': !PatientName })}>
|
|
||||||
{PatientName || `(${t('Empty')})`}
|
|
||||||
<div style={{ color: '#60656f' }}>{PatientID}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
{/* DESCRIPTION */}
|
|
||||||
<div
|
|
||||||
className="hide-xs"
|
|
||||||
style={{
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
flexGrow: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{StudyDescription}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MODALITY & ACCESSION */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
maxWidth: '80px',
|
|
||||||
width: '80px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classNames({
|
|
||||||
modalities: modalities,
|
|
||||||
'empty-value': !modalities,
|
|
||||||
})}
|
|
||||||
aria-label={modalities}
|
|
||||||
title={modalities}
|
|
||||||
>
|
|
||||||
{modalities || `(${t('Empty')})`}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
}}
|
|
||||||
aria-label={AccessionNumber}
|
|
||||||
title={AccessionNumber}
|
|
||||||
>
|
|
||||||
{AccessionNumber}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
{/* DATE */}
|
|
||||||
<td style={{ textAlign: 'center' }}>{StudyDate}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
|
|
||||||
const smallRowTemplate = (
|
|
||||||
<tr
|
|
||||||
onClick={() => handleClick(StudyInstanceUID)}
|
|
||||||
className={classNames({ active: isHighlighted })}
|
|
||||||
>
|
|
||||||
<td style={{ position: 'relative', overflow: 'hidden' }}>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
{/* NAME AND ID */}
|
|
||||||
<div
|
|
||||||
className={classNames({ 'empty-value': !PatientName })}
|
|
||||||
style={{ width: '150px', minWidth: '150px' }}
|
|
||||||
>
|
|
||||||
<div style={{ fontWeight: 500, paddingTop: '3px' }}>
|
|
||||||
{PatientName || `(${t('Empty')})`}
|
|
||||||
</div>
|
|
||||||
<div style={{ color: '#60656f' }}>{PatientID}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* DESCRIPTION */}
|
|
||||||
<div
|
|
||||||
className="hide-xs"
|
|
||||||
style={{
|
|
||||||
whiteSpace: 'pre-wrap',
|
|
||||||
flexGrow: 1,
|
|
||||||
paddingLeft: '35px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{StudyDescription}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MODALITY & DATE */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
maxWidth: '80px',
|
|
||||||
width: '80px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classNames({
|
|
||||||
modalities: modalities,
|
|
||||||
'empty-value': !modalities,
|
|
||||||
})}
|
|
||||||
aria-label={modalities}
|
|
||||||
title={modalities}
|
|
||||||
>
|
|
||||||
{modalities || `(${t('Empty')})`}
|
|
||||||
</div>
|
|
||||||
<div>{StudyDate}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
|
|
||||||
const rowTemplate = getContentFromUseMediaValue(
|
|
||||||
displaySize,
|
|
||||||
{
|
|
||||||
large: largeRowTemplate,
|
|
||||||
medium: mediumRowTemplate,
|
|
||||||
small: smallRowTemplate,
|
|
||||||
},
|
|
||||||
smallRowTemplate
|
|
||||||
);
|
|
||||||
|
|
||||||
return rowTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
TableRow.propTypes = {
|
|
||||||
AccessionNumber: PropTypes.string.isRequired,
|
|
||||||
isHighlighted: PropTypes.bool,
|
|
||||||
modalities: PropTypes.string,
|
|
||||||
PatientID: PropTypes.string.isRequired,
|
|
||||||
PatientName: PropTypes.string.isRequired,
|
|
||||||
StudyDate: PropTypes.string.isRequired,
|
|
||||||
StudyDescription: PropTypes.string.isRequired,
|
|
||||||
StudyInstanceUID: PropTypes.string.isRequired,
|
|
||||||
displaySize: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
TableRow.defaultProps = {
|
|
||||||
isHighlighted: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { StudyList };
|
|
||||||
@ -1,238 +0,0 @@
|
|||||||
@import './../../design/styles/common/form.styl'
|
|
||||||
@import './../../design/styles/common/global.styl'
|
|
||||||
|
|
||||||
$study-list-padding = 8%;
|
|
||||||
$study-list-toolbar-height = 75px;
|
|
||||||
$body-cell-height = 40px;
|
|
||||||
$body-cell-top-bottom-padding = 16px;
|
|
||||||
|
|
||||||
$tablePaddingMediumScreen = 5px
|
|
||||||
$tablePaddingBigScreen = 3%
|
|
||||||
$tablePaddingLargeScreen = 5%
|
|
||||||
|
|
||||||
// TODO: move this to a common mixin files
|
|
||||||
placeholder-color(c)
|
|
||||||
&::-webkit-input-placeholder
|
|
||||||
color: c
|
|
||||||
&:-moz-placeholder
|
|
||||||
color: c
|
|
||||||
&::-moz-placeholder
|
|
||||||
color: c
|
|
||||||
&:-ms-input-placeholder
|
|
||||||
color: c
|
|
||||||
|
|
||||||
.study-list-header
|
|
||||||
.addNewStudy
|
|
||||||
margin: 0 10px
|
|
||||||
|
|
||||||
label
|
|
||||||
font-weight: 400
|
|
||||||
cursor: pointer
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
input
|
|
||||||
width: 0.1px;
|
|
||||||
height: 0.1px
|
|
||||||
opacity: 0
|
|
||||||
overflow: hidden
|
|
||||||
position: absolute
|
|
||||||
z-index: -1
|
|
||||||
|
|
||||||
color: var(--text-secondary-color)
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color: var(--hover-color)
|
|
||||||
|
|
||||||
&:active
|
|
||||||
color: var(--active-color)
|
|
||||||
|
|
||||||
.study-list-header
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
background-color: var(--ui-gray-darker)
|
|
||||||
height: $study-list-toolbar-height
|
|
||||||
margin-bottom: 2px
|
|
||||||
padding: 0 $study-list-padding
|
|
||||||
line-height: $study-list-toolbar-height
|
|
||||||
|
|
||||||
.header
|
|
||||||
font-size: 22px
|
|
||||||
font-weight: 300
|
|
||||||
color: var(--table-text-secondary-color)
|
|
||||||
line-height: $study-list-toolbar-height
|
|
||||||
|
|
||||||
.actions
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.study-count
|
|
||||||
color: var(--large-numbers-color)
|
|
||||||
font-size: 40px
|
|
||||||
font-weight: 100
|
|
||||||
line-height: $study-list-toolbar-height
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dark gray background with blue border
|
|
||||||
* Spans width of page to create a distinct area for table filters
|
|
||||||
*/
|
|
||||||
.table-head-background
|
|
||||||
height: 121px
|
|
||||||
position: absolute
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
&:before, &:after
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
left: 0
|
|
||||||
position: absolute
|
|
||||||
width: 100%
|
|
||||||
|
|
||||||
&:before
|
|
||||||
background-color: var(--ui-gray-darker)
|
|
||||||
height: 100%
|
|
||||||
top: 0
|
|
||||||
z-index: 1
|
|
||||||
|
|
||||||
&:after
|
|
||||||
background-color: var(--ui-border-color-active)
|
|
||||||
bottom: -1px
|
|
||||||
height: 1px
|
|
||||||
z-index: 2
|
|
||||||
|
|
||||||
|
|
||||||
.study-list-container
|
|
||||||
width: 100%
|
|
||||||
padding: 0 $study-list-padding
|
|
||||||
position: absolute
|
|
||||||
z-index: 2
|
|
||||||
|
|
||||||
table.table
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-spacing: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
table-layout: fixed;
|
|
||||||
color: var(--table-text-primary-color)
|
|
||||||
font-weight: 300
|
|
||||||
|
|
||||||
> tbody tr
|
|
||||||
padding: 5px
|
|
||||||
background-color: black
|
|
||||||
|
|
||||||
> tbody td
|
|
||||||
padding: $body-cell-top-bottom-padding 8px;
|
|
||||||
height: $body-cell-height
|
|
||||||
word-wrap: break-word;
|
|
||||||
|
|
||||||
/* Striped Variant */
|
|
||||||
&.table--striped > tbody tr:nth-child(even)
|
|
||||||
background-color: var(--ui-gray-darker)
|
|
||||||
|
|
||||||
/* Hover Variant */
|
|
||||||
&.table--hoverable > tbody tr
|
|
||||||
&:hover, &:active, &.active
|
|
||||||
background-color: var(--table-hover-color)
|
|
||||||
&.no-hover
|
|
||||||
&:hover, &:active, &.active
|
|
||||||
background-color: var(--ui-gray-darker)
|
|
||||||
|
|
||||||
.empty-value
|
|
||||||
color: var(--ui-gray-light)
|
|
||||||
|
|
||||||
.study-list-container > table.table > tr
|
|
||||||
height: 20px
|
|
||||||
|
|
||||||
.study-list-container > table.table > thead
|
|
||||||
::-webkit-datetime-edit-year-field:not([aria-valuenow]),
|
|
||||||
::-webkit-datetime-edit-month-field:not([aria-valuenow]),
|
|
||||||
::-webkit-datetime-edit-day-field:not([aria-valuenow])
|
|
||||||
color: transparent
|
|
||||||
|
|
||||||
.study-list-container > table.table > thead > tr > th
|
|
||||||
padding: 0;
|
|
||||||
border-bottom: 1px solid var(--ui-border-color-active);
|
|
||||||
width: 100%;
|
|
||||||
text-align: left;
|
|
||||||
border-top: 0;
|
|
||||||
|
|
||||||
.study-list-container > table.table > thead > tr > th.StudyDate
|
|
||||||
min-width: 230px
|
|
||||||
.study-list-container > table.table .modalities
|
|
||||||
font-weight: 500;
|
|
||||||
min-height: 20px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.study-list-container
|
|
||||||
.filters
|
|
||||||
label
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 95px;
|
|
||||||
margin: 0 auto;
|
|
||||||
color: var(--table-text-primary-color);
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 20px 8px;
|
|
||||||
user-select: none;
|
|
||||||
font-size: 15px;
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color: var(--active-color);
|
|
||||||
|
|
||||||
&.active, &:active
|
|
||||||
color: var(--active-color);
|
|
||||||
i
|
|
||||||
margin: 0 5px;
|
|
||||||
|
|
||||||
input
|
|
||||||
height: 40px
|
|
||||||
margin: 0 5px 20px 5px
|
|
||||||
padding: 0 20px
|
|
||||||
cursor: pointer
|
|
||||||
border: none
|
|
||||||
background-color: var(--input-background-color)
|
|
||||||
color: var(--input-placeholder-color)
|
|
||||||
font-size: 10pt
|
|
||||||
font-weight: normal
|
|
||||||
border-radius: 4px
|
|
||||||
width: calc(100% - 10px); /* Just use padding? */
|
|
||||||
transition(all 0.15s ease)
|
|
||||||
placeholder-color(var(--input-placeholder-color))
|
|
||||||
|
|
||||||
&:active, &:hover
|
|
||||||
background-color: var(--input-background-color)
|
|
||||||
|
|
||||||
.loading-text
|
|
||||||
color: var(--table-text-secondary-color)
|
|
||||||
text-align: center;
|
|
||||||
font-size: 30px
|
|
||||||
|
|
||||||
.notFound
|
|
||||||
color: var(--table-text-secondary-color)
|
|
||||||
font-size: 30px
|
|
||||||
font-weight: 200
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
@media only screen and (max-width: 768px)
|
|
||||||
.study-list-header
|
|
||||||
padding: 0 16px;
|
|
||||||
|
|
||||||
.study-list-container
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.study-list-container > table.table > thead > tr > th
|
|
||||||
padding: 0 13px;
|
|
||||||
|
|
||||||
.study-list-container > table.table > tbody > tr > td
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
.study-list-container .filters label
|
|
||||||
padding: 8px;
|
|
||||||
|
|
||||||
@media only screen and (max-width: 500px)
|
|
||||||
.hide-xs
|
|
||||||
display: none;
|
|
||||||
@ -1,106 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import './PaginationArea.styl';
|
|
||||||
import { withTranslation } from '../../contextProviders';
|
|
||||||
|
|
||||||
class TablePagination extends PureComponent {
|
|
||||||
static defaultProps = {
|
|
||||||
pageOptions: [5, 10, 25, 50, 100],
|
|
||||||
rowsPerPage: 25,
|
|
||||||
currentPage: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
/* Values to show in "Rows per page" select dropdown */
|
|
||||||
pageOptions: PropTypes.array,
|
|
||||||
rowsPerPage: PropTypes.number.isRequired,
|
|
||||||
currentPage: PropTypes.number.isRequired,
|
|
||||||
nextPageFunc: PropTypes.func,
|
|
||||||
prevPageFunc: PropTypes.func,
|
|
||||||
onRowsPerPageChange: PropTypes.func,
|
|
||||||
recordCount: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
nextPage = () => {
|
|
||||||
this.props.nextPageFunc(this.props.currentPage);
|
|
||||||
};
|
|
||||||
|
|
||||||
prevPage = () => {
|
|
||||||
this.props.prevPageFunc(this.props.currentPage);
|
|
||||||
};
|
|
||||||
|
|
||||||
onRowsPerPageChange = event => {
|
|
||||||
this.props.onRowsPerPageChange(parseInt(event.target.value));
|
|
||||||
};
|
|
||||||
|
|
||||||
renderPaginationButtons() {
|
|
||||||
return (
|
|
||||||
<div className="col-xs-8 col-sm-9 col-md-9">
|
|
||||||
<div className="form-inline form-group page-buttons noselect">
|
|
||||||
<React.Fragment>
|
|
||||||
<ul className="pagination-control no-margins">
|
|
||||||
<li className="page-item prev">
|
|
||||||
<button
|
|
||||||
onClick={this.prevPage}
|
|
||||||
disabled={this.props.currentPage === 0}
|
|
||||||
className="btn page-link"
|
|
||||||
>
|
|
||||||
{this.props.t('Previous')}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li className="page-item next">
|
|
||||||
<button
|
|
||||||
onClick={this.nextPage}
|
|
||||||
disabled={
|
|
||||||
this.props.recordCount === 0 ||
|
|
||||||
this.props.rowsPerPage > this.props.recordCount
|
|
||||||
}
|
|
||||||
className="btn page-link"
|
|
||||||
>
|
|
||||||
{this.props.t('Next')}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</React.Fragment>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRowsPerPageDropdown() {
|
|
||||||
return (
|
|
||||||
<div className="form-inline form-group rows-per-page">
|
|
||||||
<span>{this.props.t('Show')}</span>
|
|
||||||
<select
|
|
||||||
onChange={this.onRowsPerPageChange}
|
|
||||||
defaultValue={this.props.rowsPerPage}
|
|
||||||
>
|
|
||||||
{this.props.pageOptions.map(pageNumber => {
|
|
||||||
return (
|
|
||||||
<option key={pageNumber} value={pageNumber}>
|
|
||||||
{pageNumber}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</select>
|
|
||||||
<span>{this.props.t('RowsPerPage')}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="pagination-area">
|
|
||||||
<div className="rows-dropdown">{this.renderRowsPerPageDropdown()}</div>
|
|
||||||
<div className="pagination-buttons">
|
|
||||||
<div className="form-inline form-group page-number pull-right">
|
|
||||||
{this.renderPaginationButtons()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectedComponent = withTranslation('Common')(TablePagination);
|
|
||||||
export { connectedComponent as TablePagination };
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
export default function(searchData) {
|
|
||||||
this.setState({ searchData });
|
|
||||||
|
|
||||||
const filter = (key, searchData, study) => {
|
|
||||||
if (key === 'studyDateFrom' && searchData[key] && study['StudyDate']) {
|
|
||||||
const StudyDate = moment(study['StudyDate'], 'YYYYMMDD');
|
|
||||||
return StudyDate.isBetween(
|
|
||||||
searchData['studyDateFrom'],
|
|
||||||
searchData['studyDateTo'],
|
|
||||||
'days',
|
|
||||||
'[]'
|
|
||||||
);
|
|
||||||
} else if (searchData[key] && !study[key].includes(searchData[key])) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { field, order } = searchData.sortData;
|
|
||||||
|
|
||||||
// just a example of local filtering
|
|
||||||
let filteredStudies = this.defaultStudies
|
|
||||||
.filter(function(study) {
|
|
||||||
const all = [
|
|
||||||
'PatientName',
|
|
||||||
'PatientID',
|
|
||||||
'AccessionNumber',
|
|
||||||
'modalities',
|
|
||||||
'StudyDescription',
|
|
||||||
'studyDateFrom',
|
|
||||||
].every(key => {
|
|
||||||
return filter(key, searchData, study);
|
|
||||||
});
|
|
||||||
|
|
||||||
return all;
|
|
||||||
})
|
|
||||||
.sort(function(a, b) {
|
|
||||||
if (order === 'desc') {
|
|
||||||
if (a[field] < b[field]) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a[field] > b[field]) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
if (a[field] > b[field]) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a[field] < b[field]) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// User can notice the loading icon
|
|
||||||
return new Promise(resolve => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const first = searchData.currentPage * searchData.rowsPerPage;
|
|
||||||
let last =
|
|
||||||
searchData.currentPage * searchData.rowsPerPage +
|
|
||||||
searchData.rowsPerPage;
|
|
||||||
last = last >= filteredStudies.length ? filteredStudies.length : last;
|
|
||||||
this.setState({ studies: filteredStudies.slice(first, last) });
|
|
||||||
resolve();
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
const studies = [
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '11111.111111.111111.11111',
|
|
||||||
PatientName: 'John Doe',
|
|
||||||
PatientID: '1',
|
|
||||||
AccessionNumber: '1234567',
|
|
||||||
StudyDate: '19930201',
|
|
||||||
modalities: 'MR',
|
|
||||||
StudyDescription: 'BRAIN',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '2222.222222.22222.22222',
|
|
||||||
PatientName: 'José Silva',
|
|
||||||
PatientID: '2',
|
|
||||||
AccessionNumber: '7654321',
|
|
||||||
StudyDate: moment().format('YYYYMMDD'),
|
|
||||||
modalities: 'CT',
|
|
||||||
StudyDescription: 'PET CT STANDARD',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '3333.333333.33333.33333',
|
|
||||||
PatientName: 'Antônio Jefferson',
|
|
||||||
PatientID: '3',
|
|
||||||
AccessionNumber: '732311',
|
|
||||||
StudyDate: moment()
|
|
||||||
.subtract(14, 'days')
|
|
||||||
.format('YYYYMMDD'),
|
|
||||||
modalities: 'US',
|
|
||||||
StudyDescription: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '444444.44444.44444.4444',
|
|
||||||
PatientName: 'Antonio da Silva',
|
|
||||||
PatientID: '4',
|
|
||||||
AccessionNumber: '732311',
|
|
||||||
StudyDate: moment()
|
|
||||||
.subtract(1, 'months')
|
|
||||||
.format('YYYYMMDD'),
|
|
||||||
modalities: 'US',
|
|
||||||
StudyDescription: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '55555.55555.55555.55555',
|
|
||||||
PatientName: 'Bezerra Souza',
|
|
||||||
PatientID: '5',
|
|
||||||
AccessionNumber: '5134543',
|
|
||||||
StudyDate: moment()
|
|
||||||
.subtract(6, 'days')
|
|
||||||
.format('YYYYMMDD'),
|
|
||||||
modalities: 'US',
|
|
||||||
StudyDescription: '0',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '66666.66666.66666.6666',
|
|
||||||
PatientName: 'Geraldo Roger',
|
|
||||||
PatientID: '6',
|
|
||||||
AccessionNumber: '5315135',
|
|
||||||
StudyDate: moment()
|
|
||||||
.subtract(7, 'days')
|
|
||||||
.format('YYYYMMDD'),
|
|
||||||
modalities: 'US',
|
|
||||||
StudyDescription: 'US',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StudyInstanceUID: '77777.77777.77777.77777',
|
|
||||||
PatientName: '',
|
|
||||||
PatientID: '7',
|
|
||||||
AccessionNumber: '5315136',
|
|
||||||
StudyDate: moment()
|
|
||||||
.subtract(5, 'days')
|
|
||||||
.format('YYYYMMDD'),
|
|
||||||
modalities: 'US',
|
|
||||||
StudyDescription: 'US',
|
|
||||||
},
|
|
||||||
].sort(function(a, b) {
|
|
||||||
if (a.PatientName < b.PatientName) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.PatientName > b.PatientName) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
export default studies;
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
---
|
|
||||||
name: Study List
|
|
||||||
menu: Components
|
|
||||||
route: /components/study-list
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Playground, Props } from 'docz'
|
|
||||||
import { State } from 'react-powerplug'
|
|
||||||
import { StudyList } from './../index.js'
|
|
||||||
// Data
|
|
||||||
import defaultStudies from './studies.js'
|
|
||||||
import onSearch from './onSearch.js'
|
|
||||||
import moment from 'moment'
|
|
||||||
|
|
||||||
# Study List
|
|
||||||
|
|
||||||
## Basic usage
|
|
||||||
|
|
||||||
<Playground>
|
|
||||||
<State initial={{
|
|
||||||
searchData: {},
|
|
||||||
studies: defaultStudies.filter(study => {
|
|
||||||
const StudyDate = moment(study['StudyDate'], 'YYYYMMDD');
|
|
||||||
const startDate = moment().subtract(5, 'days');
|
|
||||||
const endDate = moment();
|
|
||||||
|
|
||||||
return StudyDate.isBetween(startDate, endDate, 'days', '[]');
|
|
||||||
}).slice(0, 5)
|
|
||||||
}}>
|
|
||||||
{({ state, setState }) => (
|
|
||||||
<div className="row" style={{ backgroundColor: '#000', height: '600px' }}>
|
|
||||||
<div className="col-xs-12" style={{ padding: 0 }}>
|
|
||||||
<StudyList
|
|
||||||
studies={state.studies}
|
|
||||||
pageOptions={[1, 2, 3, 5, 10, 15, 20, 25, 50, 100]}
|
|
||||||
studyListFunctionsEnabled={true}
|
|
||||||
onImport={(e) => alert('Import study mock ' + e)}
|
|
||||||
onSelectItem={(StudyInstanceUID) => { alert(StudyInstanceUID + ' has selected! Now you can open your study.'); }}
|
|
||||||
rowsPerPage={5}
|
|
||||||
defaultSort={{ field: 'PatientName', order: 'desc', }}
|
|
||||||
studyListDateFilterNumDays={7}
|
|
||||||
onSearch={onSearch.bind({ setState, defaultStudies })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</State>
|
|
||||||
|
|
||||||
</Playground>
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
<Props of={StudyList} />
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import './TableList.styl';
|
|
||||||
|
|
||||||
export class TableList extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
customHeader: PropTypes.node,
|
|
||||||
defaultItems: PropTypes.object,
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
headerTitle: PropTypes.string,
|
|
||||||
headless: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
headless: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="tableList">
|
|
||||||
{!this.props.headless && (
|
|
||||||
<div className="tableListHeader" onClick={this.onHeaderClick}>
|
|
||||||
{this.getHeader()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="tableListContent">{this.props.children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getHeader = () => {
|
|
||||||
if (this.props.customHeader) {
|
|
||||||
return this.props.customHeader;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className="tableListHeaderTitle">{this.props.headerTitle}</div>
|
|
||||||
<div className="numberOfItems">{this.props.children.length}</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
import './TableListItem.styl';
|
|
||||||
|
|
||||||
import { Component } from 'react';
|
|
||||||
import { Icon } from './../../elements/Icon';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export class TableListItem extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
itemClass: PropTypes.string,
|
|
||||||
itemIndex: PropTypes.number,
|
|
||||||
itemMeta: PropTypes.node,
|
|
||||||
itemMetaClass: PropTypes.string,
|
|
||||||
itemKey: PropTypes.oneOfType([
|
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number,
|
|
||||||
]),
|
|
||||||
onItemClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
itemMeta: null,
|
|
||||||
itemMetaClass: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`tableListItem ${this.props.itemClass}`}
|
|
||||||
onClick={this.onItemClick}
|
|
||||||
>
|
|
||||||
<div className={`itemIndex ${this.props.itemMetaClass}`}>
|
|
||||||
{this.props.itemIndex}
|
|
||||||
{this.props.itemMeta}
|
|
||||||
<span className="warning-icon">
|
|
||||||
<Icon name="exclamation-triangle" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="itemContent">{this.props.children}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onItemClick = event => {
|
|
||||||
if (this.props.onItemClick) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
this.props.onItemClick(event, this.props.itemKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,194 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import adjust from './icons/adjust.svg';
|
|
||||||
// Icons
|
|
||||||
import angleDoubleDown from './icons/angle-double-down.svg';
|
|
||||||
import angleDoubleUp from './icons/angle-double-up.svg';
|
|
||||||
import angleLeft from './icons/angle-left.svg';
|
|
||||||
import arrows from './icons/arrows.svg';
|
|
||||||
import arrowsAltH from './icons/arrows-alt-h.svg';
|
|
||||||
import arrowsAltV from './icons/arrows-alt-v.svg';
|
|
||||||
import bars from './icons/bars.svg';
|
|
||||||
import brain from './icons/brain.svg';
|
|
||||||
import brush from './icons/brush.svg';
|
|
||||||
import caretDown from './icons/caret-down.svg';
|
|
||||||
import caretUp from './icons/caret-up.svg';
|
|
||||||
import check from './icons/check.svg';
|
|
||||||
import checkCircle from './icons/check-circle.svg';
|
|
||||||
import checkCircleO from './icons/check-circle-o.svg';
|
|
||||||
import chevronDown from './icons/chevron-down.svg';
|
|
||||||
import circle from './icons/circle.svg';
|
|
||||||
import circleNotch from './icons/circle-notch.svg';
|
|
||||||
import circleO from './icons/circle-o.svg';
|
|
||||||
import cog from './icons/cog.svg';
|
|
||||||
import createComment from './icons/create-comment.svg';
|
|
||||||
import createScreenCapture from './icons/create-screen-capture.svg';
|
|
||||||
import crosshairs from './icons/crosshairs.svg';
|
|
||||||
import cube from './icons/cube.svg';
|
|
||||||
import d3Rotate from './icons/3d-rotate.svg';
|
|
||||||
import database from './icons/database.svg';
|
|
||||||
import dotCircle from './icons/dot-circle.svg';
|
|
||||||
import edit from './icons/edit.svg';
|
|
||||||
import ellipseCircle from './icons/ellipse-circle.svg';
|
|
||||||
import ellipseH from './icons/ellipse-h.svg';
|
|
||||||
import ellipseV from './icons/ellipse-v.svg';
|
|
||||||
import exclamationCircle from './icons/exclamation-circle.svg';
|
|
||||||
import exclamationTriangle from './icons/exclamation-triangle.svg';
|
|
||||||
import fastBackward from './icons/fast-backward.svg';
|
|
||||||
import fastForward from './icons/fast-forward.svg';
|
|
||||||
import stop from './icons/stop.svg';
|
|
||||||
import info from './icons/info.svg';
|
|
||||||
import inlineEdit from './icons/inline-edit.svg';
|
|
||||||
import level from './icons/level.svg';
|
|
||||||
import link from './icons/link.svg';
|
|
||||||
import linkCircles from './icons/link-circles.svg';
|
|
||||||
import list from './icons/list.svg';
|
|
||||||
import liver from './icons/liver.svg';
|
|
||||||
import lock from './icons/lock.svg';
|
|
||||||
import lockAlt from './icons/lock-alt.svg';
|
|
||||||
import lung from './icons/lung.svg';
|
|
||||||
import measureNonTarget from './icons/measure-non-target.svg';
|
|
||||||
import measureTarget from './icons/measure-target.svg';
|
|
||||||
import measureTargetCr from './icons/measure-target-cr.svg';
|
|
||||||
import measureTargetNe from './icons/measure-target-ne.svg';
|
|
||||||
import measureTargetUn from './icons/measure-target-un.svg';
|
|
||||||
import measureTemp from './icons/measure-temp.svg';
|
|
||||||
import objectGroup from './icons/object-group.svg';
|
|
||||||
import ohifLogo from './icons/ohif-logo.svg';
|
|
||||||
import ohifTextLogo from './icons/ohif-text-logo.svg';
|
|
||||||
import oval from './icons/oval.svg';
|
|
||||||
import palette from './icons/palette.svg';
|
|
||||||
import play from './icons/play.svg';
|
|
||||||
import plus from './icons/plus.svg';
|
|
||||||
import powerOff from './icons/power-off.svg';
|
|
||||||
import reset from './icons/reset.svg';
|
|
||||||
import rotate from './icons/rotate.svg';
|
|
||||||
import rotateRight from './icons/rotate-right.svg';
|
|
||||||
import saveRegular from './icons/save-regular.svg';
|
|
||||||
import scissors from './icons/scissors.svg';
|
|
||||||
import search from './icons/search.svg';
|
|
||||||
import searchPlus from './icons/search-plus.svg';
|
|
||||||
import softTissue from './icons/soft-tissue.svg';
|
|
||||||
import sort from './icons/sort.svg';
|
|
||||||
import sortDown from './icons/sort-down.svg';
|
|
||||||
import sortUp from './icons/sort-up.svg';
|
|
||||||
import sphere from './icons/sphere.svg';
|
|
||||||
import squareO from './icons/square-o.svg';
|
|
||||||
import star from './icons/star.svg';
|
|
||||||
import stepBackward from './icons/step-backward.svg';
|
|
||||||
import stepForward from './icons/step-forward.svg';
|
|
||||||
import sun from './icons/sun.svg';
|
|
||||||
import th from './icons/th.svg';
|
|
||||||
import thLarge from './icons/th-large.svg';
|
|
||||||
import thList from './icons/th-list.svg';
|
|
||||||
import times from './icons/times.svg';
|
|
||||||
import trash from './icons/trash.svg';
|
|
||||||
import unlink from './icons/unlink.svg';
|
|
||||||
import user from './icons/user.svg';
|
|
||||||
import youtube from './icons/youtube.svg';
|
|
||||||
import eye from './icons/eye.svg';
|
|
||||||
import eyeClosed from './icons/eye-closed.svg';
|
|
||||||
|
|
||||||
const ICONS = {
|
|
||||||
eye,
|
|
||||||
'eye-closed': eyeClosed,
|
|
||||||
brush,
|
|
||||||
scissors,
|
|
||||||
user,
|
|
||||||
sort,
|
|
||||||
th,
|
|
||||||
star,
|
|
||||||
'sort-up': sortUp,
|
|
||||||
sphere,
|
|
||||||
'sort-down': sortDown,
|
|
||||||
info,
|
|
||||||
cube,
|
|
||||||
crosshairs,
|
|
||||||
'dot-circle': dotCircle,
|
|
||||||
'angle-left': angleLeft,
|
|
||||||
'3d-rotate': d3Rotate,
|
|
||||||
plus,
|
|
||||||
'chevron-down': chevronDown,
|
|
||||||
'angle-double-down': angleDoubleDown,
|
|
||||||
'angle-double-up': angleDoubleUp,
|
|
||||||
'arrows-alt-h': arrowsAltH,
|
|
||||||
'arrows-alt-v': arrowsAltV,
|
|
||||||
bars,
|
|
||||||
'caret-down': caretDown,
|
|
||||||
'caret-up': caretUp,
|
|
||||||
'check-circle-o': checkCircleO,
|
|
||||||
check,
|
|
||||||
circle,
|
|
||||||
'circle-o': circleO,
|
|
||||||
times,
|
|
||||||
'create-comment': createComment,
|
|
||||||
'create-screen-capture': createScreenCapture,
|
|
||||||
edit,
|
|
||||||
'fast-backward': fastBackward,
|
|
||||||
'fast-forward': fastForward,
|
|
||||||
'object-group': objectGroup,
|
|
||||||
search,
|
|
||||||
'power-off': powerOff,
|
|
||||||
'inline-edit': inlineEdit,
|
|
||||||
list,
|
|
||||||
'ohif-logo': ohifLogo,
|
|
||||||
'ohif-text-logo': ohifTextLogo,
|
|
||||||
lock,
|
|
||||||
play,
|
|
||||||
database,
|
|
||||||
cog,
|
|
||||||
'circle-notch': circleNotch,
|
|
||||||
'square-o': squareO,
|
|
||||||
'check-circle': checkCircle,
|
|
||||||
'lock-alt': lockAlt,
|
|
||||||
'step-backward': stepBackward,
|
|
||||||
'step-forward': stepForward,
|
|
||||||
stop,
|
|
||||||
'th-large': thLarge,
|
|
||||||
'th-list': thList,
|
|
||||||
sun,
|
|
||||||
palette,
|
|
||||||
youtube,
|
|
||||||
oval,
|
|
||||||
'ellipse-h': ellipseH,
|
|
||||||
'ellipse-v': ellipseV,
|
|
||||||
adjust,
|
|
||||||
level,
|
|
||||||
'link-circles': linkCircles,
|
|
||||||
'search-plus': searchPlus,
|
|
||||||
'measure-non-target': measureNonTarget,
|
|
||||||
'measure-target': measureTarget,
|
|
||||||
'measure-target-cr': measureTargetCr,
|
|
||||||
'measure-target-un': measureTargetUn,
|
|
||||||
'measure-target-ne': measureTargetNe,
|
|
||||||
'measure-temp': measureTemp,
|
|
||||||
'ellipse-circle': ellipseCircle,
|
|
||||||
arrows,
|
|
||||||
reset,
|
|
||||||
rotate,
|
|
||||||
'rotate-right': rotateRight,
|
|
||||||
trash,
|
|
||||||
unlink,
|
|
||||||
'exclamation-circle': exclamationCircle,
|
|
||||||
link,
|
|
||||||
'exclamation-triangle': exclamationTriangle,
|
|
||||||
brain,
|
|
||||||
'soft-tissue': softTissue,
|
|
||||||
lung,
|
|
||||||
liver,
|
|
||||||
save: saveRegular,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the matching SVG Icon as a React Component.
|
|
||||||
* Results in an inlined SVG Element. If there's no match,
|
|
||||||
* return `null`
|
|
||||||
*/
|
|
||||||
export default function getIcon(key, props) {
|
|
||||||
if (!key || !ICONS[key]) {
|
|
||||||
return React.createElement('div', null, 'Missing Icon');
|
|
||||||
}
|
|
||||||
|
|
||||||
return React.createElement(ICONS[key], props);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ICONS };
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
aria-labelledby="title"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<title id="title">Brush</title>
|
|
||||||
<path d="M167.02 309.34c-40.12 2.58-76.53 17.86-97.19 72.3-2.35 6.21-8 9.98-14.59 9.98-11.11 0-45.46-27.67-55.25-34.35C0 439.62 37.93 512 128 512c75.86 0 128-43.77 128-120.19 0-3.11-.65-6.08-.97-9.13l-88.01-73.34zM457.89 0c-15.16 0-29.37 6.71-40.21 16.45C213.27 199.05 192 203.34 192 257.09c0 13.7 3.25 26.76 8.73 38.7l63.82 53.18c7.21 1.8 14.64 3.03 22.39 3.03 62.11 0 98.11-45.47 211.16-256.46 7.38-14.35 13.9-29.85 13.9-45.99C512 20.64 486 0 457.89 0z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 646 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 974 B |
@ -1 +0,0 @@
|
|||||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 557 B |
@ -1,11 +0,0 @@
|
|||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 448 512"
|
|
||||||
aria-labelby="title"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<title id="title">Scissors</title>
|
|
||||||
<path d="M278.06 256L444.48 89.57c4.69-4.69 4.69-12.29 0-16.97-32.8-32.8-85.99-32.8-118.79 0L210.18 188.12l-24.86-24.86c4.31-10.92 6.68-22.81 6.68-35.26 0-53.02-42.98-96-96-96S0 74.98 0 128s42.98 96 96 96c4.54 0 8.99-.32 13.36-.93L142.29 256l-32.93 32.93c-4.37-.61-8.83-.93-13.36-.93-53.02 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96c0-12.45-2.37-24.34-6.68-35.26l24.86-24.86L325.69 439.4c32.8 32.8 85.99 32.8 118.79 0 4.69-4.68 4.69-12.28 0-16.97L278.06 256zM96 160c-17.64 0-32-14.36-32-32s14.36-32 32-32 32 14.36 32 32-14.36 32-32 32zm0 256c-17.64 0-32-14.36-32-32s14.36-32 32-32 32 14.36 32 32-14.36 32-32 32z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 807 B |
@ -1,21 +0,0 @@
|
|||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
viewBox="0 0 512 512"
|
|
||||||
aria-labelby="title"
|
|
||||||
width="1em"
|
|
||||||
height="1em"
|
|
||||||
fill="currentColor"
|
|
||||||
>
|
|
||||||
<title id="title">Sphere</title>
|
|
||||||
<path d="M256,0C114.837,0,0,114.837,0,256s114.837,256,256,256s256-114.837,256-256S397.163,0,256,0z M256,21.333
|
|
||||||
c129.387,0,234.667,105.28,234.667,234.667c0,40.363-96.384,85.333-234.667,85.333c-28.629,0-55.339-2.005-79.893-5.44
|
|
||||||
c-3.435-24.555-5.44-51.264-5.44-79.893C170.667,117.717,215.637,21.333,256,21.333z M208.512,26.176
|
|
||||||
C173.184,67.435,149.333,153.707,149.333,256c0,26.688,1.621,52.309,4.651,76.309C72.683,317.205,21.333,285.419,21.333,256
|
|
||||||
C21.333,142.869,101.824,48.192,208.512,26.176z M26.176,303.488c27.072,23.168,73.429,41.429,131.136,51.2
|
|
||||||
c9.792,57.707,28.032,104.064,51.2,131.136C117.163,466.965,45.035,394.837,26.176,303.488z M256,490.667
|
|
||||||
c-29.419,0-61.205-51.349-76.309-132.651c24,3.029,49.621,4.651,76.309,4.651c102.293,0,188.565-23.851,229.824-59.179
|
|
||||||
C463.808,410.176,369.131,490.667,256,490.667z"/>
|
|
||||||
</svg>
|
|
||||||
<!-- style="enable-background:new 0 0 512 512;" xml:space="preserve" -->
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,53 +0,0 @@
|
|||||||
import './Range.css';
|
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
class Range extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { value: props.value || 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = event => {
|
|
||||||
this.setState({ value: event.target.value });
|
|
||||||
if (this.props.onChange) this.props.onChange(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
value={this.state.value}
|
|
||||||
min={this.props.min}
|
|
||||||
max={this.props.max}
|
|
||||||
step={this.props.step || 1}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
id={this.props.id}
|
|
||||||
className="range"
|
|
||||||
/>
|
|
||||||
{this.props.showPercentage && <span>{`${this.state.value}%`}</span>}
|
|
||||||
{this.props.showValue && <span>{this.state.value}</span>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Range.propTypes = {
|
|
||||||
value: PropTypes.number,
|
|
||||||
min: PropTypes.number.isRequired,
|
|
||||||
max: PropTypes.number.isRequired,
|
|
||||||
step: PropTypes.number,
|
|
||||||
id: PropTypes.string,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
showPercentage: PropTypes.bool,
|
|
||||||
showValue: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
Range.defaultProps = {
|
|
||||||
showPercentage: false,
|
|
||||||
showValue: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Range };
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
import './TextArea.css';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
class TextArea extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = { value: this.props.value };
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = event => {
|
|
||||||
this.setState({ value: event.target.value });
|
|
||||||
if (this.props.onChange) this.props.onChange();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<textarea
|
|
||||||
className="textarea-ohif"
|
|
||||||
value={this.state.value}
|
|
||||||
Rows={this.props.Rows}
|
|
||||||
cols={this.props.cols}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
id={this.props.id}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextArea.propTypes = {
|
|
||||||
value: PropTypes.string,
|
|
||||||
Rows: PropTypes.number,
|
|
||||||
cols: PropTypes.number,
|
|
||||||
id: PropTypes.string,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { TextArea };
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export function ViewportLoadingIndicator(props) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="loading-indicator"
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
margin: 'auto',
|
|
||||||
textAlign: 'center',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Loading {props.percentComplete}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ViewportLoadingIndicator.propTypes = {
|
|
||||||
percentComplete: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ViewportLoadingIndicator;
|
|
||||||
@ -1,287 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import classnames from 'classnames';
|
|
||||||
|
|
||||||
import { format } from 'date-fns';
|
|
||||||
import { Button, Icon, EmptyStudies } from '@ohif/ui';
|
|
||||||
|
|
||||||
const getGridColClass = (filtersMeta, name) => {
|
|
||||||
const filter = filtersMeta.find((filter) => filter.name === name);
|
|
||||||
return (filter && filter.gridCol && `w-${filter.gridCol}/24`) || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const StudyTableRow = (props) => {
|
|
||||||
const {
|
|
||||||
AccessionNumber,
|
|
||||||
Modalities,
|
|
||||||
Instances,
|
|
||||||
StudyDescription,
|
|
||||||
PatientId,
|
|
||||||
PatientName,
|
|
||||||
StudyDate,
|
|
||||||
series,
|
|
||||||
filtersMeta,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
|
||||||
const toggleRow = () => setIsOpened(!isOpened);
|
|
||||||
const ChevronIconName = isOpened ? 'chevron-down' : 'chevron-right';
|
|
||||||
const tdClasses = [
|
|
||||||
'px-4 py-2 text-base',
|
|
||||||
{ 'border-b border-secondary-light': !isOpened },
|
|
||||||
];
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<tr>
|
|
||||||
<td
|
|
||||||
className={classnames('border-0 p-0', {
|
|
||||||
'border-b border-secondary-light bg-primary-dark': isOpened,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classnames('w-full transition duration-300', {
|
|
||||||
'border border-primary-light rounded overflow-hidden mb-2 hover:border-secondary-light': isOpened,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<table className={classnames('w-full p-4')}>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
className={classnames(
|
|
||||||
'cursor-pointer hover:bg-secondary-main transition duration-300 bg-black',
|
|
||||||
{
|
|
||||||
'bg-primary-dark': !isOpened,
|
|
||||||
},
|
|
||||||
{ 'bg-secondary-dark': isOpened }
|
|
||||||
)}
|
|
||||||
onClick={toggleRow}
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'patientName')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex flex-row items-center pl-1">
|
|
||||||
<Icon name={ChevronIconName} className="mr-4" />
|
|
||||||
{PatientName}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'mrn')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{PatientId}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'studyDate')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{format(StudyDate, 'MMM-DD-YYYY')}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'description')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{StudyDescription}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'modality')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{Modalities}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'accession')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{AccessionNumber}
|
|
||||||
</td>
|
|
||||||
<td
|
|
||||||
className={classnames(
|
|
||||||
...tdClasses,
|
|
||||||
getGridColClass(filtersMeta, 'instances')
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
name="series-active"
|
|
||||||
className={classnames('inline-flex mr-2', {
|
|
||||||
'text-primary-active': isOpened,
|
|
||||||
'text-secondary-light': !isOpened,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
{Instances}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{isOpened && (
|
|
||||||
<tr
|
|
||||||
className={classnames('bg-black max-h-0 overflow-hidden')}
|
|
||||||
>
|
|
||||||
<td colSpan="7" className="py-4 pl-12 pr-2">
|
|
||||||
<div className="block">
|
|
||||||
<Button
|
|
||||||
rounded="full"
|
|
||||||
variant="contained"
|
|
||||||
className="mr-4 font-bold"
|
|
||||||
endIcon={
|
|
||||||
<Icon
|
|
||||||
name="launch-arrow"
|
|
||||||
style={{ color: '#21a7c6' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Basic Viewer
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
rounded="full"
|
|
||||||
variant="contained"
|
|
||||||
className="mr-4 font-bold"
|
|
||||||
endIcon={
|
|
||||||
<Icon
|
|
||||||
name="launch-arrow"
|
|
||||||
style={{ color: '#21a7c6' }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Segmentation{' '}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
rounded="full"
|
|
||||||
variant="outlined"
|
|
||||||
endIcon={<Icon name="launch-info" />}
|
|
||||||
className="font-bold"
|
|
||||||
>
|
|
||||||
Module 3
|
|
||||||
</Button>
|
|
||||||
<div className="ml-5 text-lg text-common-bright inline-flex items-center">
|
|
||||||
<Icon
|
|
||||||
name="notificationwarning-diamond"
|
|
||||||
className="mr-2 w-5 h-5"
|
|
||||||
/>
|
|
||||||
Feedback text lorem ipsum dolor sit amet
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4">
|
|
||||||
<Table>
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell size="normal">Description</TableCell>
|
|
||||||
<TableCell size="small">Series</TableCell>
|
|
||||||
<TableCell size="small">Modality</TableCell>
|
|
||||||
<TableCell size="normal">Instances</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
|
|
||||||
<TableBody>
|
|
||||||
{series.map((seriesItem, i) => (
|
|
||||||
<TableRow key={i}>
|
|
||||||
<TableCell size="normal">
|
|
||||||
Patient Protocol
|
|
||||||
</TableCell>
|
|
||||||
<TableCell size="small">
|
|
||||||
{seriesItem.SeriesNumber}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell size="small">
|
|
||||||
{seriesItem.Modality}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell size="normal">
|
|
||||||
{seriesItem.instances.length}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
StudyTableRow.propTypes = {
|
|
||||||
AccessionNumber: PropTypes.string.isRequired,
|
|
||||||
Modalities: PropTypes.string.isRequired,
|
|
||||||
Instances: PropTypes.number.isRequired,
|
|
||||||
PatientId: PropTypes.string.isRequired,
|
|
||||||
PatientName: PropTypes.string.isRequired,
|
|
||||||
StudyDescription: PropTypes.string.isRequired,
|
|
||||||
StudyDate: PropTypes.string.isRequired,
|
|
||||||
series: PropTypes.array.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const StudyListTable = ({ studies, numOfStudies, filtersMeta }) => {
|
|
||||||
const renderTable = () => {
|
|
||||||
return (
|
|
||||||
<table className="w-full text-white">
|
|
||||||
<tbody>
|
|
||||||
{studies.map((study, i) => (
|
|
||||||
<StudyTableRow
|
|
||||||
key={i}
|
|
||||||
AccessionNumber={study.AccessionNumber || ''}
|
|
||||||
Modalities={study.Modalities || ''}
|
|
||||||
Instances={study.Instances || ''}
|
|
||||||
StudyDescription={study.StudyDescription || ''}
|
|
||||||
PatientId={study.PatientId || ''}
|
|
||||||
PatientName={study.PatientName || ''}
|
|
||||||
StudyDate={study.StudyDate || ''}
|
|
||||||
series={study.series || []}
|
|
||||||
filtersMeta={filtersMeta}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderEmpty = () => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center pt-48">
|
|
||||||
<EmptyStudies />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="bg-black">
|
|
||||||
<div className="container m-auto relative">
|
|
||||||
{numOfStudies > 0 && renderTable()}
|
|
||||||
{numOfStudies === 0 && renderEmpty()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
StudyListTable.propTypes = {
|
|
||||||
studies: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
AccessionNumber: PropTypes.string.isRequired,
|
|
||||||
Modalities: PropTypes.string.isRequired,
|
|
||||||
Instances: PropTypes.number.isRequired,
|
|
||||||
PatientId: PropTypes.string.isRequired,
|
|
||||||
PatientName: PropTypes.string.isRequired,
|
|
||||||
StudyDescription: PropTypes.string.isRequired,
|
|
||||||
StudyDate: PropTypes.string.isRequired,
|
|
||||||
series: PropTypes.array.isRequired,
|
|
||||||
})
|
|
||||||
).isRequired,
|
|
||||||
numOfStudies: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StudyListTable;
|
|
||||||
@ -4,6 +4,7 @@ menu: Views
|
|||||||
route: examples/viewer
|
route: examples/viewer
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import { Playground, Props } from 'docz';
|
import { Playground, Props } from 'docz';
|
||||||
import {
|
import {
|
||||||
NavBar,
|
NavBar,
|
||||||
@ -51,6 +52,7 @@ import { tabs } from './studyBrowserMockData';
|
|||||||
onClick: (id) => setActiveMeasurementItem((s) => (s === id ? null : id)),
|
onClick: (id) => setActiveMeasurementItem((s) => (s === id ? null : id)),
|
||||||
onEdit: (id) => alert(`Edit: ${id}`),
|
onEdit: (id) => alert(`Edit: ${id}`),
|
||||||
};
|
};
|
||||||
|
const [activeViewportIndex, setActiveViewportIndex] = useState(0);
|
||||||
return (
|
return (
|
||||||
<DragAndDropProvider>
|
<DragAndDropProvider>
|
||||||
<Header />
|
<Header />
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const studyWithSR = {
|
|||||||
description: 'CHEST/ABD/PELVIS W/CONTRAST',
|
description: 'CHEST/ABD/PELVIS W/CONTRAST',
|
||||||
numInstances: 902,
|
numInstances: 902,
|
||||||
modalities: 'CT,SR',
|
modalities: 'CT,SR',
|
||||||
thumbnails: [
|
displaySets: [
|
||||||
{
|
{
|
||||||
displaySetInstanceUid: 'f69f6asdasd48c-223e-db7f-c4af-b8906641a66e',
|
displaySetInstanceUid: 'f69f6asdasd48c-223e-db7f-c4af-b8906641a66e',
|
||||||
description: 'Multiple line image series description lorem sit',
|
description: 'Multiple line image series description lorem sit',
|
||||||
|
|||||||
@ -191,8 +191,8 @@ class OHIFStandaloneViewer extends Component {
|
|||||||
{match === null ? (
|
{match === null ? (
|
||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<Component match={match} location={this.props.location} />
|
<Component match={match} location={this.props.location} />
|
||||||
)}
|
)}
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
)}
|
)}
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@ -37,9 +37,6 @@ const mapStateToProps = state => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConnectedViewportGrid = connect(
|
const ConnectedViewportGrid = connect(mapStateToProps, null)(ViewportGrid);
|
||||||
mapStateToProps,
|
|
||||||
null
|
|
||||||
)(ViewportGrid);
|
|
||||||
|
|
||||||
export default ConnectedViewportGrid;
|
export default ConnectedViewportGrid;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
|
|||||||
|
|
||||||
const { loadAndCacheDerivedDisplaySets } = utils;
|
const { loadAndCacheDerivedDisplaySets } = utils;
|
||||||
|
|
||||||
const ViewportGrid = function (props) {
|
const ViewportGrid = function(props) {
|
||||||
const {
|
const {
|
||||||
activeViewportIndex,
|
activeViewportIndex,
|
||||||
availablePlugins,
|
availablePlugins,
|
||||||
@ -24,7 +24,7 @@ const ViewportGrid = function (props) {
|
|||||||
studies,
|
studies,
|
||||||
viewportData,
|
viewportData,
|
||||||
children,
|
children,
|
||||||
isStudyLoaded
|
isStudyLoaded,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const rowSize = 100 / numRows;
|
const rowSize = 100 / numRows;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user