Fixes after rebasing

This commit is contained in:
Erik Ziegler 2020-05-15 14:29:27 +02:00
parent 70b7158c9b
commit f3f50d11fe
105 changed files with 920 additions and 3853 deletions

View File

@ -81,8 +81,12 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
/* i18n */
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
'process.env.LOCIZE_PROJECTID': JSON.stringify(
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

View File

@ -180,7 +180,7 @@ class OHIFCornerstoneViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
@ -216,18 +216,21 @@ class OHIFCornerstoneViewport extends Component {
});
}
const debouncedNewImageHandler = debounce(({ currentImageIdIndex, sopInstanceUid }) => {
const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) {
this.props.onNewImage({
StudyInstanceUID,
SOPInstanceUID: sopInstanceUid,
frameIndex: currentImageIdIndex,
activeViewportIndex: viewportIndex,
});
}
}, 700);
const debouncedNewImageHandler = debounce(
({ currentImageIdIndex, sopInstanceUid }) => {
const { displaySet } = this.props.viewportData;
const { StudyInstanceUID } = displaySet;
if (currentImageIdIndex > 0) {
this.props.onNewImage({
StudyInstanceUID,
SOPInstanceUID: sopInstanceUid,
frameIndex: currentImageIdIndex,
activeViewportIndex: viewportIndex,
});
}
},
700
);
return (
<>

View File

@ -36,7 +36,10 @@ describe('measurementServiceMappings.js', () => {
};
mappings = measurementServiceMappingsFactory(measurementServiceMock);
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 = {
toolName: definition,
measurementData: {

View File

@ -8,9 +8,7 @@ import {
import _downloadAndZip from './downloadAndZip';
const {
utils: {
Queue,
},
utils: { Queue },
} = OHIF;
export function getCommands(context) {

View File

@ -52,7 +52,10 @@ class DicomPDFViewport extends Component {
if (!this.props.useNative) {
const pdf = await PDFJS.getDocument(fileURL).promise;
this.setState(state => ({ ...state, pdf }), () => this.updatePDFCanvas());
this.setState(
state => ({ ...state, pdf }),
() => this.updatePDFCanvas()
);
}
}

View File

@ -14,7 +14,7 @@ const OHIFDicomRTStructSopClassHandler = {
id: 'OHIFDicomRTStructSopClassHandler',
type: MODULE_TYPES.SOP_CLASS_HANDLER,
sopClassUIDs,
getDisplaySetFromSeries: function (
getDisplaySetFromSeries: function(
series,
study,
dicomWebClient,
@ -67,7 +67,7 @@ const OHIFDicomRTStructSopClassHandler = {
}
}
rtStructDisplaySet.load = function (referencedDisplaySet, studies) {
rtStructDisplaySet.load = function(referencedDisplaySet, studies) {
return loadRTStruct(
rtStructDisplaySet,
referencedDisplaySet,

View File

@ -1,56 +1,262 @@
import React from 'react';
const LoadingIndicator = ({ height = "200px", width = "200px", expand = false }) => {
const LoadingIndicator = ({
height = '200px',
width = '200px',
expand = false,
}) => {
return (
<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">
<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"
>
<g transform="rotate(0 50 50)">
<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.9166666666666666s" 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.9166666666666666s"
repeatCount="indefinite"
></animate>
</rect>
</g><g transform="rotate(30 50 50)">
<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>
</g>
<g transform="rotate(30 50 50)">
<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>
</g><g transform="rotate(60 50 50)">
<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>
</g>
<g transform="rotate(60 50 50)">
<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>
</g><g transform="rotate(90 50 50)">
<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>
</g>
<g transform="rotate(90 50 50)">
<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>
</g><g transform="rotate(120 50 50)">
<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>
</g>
<g transform="rotate(120 50 50)">
<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>
</g><g transform="rotate(150 50 50)">
<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>
</g>
<g transform="rotate(150 50 50)">
<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>
</g><g transform="rotate(180 50 50)">
<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>
</g>
<g transform="rotate(180 50 50)">
<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>
</g><g transform="rotate(210 50 50)">
<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>
</g>
<g transform="rotate(210 50 50)">
<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>
</g><g transform="rotate(240 50 50)">
<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>
</g>
<g transform="rotate(240 50 50)">
<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>
</g><g transform="rotate(270 50 50)">
<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>
</g>
<g transform="rotate(270 50 50)">
<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>
</g><g transform="rotate(300 50 50)">
<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>
</g>
<g transform="rotate(300 50 50)">
<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>
</g><g transform="rotate(330 50 50)">
<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>
</g>
<g transform="rotate(330 50 50)">
<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>
</g>
</svg>

View File

@ -12,8 +12,8 @@ const PanelSection = ({
expanded = false,
loading = false,
hideVisibleButton = false,
onVisibilityChange = () => { },
onExpandChange = () => { }
onVisibilityChange = () => {},
onExpandChange = () => {},
}) => {
const [isExpanded, setIsExpanded] = useState(expanded);
const [isVisible, setIsVisible] = useState(visible);
@ -27,7 +27,7 @@ const PanelSection = ({
className="dcmrt-panel-section"
style={{
marginBottom: isExpanded ? 0 : 2,
height: isExpanded ? '100%' : 'unset'
height: isExpanded ? '100%' : 'unset',
}}
>
<div className="header">
@ -36,7 +36,7 @@ const PanelSection = ({
{!hideVisibleButton && (
<Icon
className={`eye-icon ${isVisible && 'expanded'}`}
name={isVisible ? "eye" : "eye-closed"}
name={isVisible ? 'eye' : 'eye-closed'}
width="20px"
height="20px"
onClick={() => {
@ -47,7 +47,9 @@ const PanelSection = ({
/>
)}
<Icon
className={`angle-double-${isExpanded ? 'down' : 'up'} ${isExpanded && 'expanded'}`}
className={`angle-double-${
isExpanded ? 'down' : 'up'
} ${isExpanded && 'expanded'}`}
name={`angle-double-${isExpanded ? 'down' : 'up'}`}
width="20px"
height="20px"
@ -59,7 +61,9 @@ const PanelSection = ({
/>
</div>
</div>
{loading && isExpanded && <LoadingIndicator expand height="70px" width="70px" />}
{loading && isExpanded && (
<LoadingIndicator expand height="70px" width="70px" />
)}
{children}
</div>
);
@ -70,13 +74,13 @@ PanelSection.propTypes = {
children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired,
visible: PropTypes.bool,
expanded: PropTypes.bool,
onVisibilityChange: PropTypes.func
onVisibilityChange: PropTypes.func,
};
PanelSection.defaultProps = {
visible: false,
expanded: false,
onVisibilityChange: () => { }
onVisibilityChange: () => {},
};
export default PanelSection;

View File

@ -31,7 +31,13 @@ const refreshViewport = () => {
* @param {number} props.isOpen - isOpen
* @returns component
*/
const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }) => {
const RTPanel = ({
studies,
viewports,
activeIndex,
isOpen,
onContourItemClick,
}) => {
const [selectedContour, setSelectedContour] = useState();
const DEFAULT_SET_INDEX = 0;
const DEFAULT_STATE = {
@ -65,7 +71,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
document.addEventListener('extensiondicomrtrtloaded', updateStructureSets);
return () => {
document.removeEventListener('extensiondicomrtrtloaded', updateStructureSets);
document.removeEventListener(
'extensiondicomrtrtloaded',
updateStructureSets
);
};
}, []);
@ -78,7 +87,9 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
activeViewport.SeriesInstanceUID
);
const studyMetadata = studyMetadataManager.get(activeViewport.StudyInstanceUID);
const studyMetadata = studyMetadataManager.get(
activeViewport.StudyInstanceUID
);
const referencedDisplaysets = studyMetadata.getDerivedDatasets({
referencedSeriesInstanceUID: activeViewport.SeriesInstanceUID,
Modality: 'RTSTRUCT',
@ -89,7 +100,7 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
setState({
referencedDisplaysets,
selectedSet: defaultSet,
sets: viewportSets
sets: viewportSets,
});
} else {
setState(DEFAULT_STATE);
@ -105,7 +116,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
setShowSettings(showSettings && !isOpen);
}, [isOpen]);
const toContourItem = ({ ROINumber, ROIName, RTROIObservations, colorArray, visible }, loadedSet) => {
const toContourItem = (
{ ROINumber, ROIName, RTROIObservations, colorArray, visible },
loadedSet
) => {
let interpretedType = '';
if (RTROIObservations && RTROIObservations.RTROIInterpretedType) {
interpretedType = `(${RTROIObservations.RTROIInterpretedType})`;
@ -137,14 +151,20 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
);
const frameIndex = imageIds.indexOf(imageId);
const SOPInstanceUID = cornerstone.metaData.get('SOPInstanceUID', imageId);
const StudyInstanceUID = cornerstone.metaData.get('StudyInstanceUID', imageId);
const SOPInstanceUID = cornerstone.metaData.get(
'SOPInstanceUID',
imageId
);
const StudyInstanceUID = cornerstone.metaData.get(
'StudyInstanceUID',
imageId
);
onContourItemClick({
StudyInstanceUID,
SOPInstanceUID,
frameIndex,
activeViewportIndex: activeIndex
activeViewportIndex: activeIndex,
});
}}
label={`${ROIName} ${interpretedType}`}
@ -153,7 +173,10 @@ const RTPanel = ({ studies, viewports, activeIndex, isOpen, onContourItemClick }
visible={visible}
onVisibilityChange={() => {
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)}
/>
</div>
{!state.referencedDisplaysets.length && <LoadingIndicator expand height="70px" width="70px" />}
{state.sets && state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
{!state.referencedDisplaysets.length && (
<LoadingIndicator expand height="70px" width="70px" />
)}
{state.sets &&
state.referencedDisplaysets.map(displaySet => {
const { SeriesInstanceUID, metadata, isLoaded } = displaySet;
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(viewports[activeIndex].SeriesInstanceUID);
const module = cornerstoneTools.getModule('rtstruct');
const sets = module.getters.structuresSetsWhichReferenceSeriesInstanceUid(
viewports[activeIndex].SeriesInstanceUID
);
const loadedSet = sets.find(set => set.SeriesInstanceUID === SeriesInstanceUID);
return (
<PanelSection
key={SeriesInstanceUID}
title={metadata.StructureSetLabel}
loading={!isLoaded || !loadedSet}
visible={isLoaded && loadedSet.visible}
hideVisibleButton={!isLoaded}
expanded={isLoaded && loadedSet.SeriesInstanceUID === state.selectedSet.SeriesInstanceUID}
onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct');
loadedSet.ROIContours.forEach(({ ROINumber }) => {
module.setters.toggleROIContour(loadedSet.SeriesInstanceUID, ROINumber);
});
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 }));
const loadedSet = sets.find(
set => set.SeriesInstanceUID === SeriesInstanceUID
);
return (
<PanelSection
key={SeriesInstanceUID}
title={metadata.StructureSetLabel}
loading={!isLoaded || !loadedSet}
visible={isLoaded && loadedSet.visible}
hideVisibleButton={!isLoaded}
expanded={
isLoaded &&
loadedSet.SeriesInstanceUID ===
state.selectedSet.SeriesInstanceUID
}
}}
>
<ScrollableArea>
<TableList headless>
{isLoaded && loadedSet.ROIContours.map(c => toContourItem(c, loadedSet))}
</TableList>
</ScrollableArea>
</PanelSection>
);
})}
onVisibilityChange={newVisibility => {
const module = cornerstoneTools.getModule('rtstruct');
loadedSet.ROIContours.forEach(({ ROINumber }) => {
module.setters.toggleROIContour(
loadedSet.SeriesInstanceUID,
ROINumber
);
});
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>
);
};

View File

@ -15,9 +15,7 @@ const RTSettings = ({ configuration, onBack, onChange }) => {
return (
<div className="settings-section">
<div className="header">{title}</div>
<div className="content">
{children}
</div>
<div className="content">{children}</div>
</div>
);
};
@ -61,8 +59,10 @@ const RTSettings = ({ configuration, onBack, onChange }) => {
RTSettings.propTypes = {
configuration: PropTypes.shape({
lineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
lineWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
.isRequired,
opacity: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
.isRequired,
}).isRequired,
onBack: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,

View File

@ -31,7 +31,7 @@ const StructureSetItem = ({
useEffect(() => {
setIsVisible(visible);
}, [visible])
}, [visible]);
return (
<div className={`dcmrt-structure-set-item ${selected && 'selected'}`}>
@ -99,7 +99,7 @@ StructureSetItem.propTypes = {
StructureSetItem.defaultProps = {
itemClass: '',
onClick: () => { },
onClick: () => {},
};
export default StructureSetItem;

View File

@ -68,7 +68,6 @@ export default async function loadRTStruct(
const rtStructDisplayToolName = TOOL_NAMES.RTSTRUCT_DISPLAY_TOOL;
for (let i = 0; i < ROIContourSequence.length; i++) {
const ROIContour = ROIContourSequence[i];
const { ReferencedROINumber, ContourSequence } = ROIContour;

View File

@ -70,9 +70,7 @@ export default class RTStructDisplayTool extends BaseTool {
}
const colorArray = ROIContourData.colorArray;
const color = `rgba(${colorArray[0]},${colorArray[1]},${
colorArray[2]
},${opacity})`;
const color = `rgba(${colorArray[0]},${colorArray[1]},${colorArray[2]},${opacity})`;
lineWidth;

View File

@ -10,8 +10,6 @@ export default function structureSetReferencesSeriesInstanceUid(
) {
const { referencedSeriesSequence } = StructureSet;
return referencedSeriesSequence.some(
referencedSeries =>
referencedSeries.SeriesInstanceUID ===
SeriesInstanceUID
referencedSeries => referencedSeries.SeriesInstanceUID === SeriesInstanceUID
);
}

View File

@ -154,15 +154,10 @@ function setToggleROIContour(SeriesInstanceUID, ROINumber) {
* Returns an array of StructureSets which reference the given SeriesInstanceUID.
* @param {string} SeriesInstanceUID The SeriesInstanceUID to check.
*/
function getStructuresSetsWhichReferenceSeriesInstanceUid(
SeriesInstanceUID
) {
function getStructuresSetsWhichReferenceSeriesInstanceUid(SeriesInstanceUID) {
const { StructureSets } = state;
return StructureSets.filter(StructureSet =>
structureSetReferencesSeriesInstanceUid(
StructureSet,
SeriesInstanceUID
)
structureSetReferencesSeriesInstanceUid(StructureSet, SeriesInstanceUID)
);
}

View File

@ -12,12 +12,8 @@ const BrushColorSelector = ({ defaultColor, index, onNext, onPrev }) => (
{index}
</div>
<div className="selector-buttons">
<button onClick={onPrev}>
Previous
</button>
<button onClick={onNext}>
Next
</button>
<button onClick={onPrev}>Previous</button>
<button onClick={onNext}>Next</button>
</div>
</div>
);

View File

@ -27,7 +27,7 @@ BrushRadius.propTypes = {
};
BrushRadius.defaultProps = {
onChange: () => { },
onChange: () => {},
min: 1,
max: 50,
step: 1,

View File

@ -17,7 +17,15 @@ ColoredCircle.propTypes = {
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);
useEffect(() => {
@ -61,7 +69,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Relabel
Relabel
</button>
<button
className="btnAction"
@ -70,7 +78,7 @@ const SegmentItem = ({ index, label, onClick, itemClass, color, visible = true,
<span style={{ marginRight: '4px' }}>
<Icon name="edit" width="14px" height="14px" />
</span>
Description
Description
</button>
</div>
)}
@ -90,7 +98,7 @@ SegmentItem.propTypes = {
SegmentItem.defaultProps = {
itemClass: '',
onClick: () => { }
onClick: () => {},
};
export default SegmentItem;

View File

@ -21,7 +21,7 @@ SegmentationItem.propTypes = {
};
SegmentationItem.defaultProps = {
description: ''
description: '',
};
export default SegmentationItem;

View File

@ -68,8 +68,7 @@ const SegmentationPanel = ({
*/
const [state, setState] = useState({
brushRadius: DEFAULT_BRUSH_RADIUS,
brushColor:
'rgba(221, 85, 85, 1)',
brushColor: 'rgba(221, 85, 85, 1)',
selectedSegment: null,
selectedSegmentation: null,
showSegmentationSettings: false,
@ -77,7 +76,7 @@ const SegmentationPanel = ({
labelmapList: [],
segmentList: [],
cachedSegmentsProperties: [],
isLoading: false
isLoading: false,
});
useEffect(() => {
@ -111,7 +110,10 @@ const SegmentationPanel = ({
* allows us to easily watch the module or the segmentations loading process in any other component
* without subscribing to external events.
*/
document.addEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations);
document.addEventListener(
'extensiondicomsegmentationsegloaded',
refreshSegmentations
);
/*
* These are specific to each element;
@ -126,7 +128,10 @@ const SegmentationPanel = ({
);
return () => {
document.removeEventListener('extensiondicomsegmentationsegloaded', refreshSegmentations);
document.removeEventListener(
'extensiondicomsegmentationsegloaded',
refreshSegmentations
);
cornerstoneTools.store.state.enabledElements.forEach(enabledElement =>
enabledElement.removeEventListener(
'cornerstonetoolslabelmapmodified',
@ -173,15 +178,17 @@ const SegmentationPanel = ({
segmentList: [],
}));
}
}, [
viewports,
activeIndex,
state.isLoading
]);
}, [viewports, activeIndex, state.isLoading]);
useEffect(() => {
refreshSegmentations();
}, [viewports, activeIndex, state.selectedSegmentation, activeContexts, state.isLoading]);
}, [
viewports,
activeIndex,
state.selectedSegmentation,
activeContexts,
state.isLoading,
]);
/* Handle open/closed panel behaviour */
useEffect(() => {
@ -369,9 +376,13 @@ const SegmentationPanel = ({
return !segmentsHidden[segmentIndex];
};
const cachedSegmentProperties = state.cachedSegmentsProperties[segmentNumber];
const cachedSegmentProperties =
state.cachedSegmentsProperties[segmentNumber];
let visible = isSegmentVisible();
if (cachedSegmentProperties && cachedSegmentProperties.visible !== visible) {
if (
cachedSegmentProperties &&
cachedSegmentProperties.visible !== visible
) {
toggleSegmentVisibility();
}
@ -399,7 +410,9 @@ const SegmentationPanel = ({
onSegmentVisibilityChange(segmentNumber, newVisibility);
}
updateCachedSegmentsProperties(segmentNumber, { visible: newVisibility });
updateCachedSegmentsProperties(segmentNumber, {
visible: newVisibility,
});
refreshViewport();
}}
/>
@ -422,10 +435,9 @@ const SegmentationPanel = ({
const segmentsProperties = state.cachedSegmentsProperties;
const segmentProperties = state.cachedSegmentsProperties[segmentNumber];
segmentsProperties[segmentNumber] =
segmentProperties ?
{ ...segmentProperties, ...properties } :
properties;
segmentsProperties[segmentNumber] = segmentProperties
? { ...segmentProperties, ...properties }
: properties;
updateState('cachedSegmentsProperties', segmentsProperties);
};
@ -495,7 +507,10 @@ const SegmentationPanel = ({
refreshViewport();
};
const disabledConfigurationFields = ['outlineAlpha', 'shouldRenderInactiveLabelmaps'];
const disabledConfigurationFields = [
'outlineAlpha',
'shouldRenderInactiveLabelmaps',
];
if (state.showSegmentationSettings) {
return (
<SegmentationSettings
@ -622,7 +637,7 @@ const _setActiveLabelmap = async (
displaySet,
firstImageId,
activeLabelmapIndex,
callback = () => { },
callback = () => {},
onDisplaySetLoadFailure
) => {
if (displaySet.labelmapIndex === activeLabelmapIndex) {

View File

@ -19,7 +19,7 @@ const uiGrayDark = computedstyle.getPropertyValue('--ui-gray-dark');
const segmentationSelectStyles = {
singleValue: (base, state) => ({
...base,
width: '100%'
width: '100%',
}),
control: (base, state) => ({
...base,

View File

@ -4,7 +4,12 @@ import { Range } from '@ohif/ui';
import './SegmentationSettings.css';
const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields = [] }) => {
const SegmentationSettings = ({
configuration,
onBack,
onChange,
disabledFields = [],
}) => {
const [state, setState] = useState({
renderFill: configuration.renderFill,
renderOutline: configuration.renderOutline,
@ -13,7 +18,7 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields
outlineAlpha: configuration.outlineAlpha,
outlineWidth: configuration.outlineWidth,
fillAlphaInactive: configuration.fillAlphaInactive,
outlineAlphaInactive: configuration.outlineAlphaInactive
outlineAlphaInactive: configuration.outlineAlphaInactive,
});
useEffect(() => {
@ -78,7 +83,9 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields
step={1}
min={0}
max={100}
onChange={event => save('outlineAlpha', toFloat(event.target.value))}
onChange={event =>
save('outlineAlpha', toFloat(event.target.value))
}
/>
)}
{!disabledFields.includes('outlineWidth') && (
@ -89,50 +96,64 @@ const SegmentationSettings = ({ configuration, onBack, onChange, disabledFields
step={1}
min={0}
max={5}
onChange={event => save('outlineWidth', parseInt(event.target.value))}
onChange={event =>
save('outlineWidth', parseInt(event.target.value))
}
/>
)}
</>
)}
</div>
{(state.renderFill || state.renderOutline) && !disabledFields.includes('shouldRenderInactiveLabelmaps') && (
<div
className="settings-group"
style={{ marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0 }}
>
<CustomCheck
label="Render inactive segmentations"
checked={state.shouldRenderInactiveLabelmaps}
onChange={() => check('shouldRenderInactiveLabelmaps')}
/>
{state.shouldRenderInactiveLabelmaps && (
<>
{state.renderFill && !disabledFields.includes('fillAlphaInactive') && (
<CustomRange
label="Fill Opacity"
showPercentage
step={1}
min={0}
max={100}
value={state.fillAlphaInactive * 100}
onChange={event => save('fillAlphaInactive', toFloat(event.target.value))}
/>
)}
{state.renderOutline && !disabledFields.includes('outlineAlphaInactive') && (
<CustomRange
label="Outline Opacity"
showPercentage
step={1}
min={0}
max={100}
value={state.outlineAlphaInactive * 100}
onChange={event => save('outlineAlphaInactive', toFloat(event.target.value))}
/>
)}
</>
)}
</div>
)}
{(state.renderFill || state.renderOutline) &&
!disabledFields.includes('shouldRenderInactiveLabelmaps') && (
<div
className="settings-group"
style={{
marginBottom: state.shouldRenderInactiveLabelmaps ? 15 : 0,
}}
>
<CustomCheck
label="Render inactive segmentations"
checked={state.shouldRenderInactiveLabelmaps}
onChange={() => check('shouldRenderInactiveLabelmaps')}
/>
{state.shouldRenderInactiveLabelmaps && (
<>
{state.renderFill &&
!disabledFields.includes('fillAlphaInactive') && (
<CustomRange
label="Fill Opacity"
showPercentage
step={1}
min={0}
max={100}
value={state.fillAlphaInactive * 100}
onChange={event =>
save('fillAlphaInactive', toFloat(event.target.value))
}
/>
)}
{state.renderOutline &&
!disabledFields.includes('outlineAlphaInactive') && (
<CustomRange
label="Outline Opacity"
showPercentage
step={1}
min={0}
max={100}
value={state.outlineAlphaInactive * 100}
onChange={event =>
save(
'outlineAlphaInactive',
toFloat(event.target.value)
)
}
/>
)}
</>
)}
</div>
)}
</div>
);
};
@ -169,8 +190,10 @@ SegmentationSettings.propTypes = {
renderFill: PropTypes.bool.isRequired,
renderOutline: PropTypes.bool.isRequired,
shouldRenderInactiveLabelmaps: PropTypes.bool.isRequired,
fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, /* TODO: why fillAlpha is string? */
fillAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired /* TODO: why fillAlpha is string? */,
outlineAlpha: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
.isRequired /* TODO: why fillAlpha is string? */,
outlineWidth: PropTypes.number.isRequired,
fillAlphaInactive: PropTypes.number.isRequired,
outlineAlphaInactive: PropTypes.number.isRequired,

View File

@ -46,7 +46,7 @@ export default {
const onSegmentVisibilityChangeHandler = (segmentNumber, visible) => {
commandsManager.runCommand('setSegmentConfiguration', {
segmentNumber,
visible
visible,
});
};
@ -55,7 +55,7 @@ export default {
globalOpacity: configuration.fillAlpha,
outlineThickness: configuration.outlineWidth,
renderOutline: configuration.renderOutline,
visible: configuration.renderFill
visible: configuration.renderFill,
});
};

View File

@ -32,16 +32,16 @@ class LoadingIndicator extends PureComponent {
</div>
</div>
) : (
<div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<p>
{this.props.t('Loading...')}
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
{percComplete}
</p>
</div>
<div className="imageViewerLoadingIndicator loadingIndicator">
<div className="indicatorContents">
<p>
{this.props.t('Loading...')}
<i className="fa fa-spin fa-circle-o-notch fa-fw" />
{percComplete}
</p>
</div>
)}
</div>
)}
</React.Fragment>
);
}

View File

@ -49,7 +49,7 @@ class OHIFVTKViewport extends Component {
state = {
volumes: null,
paintFilterLabelMapImageData: null,
paintFilterBackgroundImageData: null
paintFilterBackgroundImageData: null,
};
static propTypes = {
@ -69,7 +69,7 @@ class OHIFVTKViewport extends Component {
};
static defaultProps = {
onScroll: () => { },
onScroll: () => {},
};
static id = 'OHIFVTKViewport';
@ -156,9 +156,11 @@ class OHIFVTKViewport extends Component {
const { activeLabelmapIndex } = brushStackState;
const labelmap3D = brushStackState.labelmaps3D[activeLabelmapIndex];
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(isHidden => {
return { visible: !isHidden };
});
this.segmentsDefaultProperties = labelmap3D.segmentsHidden.map(
isHidden => {
return { visible: !isHidden };
}
);
const vtkLabelmapID = `${firstImageId}_${activeLabelmapIndex}`;
@ -349,7 +351,7 @@ class OHIFVTKViewport extends Component {
if (
displaySet.displaySetInstanceUID !==
prevDisplaySet.displaySetInstanceUID ||
prevDisplaySet.displaySetInstanceUID ||
displaySet.SOPInstanceUID !== prevDisplaySet.SOPInstanceUID ||
displaySet.frameIndex !== prevDisplaySet.frameIndex
) {
@ -435,7 +437,7 @@ class OHIFVTKViewport extends Component {
segmentsDefaultProperties: this.segmentsDefaultProperties,
onNewSegmentationRequested: () => {
this.setStateFromProps();
}
},
}}
onScroll={this.props.onScroll}
/>

View File

@ -165,7 +165,7 @@ const commandsModule = ({ commandsManager }) => {
segmentNumber,
frameIndex,
frame,
done = () => { }
done = () => {},
}) => {
let api = apis[viewports.activeViewportIndex];
@ -180,10 +180,13 @@ const commandsModule = ({ commandsManager }) => {
displaySetInstanceUID,
SOPClassUID,
SOPInstanceUID,
frameIndex,
frameIndex
);
const imageDataObject = getImageData(stack.imageIds, displaySetInstanceUID);
const imageDataObject = getImageData(
stack.imageIds,
displaySetInstanceUID
);
let pixelIndex = 0;
let x = 0;
@ -209,7 +212,10 @@ const commandsModule = ({ commandsManager }) => {
y /= count;
const position = [x, y, frameIndex];
const worldPos = _convertModelToWorldSpace(position, imageDataObject.vtkImageData);
const worldPos = _convertModelToWorldSpace(
position,
imageDataObject.vtkImageData
);
api.svgWidgets.crosshairsWidget.moveCrosshairs(worldPos, apis);
done();

View File

@ -8,10 +8,13 @@ const { studyMetadataManager } = utils;
let isVisible = true;
const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportIndex) => {
const _isDisplaySetReconstructable = (
viewportSpecificData = {},
activeViewportIndex
) => {
if (!viewportSpecificData[activeViewportIndex]) {
return false;
};
}
const { displaySetInstanceUID, StudyInstanceUID } = viewportSpecificData[
activeViewportIndex
@ -27,11 +30,13 @@ const _isDisplaySetReconstructable = (viewportSpecificData = {}, activeViewportI
return false;
}
const displaySet = study._displaySets.find(set => set.displaySetInstanceUID === displaySetInstanceUID);
const displaySet = study._displaySets.find(
set => set.displaySetInstanceUID === displaySetInstanceUID
);
if (!displaySet) {
return false;
};
}
return displaySet.isReconstructable;
};
@ -52,12 +57,12 @@ function VTKMPRToolbarButton({
return {
viewportSpecificData,
activeViewportIndex,
}
};
});
isVisible = _isDisplaySetReconstructable(
viewportSpecificData,
activeViewportIndex,
activeViewportIndex
);
return (

View File

@ -131,4 +131,4 @@ const definitions = [
export default {
definitions,
defaultContext: 'ACTIVE_VIEWPORT::VTK',
}
};

View File

@ -39,7 +39,8 @@
"@babel/runtime": "7.7.6",
"ajv": "^6.10.0",
"dcmjs": "^0.12.4",
"dicomweb-client": "^0.5.2",
"dicomweb-client": "^0.6.0",
"immer": "6.0.2",
"isomorphic-base64": "^1.0.2",
"lodash.clonedeep": "^4.5.0",
"lodash.merge": "^4.6.1",

View File

@ -79,7 +79,7 @@ export class StudyMetadata extends Metadata {
Object.defineProperty(this, 'studyInstanceUID', {
configurable: false,
enumerable: false,
get: function () {
get: function() {
return this.getStudyInstanceUID();
},
});

View File

@ -182,7 +182,7 @@ export default class ExtensionManager {
commandsManager: this._commandsManager,
appConfig: this._appConfig,
configuration,
api: this._api
api: this._api,
});
if (!extensionModule) {

View File

@ -7,6 +7,7 @@ import classes, { CommandsManager, HotkeysManager } from './classes/';
import DICOMWeb from './DICOMWeb';
import DICOMSR from './DICOMSR';
import cornerstone from './cornerstone.js';
import errorHandler from './errorHandler.js';
import hangingProtocols from './hanging-protocols';
import header from './header.js';
import log from './log.js';

View File

@ -2,7 +2,12 @@ import redux from './index.js';
describe('redux exports', () => {
test('have not changed', () => {
const expectedExports = ['actions', 'reducers', 'localStorage', 'sessionStorage'].sort();
const expectedExports = [
'actions',
'reducers',
'localStorage',
'sessionStorage',
].sort();
const exports = Object.keys(redux).sort();

View File

@ -34,7 +34,10 @@ describe('MeasurementService.js', () => {
unit: 'mm',
area: 123,
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,
};
toAnnotation = () => annotation;

View File

@ -1,6 +1,4 @@
export default class Queue {
constructor(limit) {
this.limit = limit;
this.size = 0;

View File

@ -167,8 +167,8 @@ function _getSpacingIssue(spacing, averageSpacing) {
function _getPerpendicularDistance(a, b) {
return Math.sqrt(
Math.pow(a[0] - b[0], 2) +
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
Math.pow(a[1] - b[1], 2) +
Math.pow(a[2] - b[2], 2)
);
}

View File

@ -1,7 +1,9 @@
export default function makeDeferred() {
let reject, resolve, promise = new Promise(function (res, rej) {
resolve = res;
reject = rej;
});
let reject,
resolve,
promise = new Promise(function(res, rej) {
resolve = res;
reject = rej;
});
return Object.freeze({ promise, resolve, reject });
}

View File

@ -116,9 +116,8 @@ function _getPaletteColor(server, paletteColorLookupTableData, lutDescriptor) {
const bits = lutDescriptor[2];
const arrayBufferToPaletteColorLUT = arraybuffer => {
const byteArray = bits === 16 ?
new Uint16Array(arraybuffer) :
new Uint8Array(arraybuffer);
const byteArray =
bits === 16 ? new Uint16Array(arraybuffer) : new Uint8Array(arraybuffer);
const lut = [];
for (let i = 0; i < numLutEntries; i++) {

View File

@ -124,7 +124,7 @@ export default function getPixelSpacingInformation(instance) {
};
}
log.info(
log.info(
'Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.'
);
}

View File

@ -1,7 +1,7 @@
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
'ar': {
export default {
ar: {
UserPreferencesModal,
}
},
};

View File

@ -9,7 +9,7 @@ import StudyList from './StudyList.json';
import UserPreferencesModal from './UserPreferencesModal.json';
import ViewportDownloadForm from './ViewportDownloadForm.json';
export default {
export default {
'en-US': {
AboutModal,
Buttons,
@ -21,5 +21,5 @@ export default {
StudyList,
UserPreferencesModal,
ViewportDownloadForm,
}
},
};

View File

@ -6,8 +6,8 @@ import MeasurementTable from './MeasurementTable.json';
import StudyList from './StudyList.json';
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
'es': {
export default {
es: {
Buttons,
CineDialog,
Common,
@ -15,5 +15,5 @@ export default {
MeasurementTable,
StudyList,
UserPreferencesModal,
}
},
};

View File

@ -4,12 +4,12 @@ import Common from './Common.json';
import Header from './Header.json';
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
'fr': {
export default {
fr: {
Buttons,
CineDialog,
Common,
Header,
UserPreferencesModal,
}
},
};

View File

@ -4,12 +4,12 @@ import Common from './Common.json';
import Header from './Header.json';
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
export default {
'ja-JP': {
Buttons,
CineDialog,
Common,
Header,
UserPreferencesModal,
}
},
};

View File

@ -2,10 +2,10 @@ import Buttons from './Buttons.json';
import Common from './Common.json';
import Header from './Header.json';
export default {
'nl': {
export default {
nl: {
Buttons,
Common,
Header,
}
},
};

View File

@ -6,7 +6,7 @@ import DatePicker from './DatePicker.json';
import Header from './Header.json';
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
export default {
'pt-BR': {
AboutModal,
Buttons,
@ -15,5 +15,5 @@ export default {
DatePicker,
Header,
UserPreferencesModal,
}
},
};

View File

@ -5,13 +5,13 @@ import Header from './Header.json';
import StudyList from './StudyList.json';
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
'vi': {
export default {
vi: {
Buttons,
CineDialog,
Common,
Header,
StudyList,
UserPreferencesModal,
}
},
};

View File

@ -6,8 +6,8 @@ import MeasurementTable from './MeasurementTable.json';
import StudyList from './StudyList.json';
import UserPreferencesModal from './UserPreferencesModal.json';
export default {
'zh': {
export default {
zh: {
Buttons,
CineDialog,
Common,
@ -15,5 +15,5 @@ export default {
MeasurementTable,
StudyList,
UserPreferencesModal,
}
},
};

View File

@ -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
React component library powered by `docz`
@ -60,4 +17,3 @@ yarn build # npm run build
# Serve (the "build" output)
yarn serve # npm run serve
```
>>>>>>> 4a8675655... feat: nuke UI project; start new with updated tooling BREAKING_CHANGE (#1468)

View File

@ -1,8 +1,11 @@
const path = require('path');
// ~~ Plugins
const PnpWebpackPlugin = require(`pnp-webpack-plugin`); // Required until Webpack@5
exports.onCreateWebpackConfig = args => {
args.actions.setWebpackConfig({
resolve: {
plugins: [PnpWebpackPlugin],
// Note the '..' in the path because docz gatsby project lives in the '.docz' directory
modules: [
// monorepo root
@ -16,5 +19,8 @@ exports.onCreateWebpackConfig = args => {
// symlinks: true,
// },
},
resolveLoader: {
plugins: [PnpWebpackPlugin.moduleLoader(module)],
},
});
};

View File

@ -19,10 +19,12 @@
],
"scripts": {
"start": "yarn run dev",
"build:styles": "postcss src/styles/tailwind.css -o src/gatsby-theme-docz/app.css",
"dev": "docz dev",
"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": {
"classnames": "^2.2.6",

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';
import PropTypes from 'prop-types';
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';
const defaults = {
variant: 'contained',
color: 'default',
size: 'medium',
radius: 'medium',
disabled: false,
fullWidth: false,
rounded: 'medium',
size: 'medium',
type: 'button',
variant: 'contained',
};
const radiusClasses = {
const roundedClasses = {
none: '',
small: 'rounded-sm',
small: 'rounded',
medium: 'rounded-md',
large: 'rounded-lg',
full: 'rounded-full',
@ -77,7 +78,7 @@ const Button = ({
variant = defaults.variant,
color = defaults.color,
size = defaults.size,
radius = defaults.radius,
rounded = defaults.rounded,
disabled = defaults.disabled,
type = defaults.type,
fullWidth = defaults.fullWidth,
@ -86,14 +87,24 @@ const Button = ({
className,
...rest
}) => {
const baseClasses =
'inline-flex items-center outline-none transition duration-300 ease-in-out font-bold focus:outline-none';
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();
if (rest.onClick) {
rest.onClick(e);
@ -106,11 +117,13 @@ const Button = ({
className,
baseClasses,
variantClasses[variant][color],
radiusClasses[radius],
roundedClasses[rounded],
sizeClasses[size],
fullWidthClasses[fullWidth],
disabledClasses[disabled]
)}
ref={buttonElement}
onClick={handleOnClick}
type={type}
{...rest}
>

View File

@ -24,10 +24,10 @@ import { Button } from '@ohif/ui';
<Button variant="outlined" className="m-1">
Default
</Button>
<Button variant="contained" color="primary" className="m-1">
<Button variant="outlined" color="primary" className="m-1">
Primary
</Button>
<Button variant="contained" color="secondary" className="m-1">
<Button variant="outlined" color="secondary" className="m-1">
Secondary
</Button>
<Button variant="outlined" color="white" className="m-1">
@ -36,17 +36,17 @@ import { Button } from '@ohif/ui';
</div>
</Playground>
## Outlined Buttons
## Contained Buttons
<Playground>
<div className="p-4">
<Button variant="contained" className="m-1">
Default
</Button>
<Button variant="outlined" color="primary" className="m-1">
<Button variant="contained" color="primary" className="m-1">
Primary
</Button>
<Button variant="outlined" color="secondary" className="m-1">
<Button variant="contained" color="secondary" className="m-1">
Secondary
</Button>
<Button variant="contained" color="white" className="m-1">
@ -209,19 +209,19 @@ import { Button } from '@ohif/ui';
<Playground>
<div className="p-4">
<div>
<Button variant="text" size="small" className="m-1">
<Button variant="contained" size="small" className="m-1">
Small
</Button>
<Button variant="contained" size="medium" className="m-1">
Medium
</Button>
<Button variant="outlined" size="large" className="m-1">
<Button variant="contained" size="large" className="m-1">
Large
</Button>
</div>
<div>
<Button
variant="text"
variant="contained"
size="small"
color="primary"
className="m-1"
@ -237,39 +237,65 @@ import { Button } from '@ohif/ui';
Medium
</Button>
<Button
variant="outlined"
variant="contained"
size="large"
color="primary"
className="m-1"
>
large
Large
</Button>
</div>
<div>
<Button
variant="text"
size="small"
color="secondary"
className="m-1"
>
Small
</Button>
<Button
variant="contained"
size="medium"
color="secondary"
className="m-1"
>
Medium
</Button>
<Button
variant="outlined"
size="large"
color="secondary"
className="m-1"
>
Large
</Button>
<Button
variant="contained"
size="small"
color="secondary"
className="m-1"
>
Small
</Button>
<Button
variant="contained"
size="medium"
color="secondary"
className="m-1"
>
Medium
</Button>
<Button
variant="contained"
size="large"
color="secondary"
className="m-1"
>
Large
</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>

View File

@ -46,7 +46,7 @@ const renderYearsOptions = () => {
return options;
};
const DateRange = props => {
const DateRange = (props) => {
const { onChange, startDate, endDate } = props;
const [focusedInput, setFocusedInput] = useState(null);
const renderYearsOptionsCallback = useCallback(renderYearsOptions, []);
@ -82,11 +82,11 @@ const DateRange = props => {
onYearSelect: PropTypes.func,
};
const handleMonthChange = event => {
const handleMonthChange = (event) => {
onMonthSelect(month, event.target.value);
};
const handleYearChange = event => {
const handleYearChange = (event) => {
onYearSelect(month, event.target.value);
};
@ -140,7 +140,7 @@ const DateRange = props => {
})
}}
focusedInput={focusedInput}
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
onFocusChange={(updatedVal) => setFocusedInput(updatedVal)}
/** OPTIONAL */
renderCalendarInfo={renderDatePresets}
renderMonthElement={renderMonthElement}
@ -150,7 +150,7 @@ const DateRange = props => {
closeDatePicker: 'Close',
clearDates: 'Clear dates',
}}
isOutsideRange={day => !isInclusivelyBeforeDay(day, moment())}
isOutsideRange={(day) => !isInclusivelyBeforeDay(day, moment())}
hideKeyboardShortcutsPanel={true}
numberOfMonths={1}
showClearDates={false}

View File

@ -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 activeButtonClasses = 'bg-primary-main';
const getInitialActiveTab = tabs => {
const getInitialActiveTab = (tabs) => {
return tabs && tabs[0] && tabs[0].name;
};
const getTrackedSeries = displaySets => {
const getTrackedSeries = (displaySets) => {
let trackedSeries = 0;
displaySets.forEach(displaySet => {
displaySets.forEach((displaySet) => {
if (displaySet.isTracked) {
trackedSeries++;
}
@ -25,9 +25,10 @@ const getTrackedSeries = displaySets => {
const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
const [tabActive, setTabActive] = useState(getInitialActiveTab(tabs));
const [studyActive, setStudyActive] = useState(null);
const [thumbnailActive, setThumbnailActive] = useState(null);
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)) {
return;
@ -36,9 +37,9 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
return tabData.studies.map(
({
studyInstanceUid,
studyDate,
studyDescription,
instances,
date,
description,
numInstances,
modalities,
displaySets,
}) => {
@ -46,9 +47,9 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
return (
<React.Fragment key={studyInstanceUid}>
<StudyItem
studyDate={studyDate}
studyDescription={studyDescription}
instances={instances}
date={date}
description={description}
numInstances={numInstances}
modalities={modalities}
trackedSeries={getTrackedSeries(displaySets)}
isActive={isActive}
@ -64,16 +65,17 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
<ThumbnailList
thumbnails={displaySets}
thumbnailActive={thumbnailActive}
onThumbnailClick={thumbnailId => {
setThumbnailActive(
thumbnailId === thumbnailActive ? null : thumbnailId
);
onThumbnailClick={(thumbnailId) => {
setThumbnailActive(
thumbnailId === thumbnailActive ? null : thumbnailId
)
if (onClickThumbnail) {
// TODO: what is thumbnailId? Should pass display set instead
onClickThumbnail(thumbnailId);
if (onClickThumbnail) {
// TODO: what is thumbnailId? Should pass display set instead
onClickThumbnail(thumbnailId);
}
}
}}
}
/>
)}
</React.Fragment>
@ -90,7 +92,7 @@ const StudyBrowser = ({ tabs, onClickStudy, onClickThumbnail }) => {
color="inherit"
className="border border-secondary-light rounded-md"
>
{tabs.map(tab => {
{tabs.map((tab) => {
const { name, label } = tab;
const isActive = tabActive === name;
return (
@ -131,7 +133,7 @@ StudyBrowser.propTypes = {
studyInstanceUid: PropTypes.string.isRequired,
date: PropTypes.string,
numInstances: PropTypes.number,
modality: PropTypes.string,
modalities: PropTypes.string,
description: PropTypes.string,
displaySets: PropTypes.arrayOf(
PropTypes.shape({

View File

@ -18,7 +18,88 @@ import { StudyBrowser } from '@ohif/ui';
## Basic usage
<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>
## Properties

View File

@ -8,9 +8,9 @@ const baseClasses =
'first:border-0 border-t border-secondary-light cursor-pointer outline-none';
const StudyItem = ({
studyDate,
studyDescription,
instances,
date,
description,
numInstances,
modalities,
trackedSeries,
isActive,
@ -29,15 +29,15 @@ const StudyItem = ({
>
<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="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">
<Icon name="group-layers" className="text-blue-300 mx-2 w-4" />
{instances}
{numInstances}
</div>
</div>
<div className="flex flex-row py-1">
<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>
{!!trackedSeries && (

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Thumbnail, ThumbnailNoImage, ThumbnailTracked } from '@ohif/ui';
const ThumbnailList = ({ thumbnails }) => {
const ThumbnailList = ({ thumbnails, thumbnailActive, onThumbnailClick }) => {
return (
<div className="bg-black py-3">
{thumbnails.map(
@ -17,10 +17,11 @@ const ThumbnailList = ({ thumbnails }) => {
seriesDate,
viewportIdentificator,
isTracked,
isActive,
imageSrc,
imageAltText,
}) => {
const isActive = thumbnailActive === displaySetInstanceUid;
switch (componentType) {
case 'thumbnail':
return (
@ -33,7 +34,7 @@ const ThumbnailList = ({ thumbnails }) => {
imageAltText={imageAltText}
viewportIdentificator={viewportIdentificator}
isActive={isActive}
onClick={() => {}}
onClick={() => onThumbnailClick(displaySetInstanceUid)}
/>
);
case 'thumbnailNoImage':
@ -43,7 +44,7 @@ const ThumbnailList = ({ thumbnails }) => {
modality={modality}
seriesDate={seriesDate}
description={description}
onClick={() => {}}
onClick={() => onThumbnailClick(displaySetInstanceUid)}
/>
);
case 'thumbnailTracked':
@ -58,7 +59,7 @@ const ThumbnailList = ({ thumbnails }) => {
viewportIdentificator={viewportIdentificator}
isTracked={isTracked}
isActive={isActive}
onClick={() => {}}
onClick={() => onThumbnailClick(displaySetInstanceUid)}
/>
);
default:
@ -87,9 +88,10 @@ ThumbnailList.propTypes = {
]).isRequired,
viewportIdentificator: PropTypes.string,
isTracked: PropTypes.bool,
isActive: PropTypes.bool,
})
),
thumbnailActive: PropTypes.string,
onThumbnailClick: PropTypes.func,
};
export default ThumbnailList;

View File

@ -8,18 +8,19 @@ const ThumbnailNoImage = ({ description, seriesDate, modality, onClick }) => {
<div
className="flex flex-row flex-1 px-4 py-3 cursor-pointer"
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-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}
</div>
<span className="text-blue-300 text-base">{seriesDate}</span>
</div>
<div className="text-white text-base">{description}</div>
<div className="text-white text-base ml-12">{description}</div>
</div>
</div>
);

View File

@ -21,14 +21,12 @@ import { Thumbnail } from '@ohif/ui';
<Playground>
<div className="flex flex-1 items-center justify-center p-4">
<div className="w-56">
<Thumbnail
imageAltText="DisplaySet thumbnail image"
<div className="w-64">
<ThumbnailNoImage
modality={'SR'}
seriesDate="13-Mar-1999"
description="Multiple line image series description lorem sit"
seriesNumber={4}
numInstances={902}
onClick={() => alert('Thumbnail was clicked!')}
isActive={false}
onClick={() => alert('ThumbnailNoImage was clicked!')}
/>
</div>
</div>
@ -36,4 +34,4 @@ import { Thumbnail } from '@ohif/ui';
## Properties
<Props of={Thumbnail} />
<Props of={ThumbnailNoImage} />

View File

@ -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;

View File

@ -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 };

View File

@ -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;

View File

@ -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,
});
};
}

View File

@ -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)}
/>
);
});
};
}

View File

@ -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 {};
};
}

View File

@ -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

View File

@ -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)}
/>
);
});
};
}

View File

@ -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} />

View File

@ -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,
},
],
},
];

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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');
}

View File

@ -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 };

View File

@ -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;

View File

@ -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 };

View File

@ -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);
});
}

View File

@ -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;

View File

@ -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} />

View File

@ -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>
);
}
};
}

View File

@ -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);
}
};
}

View File

@ -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 };

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 };

View File

@ -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 };

View File

@ -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;

View File

@ -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;

View File

@ -4,6 +4,7 @@ menu: Views
route: examples/viewer
---
import { useState } from 'react';
import { Playground, Props } from 'docz';
import {
NavBar,
@ -51,6 +52,7 @@ import { tabs } from './studyBrowserMockData';
onClick: (id) => setActiveMeasurementItem((s) => (s === id ? null : id)),
onEdit: (id) => alert(`Edit: ${id}`),
};
const [activeViewportIndex, setActiveViewportIndex] = useState(0);
return (
<DragAndDropProvider>
<Header />

View File

@ -4,7 +4,7 @@ const studyWithSR = {
description: 'CHEST/ABD/PELVIS W/CONTRAST',
numInstances: 902,
modalities: 'CT,SR',
thumbnails: [
displaySets: [
{
displaySetInstanceUid: 'f69f6asdasd48c-223e-db7f-c4af-b8906641a66e',
description: 'Multiple line image series description lorem sit',

View File

@ -191,8 +191,8 @@ class OHIFStandaloneViewer extends Component {
{match === null ? (
<></>
) : (
<Component match={match} location={this.props.location} />
)}
<Component match={match} location={this.props.location} />
)}
</CSSTransition>
)}
</Route>

View File

@ -37,9 +37,6 @@ const mapStateToProps = state => {
};
};
const ConnectedViewportGrid = connect(
mapStateToProps,
null
)(ViewportGrid);
const ConnectedViewportGrid = connect(mapStateToProps, null)(ViewportGrid);
export default ConnectedViewportGrid;

View File

@ -12,7 +12,7 @@ import EmptyViewport from './EmptyViewport.js';
const { loadAndCacheDerivedDisplaySets } = utils;
const ViewportGrid = function (props) {
const ViewportGrid = function(props) {
const {
activeViewportIndex,
availablePlugins,
@ -24,7 +24,7 @@ const ViewportGrid = function (props) {
studies,
viewportData,
children,
isStudyLoaded
isStudyLoaded,
} = props;
const rowSize = 100 / numRows;

Some files were not shown because too many files have changed in this diff Show More