OHIF-79: Study Browser side panel (#1614)
* Initial code changes for StudyBrowser * StudyItem component basic strucure * Adding new icon * Updates to get StudyItem working * Small fix on side panel open header * Adding new icons * Thumbnail List * ThumbnailSR WIP * Thumanil WIP * Export new components into ohif/ui * Updates on style * Update ThumbnailSR style * Margin updates * MAke it scrollable * Make it scrollable * Style updates * Fixing tooltip * Tunning the mock data and testing scenarios * Update mock data * make all studies collapsed * small fix on tooltip * Removing empty mdx files * Updating viewer docz page * Style updates * Update button font-size * Fix: Make cancel button only visible on hover * Fixing callouts * Fix eslint
This commit is contained in:
parent
51750c6ede
commit
eda99a0bac
@ -30,6 +30,8 @@ export {
|
|||||||
Select,
|
Select,
|
||||||
SegmentationTable,
|
SegmentationTable,
|
||||||
SidePanel,
|
SidePanel,
|
||||||
|
StudyBrowser,
|
||||||
|
StudyItem,
|
||||||
StudyListExpandedRow,
|
StudyListExpandedRow,
|
||||||
StudyListPagination,
|
StudyListPagination,
|
||||||
StudyListTable,
|
StudyListTable,
|
||||||
@ -40,6 +42,9 @@ export {
|
|||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
ThemeWrapper,
|
ThemeWrapper,
|
||||||
|
Thumbnail,
|
||||||
|
ThumbnailSR,
|
||||||
|
ThumbnailList,
|
||||||
Typography,
|
Typography,
|
||||||
} from './src/components';
|
} from './src/components';
|
||||||
|
|
||||||
|
|||||||
6
platform/ui/src/assets/icons/circled-checkmark.svg
Normal file
6
platform/ui/src/assets/icons/circled-checkmark.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<circle cx="8" cy="8" r="8" fill="currentColor"/>
|
||||||
|
<path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.556 5.333L7.824 11.11c-.168.259-.424.42-.703.444-.278.023-.552-.095-.75-.324L4.444 8.993"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 366 B |
3
platform/ui/src/assets/icons/dotted-circle.svg
Normal file
3
platform/ui/src/assets/icons/dotted-circle.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path fill="none" fill-rule="evenodd" stroke="currentColor" stroke-dasharray="1.5 3.8" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 15c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 295 B |
6
platform/ui/src/assets/icons/tracked.svg
Normal file
6
platform/ui/src/assets/icons/tracked.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<circle cx="8" cy="8" r="8" fill="currentColor"/>
|
||||||
|
<path stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.556 5.333L7.824 11.11c-.168.259-.424.42-.703.444-.278.023-.552-.095-.75-.324L4.444 8.993"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 366 B |
@ -16,25 +16,12 @@
|
|||||||
@apply bg-primary-main;
|
@apply bg-primary-main;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* INVISIBLE SCROLLBAR */
|
|
||||||
.invisible-scrollbar::-webkit-scrollbar {
|
.invisible-scrollbar::-webkit-scrollbar {
|
||||||
@apply hidden;
|
@apply hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.invisible-scrollbar::-webkit-scrollbar-track {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invisible-scrollbar::-webkit-scrollbar-thumb {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invisible-scrollbar::-webkit-scrollbar-thumb:window-inactive {
|
|
||||||
@apply hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TOOLTIP WORKAROUND FOR ARROW UP */
|
/* TOOLTIP WORKAROUND FOR ARROW UP */
|
||||||
.tooltip::before {
|
.tooltip.tooltip-up::before {
|
||||||
@apply bg-primary-dark absolute z-10;
|
@apply bg-primary-dark absolute z-10;
|
||||||
content: '';
|
content: '';
|
||||||
width: 14px;
|
width: 14px;
|
||||||
@ -43,3 +30,34 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip-right {
|
||||||
|
left: calc(100% + 15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip.tooltip-right::before {
|
||||||
|
@apply bg-black absolute z-10;
|
||||||
|
content: '';
|
||||||
|
width: 2px;
|
||||||
|
height: 15px;
|
||||||
|
left: -1px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TOOLTIP WORKAROUND FOR ARROW */
|
||||||
|
.showTooltipOnHover .tooltip {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showTooltipOnHover:hover .tooltip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showExcludeButtonOnHover .excludeButton {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.showExcludeButtonOnHover:hover .excludeButton {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import React from 'react';
|
|||||||
import arrowDown from './../../assets/icons/arrow-down.svg';
|
import arrowDown from './../../assets/icons/arrow-down.svg';
|
||||||
import calendar from './../../assets/icons/calendar.svg';
|
import calendar from './../../assets/icons/calendar.svg';
|
||||||
import cancel from './../../assets/icons/cancel.svg';
|
import cancel from './../../assets/icons/cancel.svg';
|
||||||
|
import dottedCircle from './../../assets/icons/dotted-circle.svg';
|
||||||
|
import circledCheckmark from './../../assets/icons/circled-checkmark.svg';
|
||||||
import chevronDown from './../../assets/icons/chevron-down.svg';
|
import chevronDown from './../../assets/icons/chevron-down.svg';
|
||||||
import chevronLeft from './../../assets/icons/chevron-left.svg';
|
import chevronLeft from './../../assets/icons/chevron-left.svg';
|
||||||
import chevronRight from './../../assets/icons/chevron-right.svg';
|
import chevronRight from './../../assets/icons/chevron-right.svg';
|
||||||
@ -24,6 +26,7 @@ import settings from './../../assets/icons/settings.svg';
|
|||||||
import sorting from './../../assets/icons/sorting.svg';
|
import sorting from './../../assets/icons/sorting.svg';
|
||||||
import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
||||||
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
||||||
|
import tracked from './../../assets/icons/tracked.svg';
|
||||||
|
|
||||||
/** Tools */
|
/** Tools */
|
||||||
import toolZoom from './../../assets/icons/tool-zoom.svg';
|
import toolZoom from './../../assets/icons/tool-zoom.svg';
|
||||||
@ -41,6 +44,8 @@ const ICONS = {
|
|||||||
'arrow-down': arrowDown,
|
'arrow-down': arrowDown,
|
||||||
calendar: calendar,
|
calendar: calendar,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
|
'dotted-circle': dottedCircle,
|
||||||
|
'circled-checkmark': circledCheckmark,
|
||||||
'chevron-down': chevronDown,
|
'chevron-down': chevronDown,
|
||||||
'chevron-left': chevronLeft,
|
'chevron-left': chevronLeft,
|
||||||
'chevron-right': chevronRight,
|
'chevron-right': chevronRight,
|
||||||
@ -61,6 +66,7 @@ const ICONS = {
|
|||||||
'sorting-active-down': sortingActiveDown,
|
'sorting-active-down': sortingActiveDown,
|
||||||
'sorting-active-up': sortingActiveUp,
|
'sorting-active-up': sortingActiveUp,
|
||||||
sorting: sorting,
|
sorting: sorting,
|
||||||
|
tracked: tracked,
|
||||||
|
|
||||||
/** Tools */
|
/** Tools */
|
||||||
'tool-zoom': toolZoom,
|
'tool-zoom': toolZoom,
|
||||||
|
|||||||
@ -77,7 +77,7 @@ const SidePanel = ({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}}
|
}}
|
||||||
className="flex flex-row items-center border-b-2 border-secondary-main h-12 relative"
|
className="flex flex-row items-center border-b-2 border-secondary-light px-3 h-12"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
name={openIconName[side]}
|
name={openIconName[side]}
|
||||||
|
|||||||
87
platform/ui/src/components/StudyBrowser/StudyBrowser.jsx
Normal file
87
platform/ui/src/components/StudyBrowser/StudyBrowser.jsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import { ButtonGroup, Button, StudyItem, ThumbnailList } from '@ohif/ui';
|
||||||
|
|
||||||
|
import { studyWithSR, studySimple } from './mockData';
|
||||||
|
const studyGroupTypes = ['Primary', 'Recent', 'All'];
|
||||||
|
const studyGroup = {
|
||||||
|
Primary: [studySimple],
|
||||||
|
Recent: [studyWithSR, studySimple],
|
||||||
|
All: [studySimple, studyWithSR],
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonClasses = 'text-white text-base border-none bg-black';
|
||||||
|
const activeButtonClasses = 'bg-primary-main';
|
||||||
|
|
||||||
|
const StudyBrowser = () => {
|
||||||
|
const [studyGroupSelected, setStudyGroupSelected] = useState('Recent');
|
||||||
|
const [studyActive, setStudyActive] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className="flex flex-row items-center justify-center border-b-2 w-100 h-16 border-secondary-light p-4 bg-primary-dark">
|
||||||
|
<ButtonGroup
|
||||||
|
variant="outlined"
|
||||||
|
color="inherit"
|
||||||
|
className="border border-secondary-light rounded-md"
|
||||||
|
>
|
||||||
|
{studyGroupTypes.map((studyGroup) => {
|
||||||
|
const isActive = studyGroupSelected === studyGroup;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={studyGroup}
|
||||||
|
className={classnames(
|
||||||
|
buttonClasses,
|
||||||
|
isActive && activeButtonClasses
|
||||||
|
)}
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setStudyGroupSelected(studyGroup);
|
||||||
|
setStudyActive(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{studyGroup}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col flex-1 overflow-auto invisible-scrollbar">
|
||||||
|
{studyGroup[studyGroupSelected].map(
|
||||||
|
({
|
||||||
|
studyInstanceUid,
|
||||||
|
studyDate,
|
||||||
|
studyDescription,
|
||||||
|
instances,
|
||||||
|
modalities,
|
||||||
|
trackedSeries,
|
||||||
|
thumbnails,
|
||||||
|
}) => {
|
||||||
|
const isActive = studyActive === studyInstanceUid;
|
||||||
|
return (
|
||||||
|
<React.Fragment key={studyInstanceUid}>
|
||||||
|
<StudyItem
|
||||||
|
studyDate={studyDate}
|
||||||
|
studyDescription={studyDescription}
|
||||||
|
instances={instances}
|
||||||
|
modalities={modalities}
|
||||||
|
trackedSeries={trackedSeries}
|
||||||
|
isActive={isActive}
|
||||||
|
onClick={() => {
|
||||||
|
setStudyActive(isActive ? null : studyInstanceUid);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{isActive && thumbnails && (
|
||||||
|
<ThumbnailList thumbnails={thumbnails} />
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudyBrowser;
|
||||||
26
platform/ui/src/components/StudyBrowser/StudyBrowser.mdx
Normal file
26
platform/ui/src/components/StudyBrowser/StudyBrowser.mdx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
name: StudyBrowser
|
||||||
|
menu: General
|
||||||
|
route: components/studyBrowser
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Playground, Props } from 'docz';
|
||||||
|
import { StudyBrowser } from '@ohif/ui';
|
||||||
|
|
||||||
|
# StudyBrowser
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { StudyBrowser } from '@ohif/ui';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
<Playground>
|
||||||
|
<StudyBrowser />
|
||||||
|
</Playground>
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
<Props of={StudyBrowser} />
|
||||||
2
platform/ui/src/components/StudyBrowser/index.js
Normal file
2
platform/ui/src/components/StudyBrowser/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import StudyBrowser from './StudyBrowser';
|
||||||
|
export default StudyBrowser;
|
||||||
56
platform/ui/src/components/StudyBrowser/mockData.js
Normal file
56
platform/ui/src/components/StudyBrowser/mockData.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
const studyWithSR = {
|
||||||
|
studyInstanceUid: '1',
|
||||||
|
studyDate: '07-Sept-2010',
|
||||||
|
studyDescription: 'CHEST/ABD/PELVIS W/CONTRAST',
|
||||||
|
instances: 902,
|
||||||
|
modalities: 'CT,SR',
|
||||||
|
trackedSeries: 1,
|
||||||
|
thumbnails: [
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f6asdasd48c-223e-db7f-c4af-b8906641a66e',
|
||||||
|
seriesDescription: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
instanceNumber: 68,
|
||||||
|
viewportIdentificator: 'A',
|
||||||
|
isTracked: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-db7f-c4asdasdaf-b8906641a66e',
|
||||||
|
seriesDescription: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
instanceNumber: 68,
|
||||||
|
viewportIdentificator: 'B',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-dasdasdb7f-c4af-b8906641a66e',
|
||||||
|
seriesDescription: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
instanceNumber: 68,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-db7f-c4afas-b8906asd641a66e',
|
||||||
|
seriesDescription: 'Multiple line description lorem ipsum dolor sit amet',
|
||||||
|
modality: 'SR',
|
||||||
|
seriesDate: '07-Sept-2010',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const studySimple = {
|
||||||
|
studyInstanceUid: '2',
|
||||||
|
studyDate: '07-Sept-2010',
|
||||||
|
studyDescription: 'CHEST/ABD/PELVIS W/CONTRAST',
|
||||||
|
instances: 902,
|
||||||
|
modalities: 'CT',
|
||||||
|
trackedSeries: 0,
|
||||||
|
thumbnails: [
|
||||||
|
{
|
||||||
|
displaySetInstanceUid: 'f69f648c-223e-db7f-c4af-b8906641a66e',
|
||||||
|
seriesDescription: 'Multiple line image series description lorem sit',
|
||||||
|
seriesNumber: 1,
|
||||||
|
instanceNumber: 68,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export { studyWithSR, studySimple };
|
||||||
72
platform/ui/src/components/StudyItem/StudyItem.jsx
Normal file
72
platform/ui/src/components/StudyItem/StudyItem.jsx
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import { Icon } from '@ohif/ui';
|
||||||
|
|
||||||
|
const baseClasses =
|
||||||
|
'first:border-0 border-t border-secondary-light cursor-pointer';
|
||||||
|
|
||||||
|
const StudyItem = ({
|
||||||
|
studyDate,
|
||||||
|
studyDescription,
|
||||||
|
instances,
|
||||||
|
modalities,
|
||||||
|
trackedSeries,
|
||||||
|
isActive,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
isActive ? 'bg-secondary-dark' : 'bg-black hover:bg-secondary-main',
|
||||||
|
baseClasses
|
||||||
|
)}
|
||||||
|
onClick={onClick}
|
||||||
|
onKeyDown={onClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
>
|
||||||
|
<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="flex flex-row items-center text-blue-300 text-base">
|
||||||
|
<Icon name="group-layers" className="text-blue-300 mx-2 w-4" />
|
||||||
|
{instances}
|
||||||
|
</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>
|
||||||
|
</div>
|
||||||
|
{!!trackedSeries && (
|
||||||
|
<div className="flex flex-2">
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'flex flex-row bg-secondary-main text-base text-white py-1 pl-2 pr-4 mt-2 ',
|
||||||
|
isActive
|
||||||
|
? 'flex-1 border-t border-secondary-light justify-center'
|
||||||
|
: 'rounded-sm mx-4 mb-4'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon name="tracked" className="text-primary-light w-4 mr-2" />
|
||||||
|
{trackedSeries} Tracked Series
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StudyItem.propTypes = {
|
||||||
|
studyDate: PropTypes.any,
|
||||||
|
studyDescription: PropTypes.any,
|
||||||
|
modalities: PropTypes.any,
|
||||||
|
instances: PropTypes.any,
|
||||||
|
trackedSeries: PropTypes.any,
|
||||||
|
isActive: PropTypes.any,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StudyItem;
|
||||||
2
platform/ui/src/components/StudyItem/index.js
Normal file
2
platform/ui/src/components/StudyItem/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import StudyItem from './StudyItem';
|
||||||
|
export default StudyItem;
|
||||||
117
platform/ui/src/components/Thumbnail/Thumbnail.jsx
Normal file
117
platform/ui/src/components/Thumbnail/Thumbnail.jsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
import { Icon } from '@ohif/ui';
|
||||||
|
|
||||||
|
const Thumbnail = ({
|
||||||
|
seriesDescription,
|
||||||
|
seriesNumber,
|
||||||
|
instanceNumber,
|
||||||
|
viewportIdentificator,
|
||||||
|
isTracked,
|
||||||
|
isActive,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const trackedIcon = isTracked ? 'circled-checkmark' : 'dotted-circle';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="flex flex-row flex-1 px-4 py-2 showExcludeButtonOnHover cursor-pointer"
|
||||||
|
onClick={onClick}
|
||||||
|
onKeyDown={onClick}
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col flex-2 px-2 items-center">
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'flex flex-col items-center justify-start p-2 mb-2 relative cursor-pointer',
|
||||||
|
isTracked && 'rounded-sm hover:bg-gray-900 showTooltipOnHover'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon name={trackedIcon} className="text-primary-light mb-2 w-5" />
|
||||||
|
<div className="text-white text-xl leading-tight h-5">
|
||||||
|
{viewportIdentificator}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'tooltip tooltip-right absolute bg-black border border-secondary-main text-common-light text-base rounded py-2 px-4 top-0 w-max-content hidden'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row flex-1">
|
||||||
|
<div className="flex flex-col flex-1 pr-4">
|
||||||
|
<span>
|
||||||
|
Series is <span className="text-white">tracked</span>
|
||||||
|
</span>
|
||||||
|
{viewportIdentificator && (
|
||||||
|
<span>
|
||||||
|
in viewport
|
||||||
|
<span className="ml-1 text-white">
|
||||||
|
{viewportIdentificator}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-2 items-center justify-center">
|
||||||
|
<Icon name="info-link" className="text-primary-active" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
className="absolute text-black stroke-secondary-main stroke-2"
|
||||||
|
style={{
|
||||||
|
top: 'calc(50% - 8px)',
|
||||||
|
left: -15,
|
||||||
|
transform: 'rotate(270deg)',
|
||||||
|
}}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path fill="currentColor" d="M24 22h-24l12-20z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isTracked && (
|
||||||
|
<Icon name="cancel" className="text-primary-active excludeButton" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col flex-1 px-4">
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
'flex flex-1 items-center justify-center rounded-md bg-black text-white mb-2',
|
||||||
|
isActive
|
||||||
|
? 'border-2 border-primary-light'
|
||||||
|
: 'border border-secondary-light hover:border-blue-300'
|
||||||
|
)}
|
||||||
|
style={{ minHeight: '120px' }}
|
||||||
|
>
|
||||||
|
{'Thumbnail img'}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row flex-1 text-blue-300 text-base">
|
||||||
|
<div className="mr-4">
|
||||||
|
<span className="text-primary-main font-bold">{'S: '}</span>
|
||||||
|
{seriesNumber}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-centerflex-1">
|
||||||
|
<Icon name="group-layers" className="w-3 mr-2" /> {instanceNumber}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-white text-base">{seriesDescription}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Thumbnail.propTypes = {
|
||||||
|
seriesDescription: PropTypes.string.isRequired,
|
||||||
|
seriesNumber: PropTypes.number.isRequired,
|
||||||
|
instanceNumber: PropTypes.number.isRequired,
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
isTracked: PropTypes.bool,
|
||||||
|
viewportIdentificator: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Thumbnail;
|
||||||
2
platform/ui/src/components/Thumbnail/index.js
Normal file
2
platform/ui/src/components/Thumbnail/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import Thumbnail from './Thumbnail';
|
||||||
|
export default Thumbnail;
|
||||||
73
platform/ui/src/components/ThumbnailList/ThumbnailList.jsx
Normal file
73
platform/ui/src/components/ThumbnailList/ThumbnailList.jsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { Thumbnail, ThumbnailSR } from '@ohif/ui';
|
||||||
|
|
||||||
|
const ThumbnailList = ({ thumbnails }) => {
|
||||||
|
const [thumbnailActive, setThumbnailActive] = useState(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-black py-3">
|
||||||
|
{thumbnails.map(
|
||||||
|
({
|
||||||
|
displaySetInstanceUid,
|
||||||
|
seriesDescription,
|
||||||
|
seriesNumber,
|
||||||
|
instanceNumber,
|
||||||
|
modality,
|
||||||
|
seriesDate,
|
||||||
|
viewportIdentificator,
|
||||||
|
isTracked,
|
||||||
|
}) => {
|
||||||
|
const isSR = modality && modality.toLowerCase() === 'sr';
|
||||||
|
const isActive = thumbnailActive === displaySetInstanceUid;
|
||||||
|
|
||||||
|
if (isSR) {
|
||||||
|
return (
|
||||||
|
<ThumbnailSR
|
||||||
|
key={displaySetInstanceUid}
|
||||||
|
modality={modality}
|
||||||
|
seriesDate={seriesDate}
|
||||||
|
seriesDescription={seriesDescription}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Thumbnail
|
||||||
|
key={displaySetInstanceUid}
|
||||||
|
seriesDescription={seriesDescription}
|
||||||
|
seriesNumber={seriesNumber}
|
||||||
|
instanceNumber={instanceNumber}
|
||||||
|
viewportIdentificator={viewportIdentificator}
|
||||||
|
isTracked={isTracked}
|
||||||
|
isActive={isActive}
|
||||||
|
onClick={() => {
|
||||||
|
setThumbnailActive(
|
||||||
|
thumbnailActive === displaySetInstanceUid
|
||||||
|
? null
|
||||||
|
: displaySetInstanceUid
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ThumbnailList.propTypes = {
|
||||||
|
thumbnails: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
displaySetInstanceUid: PropTypes.string,
|
||||||
|
seriesDescription: PropTypes.string,
|
||||||
|
seriesNumber: PropTypes.number,
|
||||||
|
instanceNumber: PropTypes.number,
|
||||||
|
modality: PropTypes.string,
|
||||||
|
seriesDate: PropTypes.string,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThumbnailList;
|
||||||
2
platform/ui/src/components/ThumbnailList/index.js
Normal file
2
platform/ui/src/components/ThumbnailList/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import ThumbnailList from './ThumbnailList';
|
||||||
|
export default ThumbnailList;
|
||||||
31
platform/ui/src/components/ThumbnailSR/ThumbnailSR.jsx
Normal file
31
platform/ui/src/components/ThumbnailSR/ThumbnailSR.jsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { Icon } from '@ohif/ui';
|
||||||
|
|
||||||
|
const ThumbnailSR = ({ seriesDescription, seriesDate }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row flex-1 px-4 py-3">
|
||||||
|
<div className="flex flex-2 pl-4 pr-6">
|
||||||
|
<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">
|
||||||
|
SR
|
||||||
|
</div>
|
||||||
|
<span className="text-blue-300 text-base">{seriesDate}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-white text-base">{seriesDescription}</div>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ThumbnailSR.propTypes = {
|
||||||
|
seriesDescription: PropTypes.string,
|
||||||
|
seriesDate: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThumbnailSR;
|
||||||
2
platform/ui/src/components/ThumbnailSR/index.js
Normal file
2
platform/ui/src/components/ThumbnailSR/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import ThumbnailSR from './ThumbnailSR';
|
||||||
|
export default ThumbnailSR;
|
||||||
@ -16,16 +16,21 @@ import NavBar from './NavBar';
|
|||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import SegmentationTable from './SegmentationTable';
|
import SegmentationTable from './SegmentationTable';
|
||||||
import SidePanel from './SidePanel';
|
import SidePanel from './SidePanel';
|
||||||
import Svg from './Svg';
|
import StudyBrowser from './StudyBrowser';
|
||||||
import StudyListExpandedRow from './StudyListExpandedRow';
|
import StudyListExpandedRow from './StudyListExpandedRow';
|
||||||
import StudyListPagination from './StudyListPagination';
|
import StudyListPagination from './StudyListPagination';
|
||||||
import StudyListTable from './StudyListTable';
|
import StudyListTable from './StudyListTable';
|
||||||
|
import Svg from './Svg';
|
||||||
|
import StudyItem from './StudyItem';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import TableBody from './TableBody';
|
import TableBody from './TableBody';
|
||||||
import TableCell from './TableCell';
|
import TableCell from './TableCell';
|
||||||
import TableHead from './TableHead';
|
import TableHead from './TableHead';
|
||||||
import TableRow from './TableRow';
|
import TableRow from './TableRow';
|
||||||
import ThemeWrapper from './ThemeWrapper/';
|
import ThemeWrapper from './ThemeWrapper/';
|
||||||
|
import Thumbnail from './Thumbnail';
|
||||||
|
import ThumbnailSR from './ThumbnailSR';
|
||||||
|
import ThumbnailList from './ThumbnailList';
|
||||||
import Typography from './Typography';
|
import Typography from './Typography';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -47,15 +52,20 @@ export {
|
|||||||
Select,
|
Select,
|
||||||
SegmentationTable,
|
SegmentationTable,
|
||||||
SidePanel,
|
SidePanel,
|
||||||
Svg,
|
StudyBrowser,
|
||||||
|
StudyItem,
|
||||||
StudyListExpandedRow,
|
StudyListExpandedRow,
|
||||||
StudyListPagination,
|
StudyListPagination,
|
||||||
StudyListTable,
|
StudyListTable,
|
||||||
|
Svg,
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
TableCell,
|
TableCell,
|
||||||
TableHead,
|
TableHead,
|
||||||
TableRow,
|
TableRow,
|
||||||
ThemeWrapper,
|
ThemeWrapper,
|
||||||
|
Thumbnail,
|
||||||
|
ThumbnailSR,
|
||||||
|
ThumbnailList,
|
||||||
Typography,
|
Typography,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -86,7 +86,7 @@ components.
|
|||||||
{shouldShowTooltip && (
|
{shouldShowTooltip && (
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'tooltip absolute bg-primary-dark border border-secondary-main text-white text-base rounded py-1 px-4 inset-x-auto top-full mt-2 w-max-content'
|
'tooltip tooltip-up absolute bg-primary-dark border border-secondary-main text-white text-base rounded py-1 px-4 inset-x-auto top-full mt-2 w-max-content'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{tool.label}
|
{tool.label}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { NavBar, SidePanel, Svg } from '@ohif/ui';
|
import { NavBar, SidePanel, Svg, StudyBrowser } from '@ohif/ui';
|
||||||
|
|
||||||
const Viewer = () => {
|
const Viewer = () => {
|
||||||
return (
|
return (
|
||||||
@ -18,11 +18,9 @@ const Viewer = () => {
|
|||||||
iconName="group-layers"
|
iconName="group-layers"
|
||||||
iconLabel="Studies"
|
iconLabel="Studies"
|
||||||
componentLabel="Studies"
|
componentLabel="Studies"
|
||||||
defaultIsOpen={false}
|
defaultIsOpen={true}
|
||||||
>
|
>
|
||||||
<div className="flex justify-center text-white p-2">
|
<StudyBrowser />
|
||||||
panel placeholder
|
|
||||||
</div>
|
|
||||||
</SidePanel>
|
</SidePanel>
|
||||||
<div className="flex flex-1 h-100 overflow-hidden bg-primary-main items-center justify-center text-white">
|
<div className="flex flex-1 h-100 overflow-hidden bg-primary-main items-center justify-center text-white">
|
||||||
CONTENT
|
CONTENT
|
||||||
|
|||||||
@ -5,110 +5,32 @@ route: examples/viewer
|
|||||||
---
|
---
|
||||||
|
|
||||||
import { Playground, Props } from 'docz';
|
import { Playground, Props } from 'docz';
|
||||||
import {
|
import { NavBar, SidePanel, Svg, StudyBrowser } from '@ohif/ui';
|
||||||
NavBar,
|
|
||||||
SidePanel,
|
|
||||||
Svg,
|
|
||||||
MeasurementTable,
|
|
||||||
SegmentationTable,
|
|
||||||
ButtonGroup,
|
|
||||||
Button,
|
|
||||||
Icon,
|
|
||||||
IconButton,
|
|
||||||
} from '@ohif/ui';
|
|
||||||
|
|
||||||
# Viewer
|
# Viewer
|
||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
{() => {
|
<div>
|
||||||
const measurementTableData = {
|
<NavBar className="justify-start">
|
||||||
title: 'Measurements',
|
<div className="m-3">
|
||||||
amount: 10,
|
<Svg name="logo-ohif" />
|
||||||
data: new Array(10).fill(''),
|
</div>
|
||||||
};
|
</NavBar>
|
||||||
const segmentationTableData = {
|
<div
|
||||||
title: 'Segments',
|
className="flex flex-row flex-no-wrap flex-1 items-stretch overflow-hidden w-full"
|
||||||
amount: 12,
|
style={{ height: 'calc(100vh - 57px' }}
|
||||||
data: new Array(12).fill(''),
|
>
|
||||||
};
|
<SidePanel
|
||||||
return (
|
side="left"
|
||||||
<div>
|
iconName="group-layers"
|
||||||
<NavBar className="justify-start">
|
iconLabel="Studies"
|
||||||
<div className="m-3">
|
componentLabel="Studies"
|
||||||
<Svg name="logo-ohif" />
|
defaultIsOpen={false}
|
||||||
</div>
|
>
|
||||||
</NavBar>
|
<StudyBrowser />
|
||||||
<div
|
</SidePanel>
|
||||||
className="flex flex-row flex-no-wrap flex-1 items-stretch overflow-hidden w-full"
|
<div className="flex flex-1 h-100 overflow-hidden bg-primary-main items-center justify-center text-white">
|
||||||
style={{ height: 'calc(100vh - 57px' }}
|
CONTENT
|
||||||
>
|
|
||||||
<SidePanel
|
|
||||||
side="left"
|
|
||||||
iconName="group-layers"
|
|
||||||
iconLabel="Studies"
|
|
||||||
componentLabel="Studies"
|
|
||||||
defaultIsOpen={false}
|
|
||||||
>
|
|
||||||
<div className="flex justify-center text-white p-2">
|
|
||||||
panel placeholder
|
|
||||||
</div>
|
|
||||||
</SidePanel>
|
|
||||||
<div className="flex flex-1 h-100 overflow-hidden bg-primary-main items-center justify-center text-white">
|
|
||||||
CONTENT
|
|
||||||
</div>
|
|
||||||
<SidePanel
|
|
||||||
side="right"
|
|
||||||
iconName="list-bullets"
|
|
||||||
iconLabel="Measure"
|
|
||||||
componentLabel="Measurements"
|
|
||||||
defaultIsOpen={false}
|
|
||||||
>
|
|
||||||
<div className="overflow-y-auto overflow-x-hidden invisible-scrollbar">
|
|
||||||
<div className="p-2">
|
|
||||||
<div className="leading-none">
|
|
||||||
<span className="text-white text-base mr-2">07-Sep-2010</span>
|
|
||||||
<span className="px-1 text-black bg-common-bright text-base rounded-sm font-bold">
|
|
||||||
CT
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="leading-none">
|
|
||||||
<span className="text-base text-primary-light">
|
|
||||||
CHEST/ABD/PELVIS W CONTRAST
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MeasurementTable {...measurementTableData} />
|
|
||||||
<SegmentationTable {...segmentationTableData} />
|
|
||||||
</div>
|
|
||||||
<div className="p-4 flex justify-center">
|
|
||||||
<ButtonGroup onClick={() => alert('Export')}>
|
|
||||||
<Button
|
|
||||||
className="text-white border-primary-main bg-black text-base py-2 px-2"
|
|
||||||
size="initial"
|
|
||||||
color="inherit"
|
|
||||||
>
|
|
||||||
Export
|
|
||||||
</Button>
|
|
||||||
<IconButton
|
|
||||||
className="bg-black border-primary-main px-2 text-white px-2"
|
|
||||||
color="inherit"
|
|
||||||
size="initial"
|
|
||||||
>
|
|
||||||
<Icon name="arrow-down" />
|
|
||||||
</IconButton>
|
|
||||||
</ButtonGroup>
|
|
||||||
<Button
|
|
||||||
className="text-white border border-primary-main bg-black text-base py-2 px-2 ml-2"
|
|
||||||
variant="outlined"
|
|
||||||
size="initial"
|
|
||||||
color="inherit"
|
|
||||||
onClick={() => alert('Create Report')}
|
|
||||||
>
|
|
||||||
Create Report
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</SidePanel>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user