Fixes after rebasing

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

No files matched your search

+26 -13
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}
>
+60 -34
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>
@@ -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}
@@ -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({
@@ -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
@@ -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 && (
@@ -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;
@@ -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>
);
@@ -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} />
@@ -1,26 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import './ContextMenu.css';
const ContextMenu = ({ items, onClick }) => {
return (
<div className="ContextMenu" onContextMenu={e => e.preventDefault()}>
<ul>
{items.map((item, index) => (
<li key={index}>
<button className="form-action" onClick={() => onClick(item)}>
<span key={index}>{item.label}</span>
</button>
</li>
))}
</ul>
</div>
);
};
ContextMenu.propTypes = {
items: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
};
export default ContextMenu;
@@ -1,129 +0,0 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './LayoutChooser.styl';
/**
* Adds the 'hover' class to cells above and to the left of the current cell
* This is used to "fill in" the grid that the user will change the layout to,
* if they click on a specific table cell.
**/
class LayoutChooser extends PureComponent {
static propTypes = {
Rows: PropTypes.number.isRequired,
Columns: PropTypes.number.isRequired,
visible: PropTypes.bool.isRequired,
selectedCell: PropTypes.object,
boxSize: PropTypes.number.isRequired,
cellBorder: PropTypes.number.isRequired,
onClick: PropTypes.func,
onChange: PropTypes.func,
};
static defaultProps = {
Rows: 3,
Columns: 3,
visible: true,
boxSize: 20,
cellBorder: 1,
selectedCell: {
row: -1,
col: -1,
},
};
constructor(props) {
super(props);
this.emptyCell = {
row: -1,
column: -1,
};
this.state = {
table: [[]],
selectedCell: this.props.selectedCell,
};
}
componentDidMount() {
this.highlightCells(this.emptyCell);
}
onClick(currentCell) {
this.setState({
selectedCell: currentCell,
});
this.highlightCells(currentCell);
if (this.props.onClick) {
this.props.onClick(currentCell);
}
if (this.props.onChange) {
this.props.onChange(currentCell);
}
}
isRange = (cell, parentCell) => {
return cell.row <= parentCell.row && cell.col <= parentCell.col;
};
highlightCells = currentCell => {
let table = [];
for (let row = 0; row < this.props.Rows; row++) {
let newRow = [];
for (let col = 0; col < this.props.Columns; col++) {
let cell = { row: row, col: col };
if (this.isRange(cell, currentCell)) {
cell.className = 'hover';
} else if (
this.isRange(currentCell, this.emptyCell) &&
this.isRange(cell, this.state.selectedCell)
) {
cell.className = 'selectedBefore';
}
newRow.push(cell);
}
table.push(newRow);
}
this.setState({ table: table });
};
render() {
let Columns = this.props.Columns;
const style = {
display: this.props.visible ? 'block' : 'none',
minWidth:
Columns * this.props.boxSize + (Columns + 5) * this.props.cellBorder,
};
return (
<div
className="layoutChooser layoutChooser-dropdown-menu"
role="menu"
style={style}
>
<table>
<tbody>
{this.state.table.map((row, i) => {
return (
<tr key={i}>
{row.map((cell, j) => {
return (
<td
className={cell.className}
style={{
width: this.props.boxSize,
height: this.props.boxSize,
border: 'solid 1px black',
}}
key={j}
onMouseEnter={() => this.highlightCells(cell)}
onMouseLeave={() => this.highlightCells(this.emptyCell)}
onClick={() => this.onClick(cell)}
/>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
}
}
export { LayoutChooser };
@@ -1,65 +0,0 @@
.measurementItem
.rowActions
margin-left: -1px;
background-color: var(--ui-gray-darker)
height: 0
overflow: hidden
transition: all 0.3s ease
visibility: hidden
padding-left: 14px
.btnAction
background-color: transparent;
border: none;
color: var(--default-color)
cursor: pointer
line-height: 35px
height: 35px
transition: all 0.3s ease
&:hover, &:active
color: var(--text-primary-color)
i
margin-right: 4px;
&.selected
.rowActions
height: auto;
visibility: visible
.measurementLocation
margin-top: 9px;
overflow: hidden;
max-width: calc(100% - 50px);
text-overflow: ellipsis;
overflow-wrap: normal;
white-space: nowrap;
.measurementDisplayText
display: inline-block;
margin-top: 9px;
padding-left: 9px;
width: 90px;
border-left: 1px solid var(--text-secondary-color);
color: var(--text-primary-color);
.itemIndex
-webkit-box-sizing: initial;
-moz-box-sizing: initial;
box-sizing: initial;
&.hasWarnings
.itemIndex
opacity: 1
background-color: #e29e4a;
color: #fff;
.warning-icon
display: block;
margin: 7px auto 0;
svg
width: 22px;
height: 20px;
pointer-events: inherit;
@@ -1,151 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { StudiesList } from './StudiesList.js';
import { ScrollableArea } from './../../ScrollableArea/ScrollableArea.js';
import { SeriesList } from './SeriesList.js';
import './QuickSwitch.styl';
export class QuickSwitch extends Component {
static propTypes = {
side: PropTypes.string,
studyListData: PropTypes.array.isRequired,
onSeriesSelected: PropTypes.func.isRequired,
seriesData: PropTypes.array,
onStudySelected: PropTypes.func,
activeStudyInstanceUID: PropTypes.string,
activeDisplaySetInstanceUID: PropTypes.string,
};
constructor(props) {
super(props);
this.state = {
seriesQuickSwitchOpen: false,
sideClass: this.props.side || '',
activeStudyInstanceUID: this.props.activeStudyInstanceUID,
activeDisplaySetInstanceUID: this.props.activeDisplaySetInstanceUID,
};
}
componentDidUpdate(prevProps) {
const props = this.props;
if (props.activeStudyInstanceUID !== prevProps.activeStudyInstanceUID) {
this.setState({
activeStudyInstanceUID: props.activeStudyInstanceUID,
});
}
if (
props.activeDisplaySetInstanceUID !==
prevProps.activeDisplaySetInstanceUID
) {
this.setState({
activeDisplaySetInstanceUID: props.activeDisplaySetInstanceUID,
});
}
}
render() {
const quickSwitchClass = this.state.seriesQuickSwitchOpen
? 'series-triggered'
: '';
return (
<div
className={`series-quick-switch clearfix noselect ${this.state.sideClass} ${quickSwitchClass}`}
onMouseLeave={this.hideSeriesSwitch}
>
<div className="series-switch" onMouseEnter={this.showSeriesSwitch}>
<div className="title-label">Series</div>
<div className="series-box">
{this.getSmallListItems()}
<ScrollableArea scrollStep={201} class="series-browser">
<SeriesList
seriesItems={this.getSeriesItems()}
onClick={this.onSeriesClick}
activeDisplaySetInstanceUID={
this.state.activeDisplaySetInstanceUID
}
/>
</ScrollableArea>
</div>
</div>
<div className="study-switch">
<div className="title-label">Study</div>
<div className="study-box">
<ScrollableArea scrollStep={91} class="study-browser">
<StudiesList
studyListData={this.props.studyListData}
onClick={this.onStudyClick}
activeStudyInstanceUID={this.state.activeStudyInstanceUID}
/>
</ScrollableArea>
</div>
</div>
</div>
);
}
getSeriesItems = () => {
let seriesData;
if (this.props.seriesData) {
seriesData = this.props.seriesData;
} else if (this.state.activeStudyInstanceUID) {
const study = this.props.studyListData.find(
study => study.StudyInstanceUID === this.state.activeStudyInstanceUID
);
seriesData = study.thumbnails;
} else {
seriesData = this.props.studyListData[0].thumbnails;
}
return seriesData || [];
};
getSmallListItems = () => {
const seriesItems = this.getSeriesItems() || [];
return seriesItems.map((seriesData, index) => {
const active =
seriesData.displaySetInstanceUID ===
this.state.activeDisplaySetInstanceUID;
return (
<div key={index} className={`series-item ${active ? 'active' : ''}`} />
);
});
};
onStudyClick = studyDataSelected => {
if (this.props.onStudySelected) {
this.props.onStudySelected(studyDataSelected);
}
this.setState({
activeStudyInstanceUID: studyDataSelected.StudyInstanceUID,
seriesQuickSwitchOpen: true,
});
};
onSeriesClick = seriesDataSelected => {
this.setState({
activeDisplaySetInstanceUID: seriesDataSelected.displaySetInstanceUID,
});
this.props.onSeriesSelected(seriesDataSelected);
};
showSeriesSwitch = () => {
this.setState({
seriesQuickSwitchOpen: true,
});
};
hideSeriesSwitch = () => {
this.setState({
seriesQuickSwitchOpen: false,
});
};
}
@@ -1,40 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Thumbnail } from './../studyBrowser';
import './SeriesList.styl';
export class SeriesList extends Component {
static propTypes = {
seriesItems: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
activeDisplaySetInstanceUID: PropTypes.string,
};
render() {
return (
<React.Fragment>
<div className="study-browser-series clearfix thumbnails-wrapper">
<div className="study-series-container">{this.getSeriesItems()}</div>
</div>
</React.Fragment>
);
}
getSeriesItems = () => {
return this.props.seriesItems.map((seriesData, index) => {
return (
<Thumbnail
key={seriesData.displaySetInstanceUID}
id={`series_thumb_${index}`}
{...seriesData}
active={
seriesData.displaySetInstanceUID ===
this.props.activeDisplaySetInstanceUID
}
onClick={() => this.props.onClick(seriesData)}
/>
);
});
};
}
@@ -1,60 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './StudiesItem.styl';
export class StudiesItem extends Component {
static propTypes = {
onClick: PropTypes.func.isRequired,
studyData: PropTypes.object.isRequired,
active: PropTypes.bool,
};
render() {
const {
StudyDate,
StudyDescription,
modalities,
studyAvailable,
} = this.props.studyData;
const activeClass = this.props.active ? ' active' : '';
const hasDescriptionAndDate = StudyDate && StudyDescription;
return (
<div
className={`studyBrowseItem${activeClass}`}
onClick={this.props.onClick}
>
<div className="studyItemBox">
<div className="studyModality">
<div
className="studyModalityText"
style={this.getModalitiesStyle()}
>
{modalities}
</div>
</div>
<div className="studyText">
{hasDescriptionAndDate ? (
<React.Fragment>
<div className="studyDate">{StudyDate}</div>
<div className="studyDescription">{StudyDescription}</div>
</React.Fragment>
) : (
<div className="studyAvailability">
{studyAvailable ? (
<React.Fragment>N/A</React.Fragment>
) : (
<React.Fragment>Click to load</React.Fragment>
)}
</div>
)}
</div>
</div>
</div>
);
}
getModalitiesStyle = () => {
return {};
};
}
@@ -1,133 +0,0 @@
$boxHoverBackgroundColor = #14191E
$boxHoverBorderColor = #2d4660
$boxActiveBorderColor = #131D29
$boxWidth = 50px
$borderThickness = 2px
$boxBorderRadius = 8px
$nestingMargin = 6px
$spacerX = 7px
$spacerY = 12px
.studyBrowseItem
margin-bottom: 10px;
position: relative
// required transformation to make inner fixed elements relative to this one
transform: scale(1)
&.active
.studyItemBox
border-color: var(--active-color)
.studyModality
color: var(--primary-background-color)
&
&:before
&:after
background-color: var(--active-color)
border-color: $boxActiveBorderColor
&:not(.active) .studyBrowserSeries
max-height: 0 !important
.studySeriesContainer
opacity: 0
transform: translateY(-100%)
.studyBrowserSeries
overflow: hidden
transition: max-height 0.3s ease
.studySeriesContainer
opacity: 1
transition: opacity 0.3s ease, transform 0.3s ease
transform: translateY(0)
transform-origin: 50% 0%
.studyItemBox
border: 3px solid var(--ui-border-color-dark)
border-radius: 12px
cursor: pointer
padding: $spacerY $spacerX ($spacerY - 1)
position: relative
transition: $sidebarTransition
background-color: $boxHoverBackgroundColor
z-index: 0
&:hover
border-color: var(--active-color)
&.additional
color: var(--text-secondary-color)
.studyModality
color: var(--primary-background-color)
&
&:before
&:after
background-color: var(--active-color)
&.additional
color: var(--text-primary-color)
font-size: 16px
font-weight: normal
height: 91px
line-height: 91px
padding: 0
text-align: center
.studyText
font-size: 13px
left: ($spacerX * 3) + $boxWidth + ($nestingMargin * 3)
line-height: 14px
position: absolute
right: $spacerX
top: $spacerY
.studyDate
margin-top: 8px
color: var(--text-secondary-color)
.studyDescription
margin-top: 8px
color: var(--text-primary-color)
.studyAvailability
margin-top: 24px
color: var(--text-primary-color)
.studyModality
color: var(--text-secondary-color)
font-size: 20px
line-height: $boxWidth
margin-left: $nestingMargin * 2
margin-top: $nestingMargin * 2
position: relative
.studyModalityText
height: 100%
overflow: hidden
text-align: center
text-transform: uppercase
&
&:before
&:after
border: $borderThickness solid var(--primary-background-color)
background-color: var(--box-background-color)
border-radius: $boxBorderRadius
height: $boxWidth + ($borderThickness * 2)
transition($sidebarTransition)
width: $boxWidth + ($borderThickness * 2)
&:before
&:after
display: block
content: ''
position: absolute
&:before
top: -($borderThickness + $nestingMargin)
left: -($borderThickness + $nestingMargin)
z-index: -1
&:after
top: -($borderThickness + ($nestingMargin * 2))
left: -($borderThickness + ($nestingMargin * 2))
z-index: -2
@@ -1,37 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { StudiesItem } from './StudiesItem.js';
import './StudiesList.styl';
export class StudiesList extends Component {
static propTypes = {
class: PropTypes.string,
studyListData: PropTypes.array.isRequired,
onClick: PropTypes.func.isRequired,
activeStudyInstanceUID: PropTypes.string,
};
render() {
return (
<div className={`studiesList ${this.props.class}`}>
{this.getBrowserItems()}
</div>
);
}
getBrowserItems = () => {
return this.props.studyListData.map((studyData, index) => {
return (
<StudiesItem
key={index}
studyData={studyData}
active={
studyData.StudyInstanceUID === this.props.activeStudyInstanceUID
}
onClick={event => this.props.onClick(studyData)}
/>
);
});
};
}
@@ -1,61 +0,0 @@
---
name: Quick Switch
menu: Components
route: /components/quick-switch
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { QuickSwitch } from './../index.js'
//
import studies from './studies.js'
# Quick Switch
## Basic usage
<Playground>
<State initial={{
studyListData: studies,
}}>
{({ state, setState }) => (
<QuickSwitch
studyListData={state.studyListData}
// If this is always activeStudy's thumbnails,
// Why do we need to specify this?
seriesData={
state.studyListData.find((study) => {
return !!study.active;
}).thumbnails}
onSeriesSelected={(selectedSeries) => {
const { studyListData } = state;
const selectedId = selectedSeries.displaySetInstanceUID;
studyListData.forEach(study => {
study.thumbnails.forEach(series => {
series.active = series.displaySetInstanceUID === selectedId;
})
});
setState({ studyListData });
}}
onStudySelected={(selectedStudy) => {
const { studyListData } = state;
const selectedId = selectedStudy.StudyInstanceUID;
studyListData.forEach(study => {
study.active = study.StudyInstanceUID === selectedId;
});
setState({ studyListData });
}}
/>
)}
</State>
</Playground>
## API
<Props of={QuickSwitch} />
@@ -1,76 +0,0 @@
export default [
{
StudyInstanceUID: '10001',
StudyDescription: 'Anti-PD-1',
modalities: 'CT',
StudyDate: '18-nov-2018',
active: false,
thumbnails: [
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg',
SeriesDescription: 'Anti-PD-1_Lung',
SeriesNumber: '2',
numImageFrames: 512,
displaySetInstanceUID: '10001-1',
stackPercentComplete: 30,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg',
SeriesDescription: 'Anti-PD-1_MELANOMA',
SeriesNumber: '2',
InstanceNumber: '1',
numImageFrames: 256,
displaySetInstanceUID: '10001-2',
stackPercentComplete: 70,
},
],
},
{
StudyInstanceUID: '10002',
StudyDescription: 'CPTAC',
modalities: 'CT',
StudyDate: '16-aug-2017',
active: true,
thumbnails: [
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg',
SeriesDescription: 'CPTAC-GBM',
active: true,
SeriesNumber: '2',
numImageFrames: 512,
displaySetInstanceUID: '10002-1',
stackPercentComplete: 100,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg',
SeriesDescription: 'CPTAC-CM',
SeriesNumber: '2',
InstanceNumber: '1',
displaySetInstanceUID: '10002-2',
numImageFrames: 256,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg',
SeriesDescription: 'CPTAC-HNSCC',
SeriesNumber: '2',
InstanceNumber: '1',
displaySetInstanceUID: '10002-3',
numImageFrames: 256,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg',
SeriesDescription: 'CPTAC-LSCC',
SeriesNumber: '2',
InstanceNumber: '1',
displaySetInstanceUID: '10002-4',
numImageFrames: 256,
},
],
},
];
@@ -1,84 +0,0 @@
.RoundedButtonGroup {
--height: 25px;
position: relative;
z-index: 0;
}
.roundedButtonWrapper {
cursor: pointer;
display: inline-block;
float: left;
margin-left: -2px;
text-decoration: none;
text-align: center;
}
.roundedButtonWrapper.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.RoundedButtonGroup .roundedButtonWrapper .roundedButton {
align-items: center;
background-color: var(--ui-gray-dark);
border: 2px solid var(--ui-border-color-dark);
color: var(--text-secondary-color);
display: flex;
font-size: 15px;
font-weight: 500;
justify-content: center;
height: var(--height);
line-height: var(--height);
padding: 0 22px;
position: relative;
text-transform: uppercase;
transition: var(--sidebar-transition);
z-index: 1;
}
.roundedButtonWrapper .roundedButton svg .roundedButtonWrapper .roundedButton span {
margin: 0 2px;
}
.roundedButtonWrapper .roundedButton i {
line-height: 15px;
font-size: 15px;
}
.roundedButtonWrapper .bottomLabel {
padding: 0 10px 0 10px;
}
.bottomLabel {
color: var(--text-secondary-color);
font-size: 12px;
font-weight: 500;
line-height: 12px;
margin-top: 8px;
}
.roundedButtonWrapper:first-child {
margin-left: 0;
}
.RoundedButtonGroup .roundedButtonWrapper:first-child .roundedButton {
border-bottom-left-radius: var(--height);
border-top-left-radius: var(--height);
}
.RoundedButtonGroup .roundedButtonWrapper:last-child .roundedButton {
border-bottom-right-radius: var(--height);
border-top-right-radius: var(--height);
}
.roundedButtonWrapper:hover .roundedButton {
background-color: var(--box-background-color);
color: var(--ui-gray-dark);
}
.roundedButtonWrapper.active .roundedButton {
background-color: var(--active-color);
border-color: var(--ui-border-color-active);
color: var(--ui-gray-dark);
z-index: 2;
}
@@ -1,196 +0,0 @@
.sb-container {
position: fixed;
width: var(--snackbar-size);
padding: 20px;
z-index: var(--snackbar-zIndex);
box-sizing: border-box;
height: auto;
}
.sb-topLeft {
top: 0;
bottom: auto;
left: 0;
right: auto;
}
.sb-topCenter {
left: 50%;
top: 0;
bottom: auto;
transform: translateX(-50%);
}
.sb-topRight {
top: 0;
bottom: auto;
left: auto;
right: 0;
}
.sb-bottomLeft {
top: auto;
bottom: 0px;
left: 0px;
right: auto;
}
.sb-bottomCenter {
left: 50%;
bottom: 0;
top: auto;
transform: translateX(-50%);
}
.sb-bottomRight {
top: auto;
bottom: 0px;
left: auto;
right: 0px;
margin: 10px 0 0;
}
.sb-topLeft .sb-item,
.sb-topCenter .sb-item,
.sb-topRight .sb-item {
margin: 10px 0 0;
}
.sb-bottomLeft .sb-item,
.sb-bottomCenter .sb-item,
.sb-bottomRight .sb-item {
margin: 0 0 10px;
}
.sb-closeBtn {
height: 20px;
opacity: 1;
overflow: hidden;
padding: 2px;
text-align: center;
text-shadow: none;
width: 20px;
cursor: pointer;
position: absolute;
right: 5px;
top: 5px;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.6);
border-radius: 100%;
}
.sb-closeBtn:hover {
background: #fff;
}
.sb-closeIcon {
display: block;
font-size: 0;
height: 100%;
line-height: 0;
overflow: hidden;
position: relative;
width: 100%;
}
.sb-closeIcon:after,
.sb-closeIcon:before {
content: ' ';
display: block;
height: 2px;
transition: all 0.3s ease;
width: 12px;
background-color: #222;
opacity: 1;
position: absolute;
}
.sb-closeIcon:before {
left: 4px;
top: 3px;
transform: rotate(45deg);
transform-origin: 0px 50%;
}
.sb-closeIcon:after {
right: 3px;
top: 5px;
transform: rotate(-45deg);
transform-origin: calc(100% - 3px) 50%;
}
.sb-title {
font-size: 16px;
font-weight: bold;
}
.sb-message {
font-size: 14px;
word-break: normal;
}
.sb-item {
position: relative;
transition: height 300ms ease;
animation: fadein 1s;
padding: 20px;
color: white;
overflow: hidden;
border-radius: 4px;
box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.2), 0 1px 18px 0 rgba(0, 0, 0, 0.12),
0 3px 5px -1px rgba(0, 0, 0, 0.14);
}
.sb-item a {
color: white;
text-decoration: underline;
}
.sb-container .sb-hidden {
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
height: 0;
opacity: 0;
transition: all 300ms ease;
}
.sb-success {
background-color: var(--snackbar-success);
}
.sb-error {
background-color: var(--snackbar-error);
}
.sb-warning {
background-color: var(--snackbar-warning);
}
.sb-info {
background-color: var(--snackbar-info);
}
@keyframes fadein {
from {
opacity: 0;
top: 30px;
}
to {
opacity: 1;
top: 0;
}
}
/* Internet Explorer */
@-ms-keyframes fadein {
from {
opacity: 0;
top: 30px;
}
to {
opacity: 1;
top: 0;
}
}
@@ -1,144 +0,0 @@
/* global cornerstone */
import './ImageThumbnail.styl';
import { utils } from '@ohif/core';
import React, { useState, useEffect, createRef } from 'react';
import PropTypes from 'prop-types';
import ViewportErrorIndicator from '../../viewer/ViewportErrorIndicator';
import ViewportLoadingIndicator from '../../viewer/ViewportLoadingIndicator';
// TODO: How should we have this component depend on Cornerstone?
// - Passed in as a prop?
// - Set as external dependency?
// - Pass in the entire load and render function as a prop?
//import cornerstone from 'cornerstone-core';
function ImageThumbnail(props) {
const {
width,
height,
imageSrc,
imageId,
stackPercentComplete,
error: propsError,
} = props;
const [isLoading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [image, setImage] = useState({});
const canvasRef = createRef();
let loadingOrError;
let cancelablePromise;
if (propsError || error) {
loadingOrError = <ViewportErrorIndicator />;
} else if (isLoading) {
loadingOrError = <ViewportLoadingIndicator />;
}
const showStackLoadingProgressBar = stackPercentComplete !== undefined;
const shouldRenderToCanvas = () => {
return imageId && !imageSrc;
};
const fetchImagePromise = () => {
if (!cancelablePromise) {
return;
}
setLoading(true);
cancelablePromise
.then(response => {
setImage(response);
})
.catch(error => {
if (error.isCanceled) return;
setLoading(false);
setError(true);
throw new Error(error);
});
};
const setImagePromise = () => {
if (shouldRenderToCanvas()) {
cancelablePromise = utils.makeCancelable(
cornerstone.loadAndCacheImage(imageId)
);
}
};
const purgeCancelablePromise = () => {
if (cancelablePromise) {
cancelablePromise.cancel();
}
};
useEffect(() => {
return () => {
purgeCancelablePromise();
};
}, [purgeCancelablePromise]);
useEffect(() => {
if (image.imageId) {
cornerstone.renderToCanvas(canvasRef.current, image);
setLoading(false);
}
}, [canvasRef, image, image.imageId]);
useEffect(() => {
if (!image.imageId || image.imageId !== imageId) {
purgeCancelablePromise();
setImagePromise();
fetchImagePromise();
}
}, [fetchImagePromise, image.imageId, imageId, purgeCancelablePromise, setImagePromise]);
return (
<div className="ImageThumbnail">
<div className="image-thumbnail-canvas">
{shouldRenderToCanvas() ? (
<canvas ref={canvasRef} width={width} height={height} />
) : (
<img
className="static-image"
src={imageSrc}
//width={this.props.width}
height={height}
alt={''}
/>
)}
</div>
{loadingOrError}
{showStackLoadingProgressBar && (
<div className="image-thumbnail-progress-bar">
<div
className="image-thumbnail-progress-bar-inner"
style={{ width: `${stackPercentComplete}%` }}
/>
</div>
)}
{isLoading && <div className="image-thumbnail-loading-indicator"></div>}
</div>
);
}
ImageThumbnail.propTypes = {
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool,
width: PropTypes.number,
height: PropTypes.number,
stackPercentComplete: PropTypes.number.isRequired,
};
ImageThumbnail.defaultProps = {
error: false,
stackPercentComplete: 0,
width: 217,
height: 123,
};
export default ImageThumbnail;
@@ -1,103 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Thumbnail } from './Thumbnail.js';
import './StudyBrowser.styl';
function StudyBrowser(props) {
const {
studies,
onThumbnailClick,
onThumbnailDoubleClick,
supportsDrag,
} = props;
return (
<div className="study-browser">
<div className="scrollable-study-thumbnails">
{studies
.map((study, studyIndex) => {
const { StudyInstanceUID } = study;
return study.thumbnails.map((thumb, thumbIndex) => {
// TODO: Thumb has more props than we care about?
const {
altImageText,
displaySetInstanceUID,
imageId,
InstanceNumber,
numImageFrames,
SeriesDescription,
SeriesNumber,
stackPercentComplete,
} = thumb;
return (
<div
key={thumb.displaySetInstanceUID}
className="thumbnail-container"
data-cy="thumbnail-list"
>
<Thumbnail
supportsDrag={supportsDrag}
key={`${studyIndex}_${thumbIndex}`}
id={`${studyIndex}_${thumbIndex}`} // Unused?
// Study
StudyInstanceUID={StudyInstanceUID} // used by drop
// Thumb
altImageText={altImageText}
imageId={imageId}
InstanceNumber={InstanceNumber}
displaySetInstanceUID={displaySetInstanceUID} // used by drop
numImageFrames={numImageFrames}
SeriesDescription={SeriesDescription}
SeriesNumber={SeriesNumber}
stackPercentComplete={stackPercentComplete}
// Events
onClick={onThumbnailClick.bind(
undefined,
displaySetInstanceUID
)}
onDoubleClick={onThumbnailDoubleClick}
/>
</div>
);
});
})
.flat()}
</div>
</div>
);
}
const noop = () => {};
StudyBrowser.propTypes = {
studies: PropTypes.arrayOf(
PropTypes.shape({
StudyInstanceUID: PropTypes.string.isRequired,
thumbnails: PropTypes.arrayOf(
PropTypes.shape({
altImageText: PropTypes.string,
displaySetInstanceUID: PropTypes.string.isRequired,
imageId: PropTypes.string,
InstanceNumber: PropTypes.number,
numImageFrames: PropTypes.number,
SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.number,
stackPercentComplete: PropTypes.number,
})
),
})
).isRequired,
supportsDrag: PropTypes.bool,
onThumbnailClick: PropTypes.func,
onThumbnailDoubleClick: PropTypes.func,
};
StudyBrowser.defaultProps = {
studies: [],
supportsDrag: true,
onThumbnailClick: noop,
onThumbnailDoubleClick: noop,
};
export { StudyBrowser };
@@ -1,152 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useDrag } from 'react-dnd';
import ImageThumbnail from './ImageThumbnail';
import classNames from 'classnames';
import './Thumbnail.styl';
function ThumbnailFooter({
SeriesDescription,
SeriesNumber,
InstanceNumber,
numImageFrames,
}) {
const infoOnly = !SeriesDescription;
const getInfo = (value, icon, className = '') => {
return (
<div className={classNames('item item-series', className)}>
<div className="icon">{icon}</div>
<div className="value">{value}</div>
</div>
);
};
const getSeriesInformation = (
SeriesNumber,
InstanceNumber,
numImageFrames
) => {
if (!SeriesNumber && !InstanceNumber && !numImageFrames) {
return;
}
return (
<div className="series-information">
{getInfo(SeriesNumber, 'S:')}
{getInfo(InstanceNumber, 'I:')}
{getInfo(numImageFrames, '', 'image-frames')}
</div>
);
};
return (
<div className={classNames('series-details', { 'info-only': infoOnly })}>
<div className="series-description">{SeriesDescription}</div>
{getSeriesInformation(SeriesNumber, InstanceNumber, numImageFrames)}
</div>
);
}
function Thumbnail(props) {
const {
active,
altImageText,
error,
displaySetInstanceUID,
imageId,
imageSrc,
InstanceNumber,
numImageFrames,
SeriesDescription,
SeriesNumber,
stackPercentComplete,
StudyInstanceUID,
onClick,
onDoubleClick,
onMouseDown,
supportsDrag,
} = props;
const [collectedProps, drag, dragPreview] = useDrag({
// `droppedItem` in `dropTarget`
// The only data it will have access to
item: {
StudyInstanceUID,
displaySetInstanceUID,
type: 'thumbnail', // Has to match `dropTarget`'s type
},
canDrag: function(monitor) {
return supportsDrag;
},
});
const hasImage = imageSrc || imageId;
const hasAltText = altImageText !== undefined;
return (
<div
ref={drag}
className={classNames('thumbnail', { active: active })}
onClick={onClick}
onDoubleClick={onDoubleClick}
onMouseDown={onMouseDown}
>
{/* SHOW IMAGE */}
{hasImage && (
<ImageThumbnail
imageSrc={imageSrc}
imageId={imageId}
error={error}
stackPercentComplete={stackPercentComplete}
/>
)}
{/* SHOW TEXT ALTERNATIVE */}
{!hasImage && hasAltText && (
<div className={'alt-image-text p-x-1'}>
<h1>{altImageText}</h1>
</div>
)}
{ThumbnailFooter(props)}
</div>
);
}
const noop = () => {};
Thumbnail.propTypes = {
supportsDrag: PropTypes.bool,
id: PropTypes.string.isRequired,
displaySetInstanceUID: PropTypes.string.isRequired,
StudyInstanceUID: PropTypes.string.isRequired,
imageSrc: PropTypes.string,
imageId: PropTypes.string,
error: PropTypes.bool,
active: PropTypes.bool,
stackPercentComplete: PropTypes.number,
/**
altImageText will be used when no imageId or imageSrc is provided.
It will be displayed inside the <div>. This is useful when it is difficult
to make a preview for a type of DICOM series (e.g. DICOM-SR)
*/
altImageText: PropTypes.string,
SeriesDescription: PropTypes.string,
SeriesNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
InstanceNumber: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
numImageFrames: PropTypes.number,
onDoubleClick: PropTypes.func,
onClick: PropTypes.func,
onMouseDown: PropTypes.func,
};
Thumbnail.defaultProps = {
supportsDrag: false,
active: false,
error: false,
stackPercentComplete: 0,
onDoubleClick: noop,
onClick: noop,
onMouseDown: noop,
};
export { Thumbnail };
@@ -1,84 +0,0 @@
import React, { Component } from 'react';
import { DropTarget } from 'react-dnd';
import PropTypes from 'prop-types';
import './ExampleDropTarget.css';
// Drag sources and drop targets only interact
// if they have the same string type.
const Types = {
THUMBNAIL: 'thumbnail',
};
const divTarget = {
drop(props, monitor, component) {
// Note: For this example we use setState, but in
// OHIF we will update the redux store instead
const item = monitor.getItem();
component.setState({
item: {
id: item.id,
SeriesDescription: item.SeriesDescription,
},
});
return { id: 'ExampleDropTarget' };
},
};
// TODO: Find out why we can't move this into the Example app instead.
// It looks like the context isn't properly shared.
class CustomDropTarget extends Component {
static className = 'ExampleDropTarget';
state = {
item: null,
};
static defaultProps = {
isOver: false,
};
static propTypes = {
connectDropTarget: PropTypes.func.isRequired,
canDrop: PropTypes.bool.isRequired,
isOver: PropTypes.bool.isRequired,
};
render() {
const { canDrop, isOver, connectDropTarget } = this.props;
const isActive = canDrop && isOver;
let className = ExampleDropTarget.className;
if (isActive) {
className += ' hovered';
} else if (canDrop) {
className += ' can-drop';
}
return connectDropTarget(
<div className={className}>
<h4>
{isActive
? 'Release to drop'
: 'Drag / Drop something from the Study Browser here'}
</h4>
<p className="study-drop-results">
{this.state.item && JSON.stringify(this.state.item)}
</p>
</div>
);
}
}
const collect = (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
});
const ExampleDropTarget = DropTarget(Types.THUMBNAIL, divTarget, collect)(
CustomDropTarget
);
export { ExampleDropTarget };
@@ -1,75 +0,0 @@
export const studies = [
{
thumbnails: [
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_Lung.jpg',
SeriesDescription: 'Anti-PD-1_Lung',
active: true,
SeriesNumber: '2',
numImageFrames: 512,
stackPercentComplete: 30,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/Anti-PD-1_MELANOMA.jpg',
SeriesDescription: 'Anti-PD-1_MELANOMA',
SeriesNumber: '2',
InstanceNumber: '1',
numImageFrames: 256,
stackPercentComplete: 70,
},
{
altImageText: 'SR',
SeriesDescription: 'Imaging Measurement Report',
SeriesNumber: '3',
stackPercentComplete: 100,
},
],
},
{
thumbnails: [
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-GBM.jpg',
SeriesDescription: 'CPTAC-GBM',
active: true,
SeriesNumber: '2',
numImageFrames: 512,
stackPercentComplete: 100,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-CM.jpg',
SeriesDescription: 'CPTAC-CM',
SeriesNumber: '2',
InstanceNumber: '1',
numImageFrames: 256,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-HNSCC.jpg',
SeriesDescription: 'CPTAC-HNSCC',
SeriesNumber: '2',
InstanceNumber: '1',
numImageFrames: 256,
},
{
imageSrc:
'https://raw.githubusercontent.com/crowds-cure/cancer/master/public/screenshots/CPTAC-LSCC.jpg',
SeriesDescription: 'CPTAC-LSCC',
SeriesNumber: '2',
InstanceNumber: '1',
numImageFrames: 256,
},
],
},
];
export function onThumbnailClick() {
console.warn('onThumbnailClick');
}
export function onThumbnailDoubleClick() {
console.warn('onThumbnailDoubleClick');
}
@@ -1,409 +0,0 @@
import './StudyList.styl';
import React from 'react';
import classNames from 'classnames';
import TableSearchFilter from './TableSearchFilter.js';
import PropTypes from 'prop-types';
import { StudyListLoadingText } from './StudyListLoadingText.js';
import { useTranslation } from 'react-i18next';
const getContentFromUseMediaValue = (
displaySize,
contentArrayMap,
defaultContent
) => {
const content =
displaySize in contentArrayMap
? contentArrayMap[displaySize]
: defaultContent;
return content;
};
/**
*
*
* @param {*} props
* @returns
*/
function StudyList(props) {
const {
isLoading,
hasError,
studies,
sort,
onSort: handleSort,
filterValues,
onFilterChange: handleFilterChange,
onSelectItem: handleSelectItem,
studyListDateFilterNumDays,
displaySize,
} = props;
const { t, ready: translationsAreReady } = useTranslation('StudyList');
const largeTableMeta = [
{
displayText: t('PatientName'),
fieldName: 'PatientName',
inputType: 'text',
size: 330,
},
{
displayText: t('MRN'),
fieldName: 'PatientID',
inputType: 'text',
size: 378,
},
{
displayText: t('AccessionNumber'),
fieldName: 'AccessionNumber',
inputType: 'text',
size: 180,
},
{
displayText: t('StudyDate'),
fieldName: 'StudyDate',
inputType: 'date-range',
size: 300,
},
{
displayText: t('Modality'),
fieldName: 'modalities',
inputType: 'text',
size: 114,
},
{
displayText: t('StudyDescription'),
fieldName: 'StudyDescription',
inputType: 'text',
size: 335,
},
];
const mediumTableMeta = [
{
displayText: `${t('PatientName')} / ${t('MRN')}`,
fieldName: 'patientNameOrId',
inputType: 'text',
size: 250,
},
{
displayText: t('Description'),
fieldName: 'accessionOrModalityOrDescription',
inputType: 'text',
size: 350,
},
{
displayText: t('StudyDate'),
fieldName: 'StudyDate',
inputType: 'date-range',
size: 300,
},
];
const smallTableMeta = [
{
displayText: t('Search'),
fieldName: 'allFields',
inputType: 'text',
size: 100,
},
];
const tableMeta = getContentFromUseMediaValue(
displaySize,
{ large: largeTableMeta, medium: mediumTableMeta, small: smallTableMeta },
smallTableMeta
);
const totalSize = tableMeta
.map(field => field.size)
.reduce((prev, next) => prev + next);
return translationsAreReady ? (
<table className="table table--striped table--hoverable">
<colgroup>
{tableMeta.map((field, i) => {
const size = field.size;
const percentWidth = (size / totalSize) * 100.0;
return <col key={i} style={{ width: `${percentWidth}%` }} />;
})}
</colgroup>
<thead className="table-head">
<tr className="filters">
<TableSearchFilter
meta={tableMeta}
values={filterValues}
onSort={handleSort}
onValueChange={handleFilterChange}
sortFieldName={sort.fieldName}
sortDirection={sort.direction}
studyListDateFilterNumDays={studyListDateFilterNumDays}
/>
</tr>
</thead>
<tbody className="table-body" data-cy="study-list-results">
{/* I'm not in love with this approach, but it's the quickest way for now
*
* - Display different content based on loading, empty, results state
*
* This is not ideal because it create a jump in focus. For loading especially,
* We should keep our current results visible while we load the new ones.
*/}
{/* LOADING */}
{isLoading && (
<tr className="no-hover">
<td colSpan={tableMeta.length}>
<StudyListLoadingText />
</td>
</tr>
)}
{!isLoading && hasError && (
<tr className="no-hover">
<td colSpan={tableMeta.length}>
<div className="notFound">
{t('There was an error fetching studies')}
</div>
</td>
</tr>
)}
{/* EMPTY */}
{!isLoading && !studies.length && (
<tr className="no-hover">
<td colSpan={tableMeta.length}>
<div className="notFound">{t('No matching results')}</div>
</td>
</tr>
)}
{!isLoading &&
studies.map((study, index) => (
<TableRow
key={`${study.StudyInstanceUID}-${index}`}
onClick={StudyInstanceUID => handleSelectItem(StudyInstanceUID)}
AccessionNumber={study.AccessionNumber || ''}
modalities={study.modalities}
PatientID={study.PatientID || ''}
PatientName={study.PatientName || ''}
StudyDate={study.StudyDate}
StudyDescription={study.StudyDescription || ''}
StudyInstanceUID={study.StudyInstanceUID}
displaySize={displaySize}
/>
))}
</tbody>
</table>
) : null;
}
StudyList.propTypes = {
isLoading: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
studies: PropTypes.array.isRequired,
onSelectItem: PropTypes.func.isRequired,
// ~~ SORT
sort: PropTypes.shape({
fieldName: PropTypes.string,
direction: PropTypes.oneOf(['desc', 'asc', null]),
}).isRequired,
onSort: PropTypes.func.isRequired,
// ~~ FILTERS
filterValues: PropTypes.shape({
PatientName: PropTypes.string.isRequired,
PatientID: PropTypes.string.isRequired,
AccessionNumber: PropTypes.string.isRequired,
StudyDate: PropTypes.string.isRequired,
modalities: PropTypes.string.isRequired,
StudyDescription: PropTypes.string.isRequired,
patientNameOrId: PropTypes.string.isRequired,
accessionOrModalityOrDescription: PropTypes.string.isRequired,
allFields: PropTypes.string.isRequired,
studyDateTo: PropTypes.any,
studyDateFrom: PropTypes.any,
}).isRequired,
onFilterChange: PropTypes.func.isRequired,
studyListDateFilterNumDays: PropTypes.number,
displaySize: PropTypes.string,
};
StudyList.defaultProps = {};
function TableRow(props) {
const {
AccessionNumber,
isHighlighted,
modalities,
PatientID,
PatientName,
StudyDate,
StudyDescription,
StudyInstanceUID,
onClick: handleClick,
displaySize,
} = props;
const { t } = useTranslation('StudyList');
const largeRowTemplate = (
<tr
onClick={() => handleClick(StudyInstanceUID)}
className={classNames({ active: isHighlighted })}
>
<td className={classNames({ 'empty-value': !PatientName })}>
{PatientName || `(${t('Empty')})`}
</td>
<td>{PatientID}</td>
<td>{AccessionNumber}</td>
<td>{StudyDate}</td>
<td className={classNames({ 'empty-value': !modalities })}>
{modalities || `(${t('Empty')})`}
</td>
<td>{StudyDescription}</td>
</tr>
);
const mediumRowTemplate = (
<tr
onClick={() => handleClick(StudyInstanceUID)}
className={classNames({ active: isHighlighted })}
>
<td className={classNames({ 'empty-value': !PatientName })}>
{PatientName || `(${t('Empty')})`}
<div style={{ color: '#60656f' }}>{PatientID}</div>
</td>
<td>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{/* DESCRIPTION */}
<div
className="hide-xs"
style={{
whiteSpace: 'pre-wrap',
flexGrow: 1,
}}
>
{StudyDescription}
</div>
{/* MODALITY & ACCESSION */}
<div
style={{
display: 'flex',
flexDirection: 'column',
maxWidth: '80px',
width: '80px',
}}
>
<div
className={classNames({
modalities: modalities,
'empty-value': !modalities,
})}
aria-label={modalities}
title={modalities}
>
{modalities || `(${t('Empty')})`}
</div>
<div
style={{
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
aria-label={AccessionNumber}
title={AccessionNumber}
>
{AccessionNumber}
</div>
</div>
</div>
</td>
{/* DATE */}
<td style={{ textAlign: 'center' }}>{StudyDate}</td>
</tr>
);
const smallRowTemplate = (
<tr
onClick={() => handleClick(StudyInstanceUID)}
className={classNames({ active: isHighlighted })}
>
<td style={{ position: 'relative', overflow: 'hidden' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{/* NAME AND ID */}
<div
className={classNames({ 'empty-value': !PatientName })}
style={{ width: '150px', minWidth: '150px' }}
>
<div style={{ fontWeight: 500, paddingTop: '3px' }}>
{PatientName || `(${t('Empty')})`}
</div>
<div style={{ color: '#60656f' }}>{PatientID}</div>
</div>
{/* DESCRIPTION */}
<div
className="hide-xs"
style={{
whiteSpace: 'pre-wrap',
flexGrow: 1,
paddingLeft: '35px',
}}
>
{StudyDescription}
</div>
{/* MODALITY & DATE */}
<div
style={{
display: 'flex',
flexDirection: 'column',
maxWidth: '80px',
width: '80px',
}}
>
<div
className={classNames({
modalities: modalities,
'empty-value': !modalities,
})}
aria-label={modalities}
title={modalities}
>
{modalities || `(${t('Empty')})`}
</div>
<div>{StudyDate}</div>
</div>
</div>
</td>
</tr>
);
const rowTemplate = getContentFromUseMediaValue(
displaySize,
{
large: largeRowTemplate,
medium: mediumRowTemplate,
small: smallRowTemplate,
},
smallRowTemplate
);
return rowTemplate;
}
TableRow.propTypes = {
AccessionNumber: PropTypes.string.isRequired,
isHighlighted: PropTypes.bool,
modalities: PropTypes.string,
PatientID: PropTypes.string.isRequired,
PatientName: PropTypes.string.isRequired,
StudyDate: PropTypes.string.isRequired,
StudyDescription: PropTypes.string.isRequired,
StudyInstanceUID: PropTypes.string.isRequired,
displaySize: PropTypes.string,
};
TableRow.defaultProps = {
isHighlighted: false,
};
export { StudyList };
@@ -1,238 +0,0 @@
@import './../../design/styles/common/form.styl'
@import './../../design/styles/common/global.styl'
$study-list-padding = 8%;
$study-list-toolbar-height = 75px;
$body-cell-height = 40px;
$body-cell-top-bottom-padding = 16px;
$tablePaddingMediumScreen = 5px
$tablePaddingBigScreen = 3%
$tablePaddingLargeScreen = 5%
// TODO: move this to a common mixin files
placeholder-color(c)
&::-webkit-input-placeholder
color: c
&:-moz-placeholder
color: c
&::-moz-placeholder
color: c
&:-ms-input-placeholder
color: c
.study-list-header
.addNewStudy
margin: 0 10px
label
font-weight: 400
cursor: pointer
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
input
width: 0.1px;
height: 0.1px
opacity: 0
overflow: hidden
position: absolute
z-index: -1
color: var(--text-secondary-color)
&:hover
color: var(--hover-color)
&:active
color: var(--active-color)
.study-list-header
display: flex;
justify-content: space-between;
background-color: var(--ui-gray-darker)
height: $study-list-toolbar-height
margin-bottom: 2px
padding: 0 $study-list-padding
line-height: $study-list-toolbar-height
.header
font-size: 22px
font-weight: 300
color: var(--table-text-secondary-color)
line-height: $study-list-toolbar-height
.actions
display: flex;
.study-count
color: var(--large-numbers-color)
font-size: 40px
font-weight: 100
line-height: $study-list-toolbar-height
/*
* Dark gray background with blue border
* Spans width of page to create a distinct area for table filters
*/
.table-head-background
height: 121px
position: absolute
width: 100%
&:before, &:after
content: ''
display: block
left: 0
position: absolute
width: 100%
&:before
background-color: var(--ui-gray-darker)
height: 100%
top: 0
z-index: 1
&:after
background-color: var(--ui-border-color-active)
bottom: -1px
height: 1px
z-index: 2
.study-list-container
width: 100%
padding: 0 $study-list-padding
position: absolute
z-index: 2
table.table
width: 100%;
margin-bottom: 20px;
border-spacing: 0;
border-collapse: collapse;
table-layout: fixed;
color: var(--table-text-primary-color)
font-weight: 300
> tbody tr
padding: 5px
background-color: black
> tbody td
padding: $body-cell-top-bottom-padding 8px;
height: $body-cell-height
word-wrap: break-word;
/* Striped Variant */
&.table--striped > tbody tr:nth-child(even)
background-color: var(--ui-gray-darker)
/* Hover Variant */
&.table--hoverable > tbody tr
&:hover, &:active, &.active
background-color: var(--table-hover-color)
&.no-hover
&:hover, &:active, &.active
background-color: var(--ui-gray-darker)
.empty-value
color: var(--ui-gray-light)
.study-list-container > table.table > tr
height: 20px
.study-list-container > table.table > thead
::-webkit-datetime-edit-year-field:not([aria-valuenow]),
::-webkit-datetime-edit-month-field:not([aria-valuenow]),
::-webkit-datetime-edit-day-field:not([aria-valuenow])
color: transparent
.study-list-container > table.table > thead > tr > th
padding: 0;
border-bottom: 1px solid var(--ui-border-color-active);
width: 100%;
text-align: left;
border-top: 0;
.study-list-container > table.table > thead > tr > th.StudyDate
min-width: 230px
.study-list-container > table.table .modalities
font-weight: 500;
min-height: 20px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
.study-list-container
.filters
label
display: flex;
align-items: center;
cursor: pointer;
width: 100%;
min-width: 95px;
margin: 0 auto;
color: var(--table-text-primary-color);
font-weight: 400;
padding: 20px 8px;
user-select: none;
font-size: 15px;
&:hover
color: var(--active-color);
&.active, &:active
color: var(--active-color);
i
margin: 0 5px;
input
height: 40px
margin: 0 5px 20px 5px
padding: 0 20px
cursor: pointer
border: none
background-color: var(--input-background-color)
color: var(--input-placeholder-color)
font-size: 10pt
font-weight: normal
border-radius: 4px
width: calc(100% - 10px); /* Just use padding? */
transition(all 0.15s ease)
placeholder-color(var(--input-placeholder-color))
&:active, &:hover
background-color: var(--input-background-color)
.loading-text
color: var(--table-text-secondary-color)
text-align: center;
font-size: 30px
.notFound
color: var(--table-text-secondary-color)
font-size: 30px
font-weight: 200
text-align: center
@media only screen and (max-width: 768px)
.study-list-header
padding: 0 16px;
.study-list-container
padding: 0;
.study-list-container > table.table > thead > tr > th
padding: 0 13px;
.study-list-container > table.table > tbody > tr > td
padding: 8px;
.study-list-container .filters label
padding: 8px;
@media only screen and (max-width: 500px)
.hide-xs
display: none;
@@ -1,106 +0,0 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import './PaginationArea.styl';
import { withTranslation } from '../../contextProviders';
class TablePagination extends PureComponent {
static defaultProps = {
pageOptions: [5, 10, 25, 50, 100],
rowsPerPage: 25,
currentPage: 0,
};
static propTypes = {
/* Values to show in "Rows per page" select dropdown */
pageOptions: PropTypes.array,
rowsPerPage: PropTypes.number.isRequired,
currentPage: PropTypes.number.isRequired,
nextPageFunc: PropTypes.func,
prevPageFunc: PropTypes.func,
onRowsPerPageChange: PropTypes.func,
recordCount: PropTypes.number.isRequired,
};
nextPage = () => {
this.props.nextPageFunc(this.props.currentPage);
};
prevPage = () => {
this.props.prevPageFunc(this.props.currentPage);
};
onRowsPerPageChange = event => {
this.props.onRowsPerPageChange(parseInt(event.target.value));
};
renderPaginationButtons() {
return (
<div className="col-xs-8 col-sm-9 col-md-9">
<div className="form-inline form-group page-buttons noselect">
<React.Fragment>
<ul className="pagination-control no-margins">
<li className="page-item prev">
<button
onClick={this.prevPage}
disabled={this.props.currentPage === 0}
className="btn page-link"
>
{this.props.t('Previous')}
</button>
</li>
<li className="page-item next">
<button
onClick={this.nextPage}
disabled={
this.props.recordCount === 0 ||
this.props.rowsPerPage > this.props.recordCount
}
className="btn page-link"
>
{this.props.t('Next')}
</button>
</li>
</ul>
</React.Fragment>
</div>
</div>
);
}
renderRowsPerPageDropdown() {
return (
<div className="form-inline form-group rows-per-page">
<span>{this.props.t('Show')}</span>
<select
onChange={this.onRowsPerPageChange}
defaultValue={this.props.rowsPerPage}
>
{this.props.pageOptions.map(pageNumber => {
return (
<option key={pageNumber} value={pageNumber}>
{pageNumber}
</option>
);
})}
</select>
<span>{this.props.t('RowsPerPage')}</span>
</div>
);
}
render() {
return (
<div className="pagination-area">
<div className="rows-dropdown">{this.renderRowsPerPageDropdown()}</div>
<div className="pagination-buttons">
<div className="form-inline form-group page-number pull-right">
{this.renderPaginationButtons()}
</div>
</div>
</div>
);
}
}
const connectedComponent = withTranslation('Common')(TablePagination);
export { connectedComponent as TablePagination };
@@ -1,72 +0,0 @@
import moment from 'moment';
export default function(searchData) {
this.setState({ searchData });
const filter = (key, searchData, study) => {
if (key === 'studyDateFrom' && searchData[key] && study['StudyDate']) {
const StudyDate = moment(study['StudyDate'], 'YYYYMMDD');
return StudyDate.isBetween(
searchData['studyDateFrom'],
searchData['studyDateTo'],
'days',
'[]'
);
} else if (searchData[key] && !study[key].includes(searchData[key])) {
return false;
} else {
return true;
}
};
const { field, order } = searchData.sortData;
// just a example of local filtering
let filteredStudies = this.defaultStudies
.filter(function(study) {
const all = [
'PatientName',
'PatientID',
'AccessionNumber',
'modalities',
'StudyDescription',
'studyDateFrom',
].every(key => {
return filter(key, searchData, study);
});
return all;
})
.sort(function(a, b) {
if (order === 'desc') {
if (a[field] < b[field]) {
return -1;
}
if (a[field] > b[field]) {
return 1;
}
return 0;
} else {
if (a[field] > b[field]) {
return -1;
}
if (a[field] < b[field]) {
return 1;
}
return 0;
}
});
// User can notice the loading icon
return new Promise(resolve => {
setTimeout(() => {
const first = searchData.currentPage * searchData.rowsPerPage;
let last =
searchData.currentPage * searchData.rowsPerPage +
searchData.rowsPerPage;
last = last >= filteredStudies.length ? filteredStudies.length : last;
this.setState({ studies: filteredStudies.slice(first, last) });
resolve();
}, 500);
});
}
@@ -1,87 +0,0 @@
import moment from 'moment';
const studies = [
{
StudyInstanceUID: '11111.111111.111111.11111',
PatientName: 'John Doe',
PatientID: '1',
AccessionNumber: '1234567',
StudyDate: '19930201',
modalities: 'MR',
StudyDescription: 'BRAIN',
},
{
StudyInstanceUID: '2222.222222.22222.22222',
PatientName: 'José Silva',
PatientID: '2',
AccessionNumber: '7654321',
StudyDate: moment().format('YYYYMMDD'),
modalities: 'CT',
StudyDescription: 'PET CT STANDARD',
},
{
StudyInstanceUID: '3333.333333.33333.33333',
PatientName: 'Antônio Jefferson',
PatientID: '3',
AccessionNumber: '732311',
StudyDate: moment()
.subtract(14, 'days')
.format('YYYYMMDD'),
modalities: 'US',
StudyDescription: '0',
},
{
StudyInstanceUID: '444444.44444.44444.4444',
PatientName: 'Antonio da Silva',
PatientID: '4',
AccessionNumber: '732311',
StudyDate: moment()
.subtract(1, 'months')
.format('YYYYMMDD'),
modalities: 'US',
StudyDescription: '0',
},
{
StudyInstanceUID: '55555.55555.55555.55555',
PatientName: 'Bezerra Souza',
PatientID: '5',
AccessionNumber: '5134543',
StudyDate: moment()
.subtract(6, 'days')
.format('YYYYMMDD'),
modalities: 'US',
StudyDescription: '0',
},
{
StudyInstanceUID: '66666.66666.66666.6666',
PatientName: 'Geraldo Roger',
PatientID: '6',
AccessionNumber: '5315135',
StudyDate: moment()
.subtract(7, 'days')
.format('YYYYMMDD'),
modalities: 'US',
StudyDescription: 'US',
},
{
StudyInstanceUID: '77777.77777.77777.77777',
PatientName: '',
PatientID: '7',
AccessionNumber: '5315136',
StudyDate: moment()
.subtract(5, 'days')
.format('YYYYMMDD'),
modalities: 'US',
StudyDescription: 'US',
},
].sort(function(a, b) {
if (a.PatientName < b.PatientName) {
return -1;
}
if (a.PatientName > b.PatientName) {
return 1;
}
return 0;
});
export default studies;
@@ -1,53 +0,0 @@
---
name: Study List
menu: Components
route: /components/study-list
---
import { Playground, Props } from 'docz'
import { State } from 'react-powerplug'
import { StudyList } from './../index.js'
// Data
import defaultStudies from './studies.js'
import onSearch from './onSearch.js'
import moment from 'moment'
# Study List
## Basic usage
<Playground>
<State initial={{
searchData: {},
studies: defaultStudies.filter(study => {
const StudyDate = moment(study['StudyDate'], 'YYYYMMDD');
const startDate = moment().subtract(5, 'days');
const endDate = moment();
return StudyDate.isBetween(startDate, endDate, 'days', '[]');
}).slice(0, 5)
}}>
{({ state, setState }) => (
<div className="row" style={{ backgroundColor: '#000', height: '600px' }}>
<div className="col-xs-12" style={{ padding: 0 }}>
<StudyList
studies={state.studies}
pageOptions={[1, 2, 3, 5, 10, 15, 20, 25, 50, 100]}
studyListFunctionsEnabled={true}
onImport={(e) => alert('Import study mock ' + e)}
onSelectItem={(StudyInstanceUID) => { alert(StudyInstanceUID + ' has selected! Now you can open your study.'); }}
rowsPerPage={5}
defaultSort={{ field: 'PatientName', order: 'desc', }}
studyListDateFilterNumDays={7}
onSearch={onSearch.bind({ setState, defaultStudies })}
/>
</div>
</div>
)}
</State>
</Playground>
## API
<Props of={StudyList} />
@@ -1,44 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './TableList.styl';
export class TableList extends Component {
static propTypes = {
customHeader: PropTypes.node,
defaultItems: PropTypes.object,
children: PropTypes.node.isRequired,
headerTitle: PropTypes.string,
headless: PropTypes.bool,
};
static defaultProps = {
headless: false,
};
render() {
return (
<div className="tableList">
{!this.props.headless && (
<div className="tableListHeader" onClick={this.onHeaderClick}>
{this.getHeader()}
</div>
)}
<div className="tableListContent">{this.props.children}</div>
</div>
);
}
getHeader = () => {
if (this.props.customHeader) {
return this.props.customHeader;
} else {
return (
<React.Fragment>
<div className="tableListHeaderTitle">{this.props.headerTitle}</div>
<div className="numberOfItems">{this.props.children.length}</div>
</React.Fragment>
);
}
};
}
@@ -1,53 +0,0 @@
import './TableListItem.styl';
import { Component } from 'react';
import { Icon } from './../../elements/Icon';
import PropTypes from 'prop-types';
import React from 'react';
export class TableListItem extends Component {
static propTypes = {
children: PropTypes.node,
itemClass: PropTypes.string,
itemIndex: PropTypes.number,
itemMeta: PropTypes.node,
itemMetaClass: PropTypes.string,
itemKey: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
onItemClick: PropTypes.func.isRequired,
};
static defaultProps = {
itemMeta: null,
itemMetaClass: ''
};
render() {
return (
<div
className={`tableListItem ${this.props.itemClass}`}
onClick={this.onItemClick}
>
<div className={`itemIndex ${this.props.itemMetaClass}`}>
{this.props.itemIndex}
{this.props.itemMeta}
<span className="warning-icon">
<Icon name="exclamation-triangle" />
</span>
</div>
<div className="itemContent">{this.props.children}</div>
</div>
);
}
onItemClick = event => {
if (this.props.onItemClick) {
event.preventDefault();
event.stopPropagation();
this.props.onItemClick(event, this.props.itemKey);
}
};
}