Merge in feat/ui-v2-rebase-services

This commit is contained in:
James A. Petts committed 2020-06-11 18:26:38 +02:00
commit c5e6e97980
66 files changed
+3390 -3355

No files matched your search

-8
View File
@@ -40,11 +40,3 @@
.invisible-scrollbar::-webkit-scrollbar-thumb:window-inactive {
@apply hidden;
}
.showExcludeButtonOnHover .excludeButton {
display: none;
}
.showExcludeButtonOnHover:hover .excludeButton {
display: block;
}
@@ -1,65 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MeasurementTable } from '@ohif/ui';
const MeasurementsPanel = ({
descriptionData,
measurementTableData,
actionButtons,
}) => {
const { date, modality, description } = descriptionData;
return (
<>
<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">{date}</span>
<span className="px-1 text-black bg-common-bright text-base rounded-sm font-bold">
{modality}
</span>
</div>
<div className="leading-none">
<span className="text-base text-primary-light">{description}</span>
</div>
</div>
<MeasurementTable
data={measurementTableData.data}
title={measurementTableData.title}
amount={measurementTableData.amount}
onClick={measurementTableData.onClick}
onEdit={measurementTableData.onEdit}
/>
</div>
<div className="p-4 flex justify-center">{actionButtons}</div>
</>
);
};
MeasurementsPanel.defaultProps = {
actionButtons: null,
};
MeasurementsPanel.propTypes = {
descriptionData: PropTypes.shape({
date: PropTypes.string,
modality: PropTypes.string,
description: PropTypes.string,
}).isRequired,
measurementTableData: PropTypes.shape({
title: PropTypes.string,
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
data: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.string,
displayText: PropTypes.string,
isActive: PropTypes.bool,
})
),
onClick: PropTypes.func,
onEdit: PropTypes.func,
}).isRequired,
actionButtons: PropTypes.node,
};
export default MeasurementsPanel;
@@ -1,101 +0,0 @@
---
name: Measurements Panel
menu: Data Display
route: components/measurementsPanel
---
import { useState } from 'react';
import { Playground, Props } from 'docz';
import {
SidePanel,
MeasurementsPanel,
ButtonGroup,
Button,
IconButton,
Icon,
} from '@ohif/ui';
# Measurements Panel
## Import
```javascript
import { MeasurementsPanel } from '@ohif/ui';
```
## Basic usage
<Playground>
{() => {
const [activeMeasurementItem, setActiveMeasurementItem] = useState(null);
const descriptionData = {
date: '07-Sep-2010',
modality: 'CT',
description: 'CHEST/ABD/PELVIS W CONTRAST',
};
const measurementTableData = {
title: 'Measurements',
amount: 10,
data: new Array(10).fill({}).map((el, i) => ({
id: i + 1,
label: 'Label short description',
displayText: '24.0 x 24.0 mm (S:4, I:22)',
isActive: activeMeasurementItem === i + 1,
})),
onClick: (id) => setActiveMeasurementItem((s) => (s === id ? null : id)),
onEdit: (id) => alert(`Edit: ${id}`),
};
return (
<div className="flex flex-row flex-no-wrap flex-1 items-stretch overflow-hidden w-full h-screen">
<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={true}
>
<MeasurementsPanel
descriptionData={descriptionData}
measurementTableData={measurementTableData}
actionButtons={
<>
<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>
</>
}
/>
</SidePanel>
</div>
);
}}
</Playground>
## Properties
<Props of={MeasurementsPanel} />
@@ -1,2 +0,0 @@
import MeasurementsPanel from './MeasurementsPanel';
export default MeasurementsPanel;
@@ -1,9 +1,9 @@
import React from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { Icon } from '@ohif/ui';
import { Button, Icon } from '@ohif/ui';
const Notification = ({ type, text, actionButtons }) => {
const Notification = ({ type, message, actions, onSubmit }) => {
const iconsByType = {
error: {
icon: 'info',
@@ -35,12 +35,30 @@ const Notification = ({ type, text, actionButtons }) => {
const { icon, color } = getIconData();
return (
<div className="mx-2 mt-2 p-2 flex flex-col bg-common-bright rounded">
<div className="flex flex-col p-2 mx-2 mt-2 rounded bg-common-bright">
<div className="flex flex-grow">
<Icon name={icon} className={classnames('w-5', color)} />
<span className="text-base text-black ml-2">{text}</span>
<span className="ml-2 text-base text-black">{message}</span>
</div>
<div className="flex justify-end mt-2">
{actions.map((action, index) => {
const isFirst = index === 0;
const isPrimary = action.type === 'primary';
return (
<Button
key={index}
className={classnames({ 'ml-2': !isFirst })}
color={isPrimary ? 'primary' : undefined}
onClick={() => {
onSubmit(action.value);
}}
>
{action.text}
</Button>
);
})}
</div>
<div className="flex justify-end mt-2">{actionButtons}</div>
</div>
);
};
@@ -51,8 +69,15 @@ Notification.defaultProps = {
Notification.propTypes = {
type: PropTypes.string,
text: PropTypes.string.isRequired,
actionButtons: PropTypes.node.isRequired,
message: PropTypes.string.isRequired,
actions: PropTypes.arrayOf(
PropTypes.shape({
text: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
type: PropTypes.oneOf(['primary', 'secondary', 'cancel']).isRequired,
})
).isRequired,
onSubmit: PropTypes.func.isRequired,
};
export default Notification;
@@ -5,7 +5,7 @@ route: components/notification
---
import { Playground, Props } from 'docz';
import { Notification, Button } from '@ohif/ui';
import { Notification } from '@ohif/ui';
# Notification
@@ -22,23 +22,31 @@ import { Notification } from '@ohif/ui';
<Playground>
{() => {
const actionButtons = () => {
return (
<div>
<Button>No</Button>
<Button className="ml-2">No, do not ask again</Button>
<Button className="ml-2" color="primary">
Yes
</Button>
</div>
);
};
return (
<div className="p-4 w-full lg:w-2/3">
<Notification
text="Track all measurement for this series?"
message="Track all measurement for this series?"
type="info"
actionButtons={actionButtons()}
actions={[
{
type: 'cancel',
text: 'No',
value: 0,
},
{
type: 'secondary',
text: 'No, do not ask again',
value: -1,
},
{
type: 'primary',
text: 'Yes',
value: 1,
},
]}
onSubmit={value => {
window.alert(value);
}}
/>
</div>
);
@@ -25,6 +25,7 @@ const StudyBrowser = ({
onClickTab,
onClickStudy,
onClickThumbnail,
onClickUntrack,
}) => {
const [thumbnailActive, setThumbnailActive] = useState(null);
@@ -58,15 +59,16 @@ const StudyBrowser = ({
<ThumbnailList
thumbnails={displaySets}
thumbnailActive={thumbnailActive}
onThumbnailClick={thumbnailId => {
onThumbnailClick={displaySetInstanceUID => {
setThumbnailActive(
thumbnailId === thumbnailActive ? null : thumbnailId
displaySetInstanceUID === thumbnailActive
? null
: displaySetInstanceUID
);
if (onClickThumbnail) {
// TODO: what is thumbnailId? Should pass display set instead
onClickThumbnail(thumbnailId);
}
onClickThumbnail(displaySetInstanceUID);
}}
onClickUntrack={displaySetInstanceUID => {
onClickUntrack(displaySetInstanceUID);
}}
/>
)}
@@ -116,6 +118,7 @@ StudyBrowser.propTypes = {
onClickTab: PropTypes.func.isRequired,
onClickStudy: PropTypes.func,
onClickThumbnail: PropTypes.func,
onClickUntrack: PropTypes.func,
activeTabName: PropTypes.string.isRequired,
expandedStudyInstanceUIDs: PropTypes.arrayOf(PropTypes.string).isRequired,
tabs: PropTypes.arrayOf(
@@ -164,4 +167,13 @@ StudyBrowser.propTypes = {
),
};
const noop = () => {};
StudyBrowser.defaultProps = {
onClickTab: noop,
onClickStudy: noop,
onClickThumbnail: noop,
onClickUntrack: noop,
};
export default StudyBrowser;
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
const StudySummary = ({ date, modality, description }) => {
return (
<div className="p-2">
<div className="leading-none">
<span className="mr-2 text-base text-white">{date}</span>
<span className="px-1 text-base font-bold text-black rounded-sm bg-common-bright">
{modality}
</span>
</div>
<div className="pt-2 text-base leading-none truncate text-primary-light ellipse">
{description}
</div>
</div>
);
};
StudySummary.propTypes = {
date: PropTypes.string.isRequired,
modality: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};
export default StudySummary;
Whitespace-only changes.
@@ -0,0 +1,3 @@
import StudySummary from './StudySummary.jsx';
export default StudySummary;
@@ -3,7 +3,12 @@ import PropTypes from 'prop-types';
import { Thumbnail, ThumbnailNoImage, ThumbnailTracked } from '@ohif/ui';
const ThumbnailList = ({ thumbnails, thumbnailActive, onThumbnailClick }) => {
const ThumbnailList = ({
thumbnails,
thumbnailActive,
onThumbnailClick,
onClickUntrack,
}) => {
return (
<div className="py-3 bg-black">
{thumbnails.map(
@@ -53,6 +58,7 @@ const ThumbnailList = ({ thumbnails, thumbnailActive, onThumbnailClick }) => {
isTracked={isTracked}
isActive={isActive}
onClick={() => onThumbnailClick(displaySetInstanceUID)}
onClickUntrack={() => onClickUntrack(displaySetInstanceUID)}
/>
);
case 'thumbnailNoImage':
@@ -107,6 +113,7 @@ ThumbnailList.propTypes = {
),
thumbnailActive: PropTypes.string,
onThumbnailClick: PropTypes.func,
onClickUntrack: PropTypes.func.isRequired,
};
export default ThumbnailList;
@@ -13,6 +13,7 @@ const ThumbnailTracked = ({
numInstances,
dragData,
onClick,
onClickUntrack,
viewportIdentificator,
isTracked,
isActive,
@@ -22,7 +23,7 @@ const ThumbnailTracked = ({
return (
<div
className={classnames(
'flex flex-row flex-1 px-3 py-2 showExcludeButtonOnHover cursor-pointer outline-none',
'flex flex-row flex-1 px-3 py-2 cursor-pointer outline-none',
className
)}
>
@@ -63,10 +64,9 @@ const ThumbnailTracked = ({
</Tooltip>
</div>
{isTracked && (
<Icon
name="cancel"
className="w-4 text-primary-active excludeButton"
/>
<div onClick={onClickUntrack}>
<Icon name="cancel" className="w-4 text-primary-active" />
</div>
)}
</div>
<Thumbnail
@@ -102,6 +102,7 @@ ThumbnailTracked.propTypes = {
seriesNumber: PropTypes.number.isRequired,
numInstances: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
onClickUntrack: PropTypes.func.isRequired,
viewportIdentificator: PropTypes.string,
isTracked: PropTypes.bool,
isActive: PropTypes.bool.isRequired,
@@ -4,7 +4,7 @@ import { ViewportActionBar, Notification, Button } from '@ohif/ui';
const Viewport = ({ viewportIndex, onSeriesChange, studyData, children }) => {
return (
<div className="flex flex-col h-full relative">
<div className="relative flex flex-col h-full">
<div className="absolute top-0 left-0 w-full">
<ViewportActionBar
onSeriesChange={onSeriesChange}
@@ -13,17 +13,28 @@ const Viewport = ({ viewportIndex, onSeriesChange, studyData, children }) => {
{/* TODO: NOTIFICATION API DEFINITION - OHIF-112 */}
<Notification
text="Track all measurement for this series?"
message="Track all measurement for this series?"
type="info"
actionButtons={
<div>
<Button>No</Button>
<Button className="ml-2">No, do not ask again</Button>
<Button className="ml-2" color="primary">
Yes
</Button>
</div>
}
actions={[
{
type: 'cancel',
text: 'No',
value: 0,
},
{
type: 'secondary',
text: 'No, do not ask again',
value: -1,
},
{
type: 'primary',
text: 'Yes',
value: 1,
},
]}
onSubmit={value => {
window.alert(value);
}}
/>
</div>
@@ -39,6 +39,7 @@ function ViewportPane({
// onInteraction...
// https://reactjs.org/docs/events.html#mouse-events
// https://stackoverflow.com/questions/8378243/catch-scrolling-event-on-overflowhidden-element
onMouseDown={onInteraction}
onClick={onInteraction}
onScroll={onInteraction}
onWheel={onInteraction}
@@ -76,6 +77,6 @@ const noop = () => {};
ViewportPane.defaultProps = {
onInteraction: noop,
}
};
export default ViewportPane;
+4 -4
View File
@@ -12,7 +12,6 @@ import InputLabelWrapper from './InputLabelWrapper';
import InputMultiSelect from './InputMultiSelect';
import InputText from './InputText';
import Label from './Label';
import MeasurementsPanel from './MeasurementsPanel';
import MeasurementTable from './MeasurementTable';
import Modal from './Modal';
import NavBar from './NavBar';
@@ -21,12 +20,13 @@ import Select from './Select';
import SegmentationTable from './SegmentationTable';
import SidePanel from './SidePanel';
import StudyBrowser from './StudyBrowser';
import StudyItem from './StudyItem';
import StudyListExpandedRow from './StudyListExpandedRow';
import StudyListFilter from './StudyListFilter';
import StudyListPagination from './StudyListPagination';
import { StudyListTable, StudyListTableRow } from './StudyListTable';
import StudySummary from './StudySummary';
import Svg from './Svg';
import StudyItem from './StudyItem';
import StudyListFilter from './StudyListFilter';
import Table from './Table';
import TableBody from './TableBody';
import TableCell from './TableCell';
@@ -61,7 +61,6 @@ export {
InputMultiSelect,
InputText,
Label,
MeasurementsPanel,
MeasurementTable,
Modal,
NavBar,
@@ -76,6 +75,7 @@ export {
StudyListPagination,
StudyListTable,
StudyListTableRow,
StudySummary,
Svg,
Table,
TableBody,
@@ -1,9 +1,13 @@
import React from 'react';
import { DndProvider } from "react-dnd";
import PropTypes from 'prop-types';
import { DndProvider } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
import TouchBackend from 'react-dnd-touch-backend';
const isTouchDevice = typeof window !== `undefined` && !!('ontouchstart' in window || navigator.maxTouchPoints);
// TODO: this is false when it should not be :thinking:
const isTouchDevice =
typeof window !== `undefined` &&
!!('ontouchstart' in window || navigator.maxTouchPoints);
/**
* Relevant:
@@ -13,9 +17,11 @@ const isTouchDevice = typeof window !== `undefined` && !!('ontouchstart' in wind
* Docs:
* http://react-dnd.github.io/react-dnd/docs/api/drag-drop-context
*/
export default function DragAndDropProvider({children}) {
const backend = isTouchDevice ? TouchBackend : HTML5Backend;
const opts = isTouchDevice ? { enableMouseEvents: true } : {};
function DragAndDropProvider({ children }) {
const backend = HTML5Backend; // isTouchDevice ? TouchBackend : HTML5Backend;
const opts = {}; // isTouchDevice ? { enableMouseEvents: true } : {};
console.log('using... touch backend?', isTouchDevice);
return (
<DndProvider backend={backend} opts={opts}>
@@ -23,3 +29,9 @@ export default function DragAndDropProvider({children}) {
</DndProvider>
);
}
DragAndDropProvider.propTypes = {
children: PropTypes.any,
};
export default DragAndDropProvider;
@@ -7,10 +7,20 @@ import React, {
} from 'react';
import PropTypes from 'prop-types';
const DEFAULT_OPTIONS = {
content: null,
contentProps: null,
customClassName: null,
const DEFAULT_STATE = {
viewportIndex: null,
message: undefined,
type: 'info', // "error" | "warning" | "info" | "success"
actions: undefined, // array of { type, text, value }
// dismissable?
// blockInteraction (single viewport? allViewports? everything?)
// TODO: Buttons --> type/color? text? value?
onSubmit: () => {
console.log('btn value?');
},
onDismiss: () => {
console.log('dismiss? -1');
},
};
const ViewportDialogContext = createContext(null);
@@ -18,42 +28,26 @@ const { Provider } = ViewportDialogContext;
export const useViewportDialog = () => useContext(ViewportDialogContext);
const ViewportDialogProvider = ({
children,
dialog: Dialog,
service,
viewportIndex,
}) => {
const [options, setOptions] = useState(DEFAULT_OPTIONS);
const show = useCallback((props) => setOptions({ ...options, ...props }), [
options,
]);
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), []);
const ViewportDialogProvider = ({ children, service }) => {
const [viewportDialogState, setViewportDialogState] = useState(DEFAULT_STATE);
const show = useCallback(
params => setViewportDialogState({ ...viewportDialogState, ...params }),
[viewportDialogState]
);
const hide = useCallback(() => setViewportDialogState(DEFAULT_STATE), []);
useEffect(() => {
if (service) {
service.setServiceImplementation({ hide, show, viewportIndex });
service.setServiceImplementation({
hide,
show,
});
}
}, [hide, service, show, viewportIndex]);
const {
content: ViewportDialogContent,
contentProps,
customClassName,
} = options;
}, [hide, service, show]);
return (
<Provider value={{ show, hide }}>
<div className="relative w-full h-full">
{ViewportDialogContent && (
<Dialog className={customClassName}>
<ViewportDialogContent {...contentProps} show={show} hide={hide} />
</Dialog>
)}
{children}
</div>
<Provider value={[viewportDialogState, { show, hide }]}>
{children}
</Provider>
);
};
@@ -64,16 +58,9 @@ ViewportDialogProvider.propTypes = {
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
/** dialog component */
dialog: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func,
]).isRequired,
service: PropTypes.shape({
setServiceImplementation: PropTypes.func,
}),
viewportIndex: PropTypes.number,
};
export default ViewportDialogProvider;
@@ -28,19 +28,26 @@ component across all application.
const ViewportNotification = ({ hide }) => {
return (
<Notification
text="Track all measurement for this series?"
message="Track all measurement for this series?"
type="info"
actionButtons={
<div>
<Button onClick={hide}>No</Button>
<Button onClick={hide} className="ml-2">
No, do not ask again
</Button>
<Button onClick={hide} className="ml-2" color="primary">
Yes
</Button>
</div>
}
actions={[
{
type: 'cancel',
text: 'No',
value: 0,
},
{
type: 'secondary',
text: 'No, do not ask again',
value: -1,
},
{
type: 'primary',
text: 'Yes',
value: 1,
},
]}
onSubmit={value => { window.alert(value); }}
/>
);
};
@@ -88,17 +95,26 @@ component across all application.
const ViewportNotification = ({ hide }) => {
return (
<Notification
text="Track all measurement for this series?"
message="Track all measurement for this series?"
type="info"
actionButtons={
<div>
<Button onClick={hide}>No</Button>
<Button className="ml-2">No, do not ask again</Button>
<Button className="ml-2" color="primary">
Yes
</Button>
</div>
}
actions={[
{
type: 'cancel',
text: 'No',
value: 0,
},
{
type: 'secondary',
text: 'No, do not ask again',
value: -1,
},
{
type: 'primary',
text: 'Yes',
value: 1,
},
]}
onSubmit={value => { window.alert(value); }}
/>
);
};
+18 -7
View File
@@ -10,7 +10,6 @@ import {
NavBar,
SidePanel,
Svg,
MeasurementsPanel,
SegmentationTable,
ButtonGroup,
Button,
@@ -125,10 +124,22 @@ import { tabs } from './studyBrowserMockData';
label: 'Measurements',
name: 'measurements',
content: (
<MeasurementsPanel
descriptionData={descriptionData}
measurementTableData={measurementTableData}
actionButtons={
<>
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
<StudySummary
date={descriptionData.date}
modality={descriptionData.modality}
description={descriptionData.description}
/>
<MeasurementTable
title="Measurements"
amount={measurementTableData.data.length}
data={measurementTableData.data}
onClick={() => {}}
onEdit={id => alert(`Edit: ${id}`)}
/>
</div>
<div className="flex justify-center p-4">
<React.Fragment>
<ButtonGroup onClick={() => alert('Export')}>
<Button
@@ -156,8 +167,8 @@ import { tabs } from './studyBrowserMockData';
Create Report
</Button>
</React.Fragment>
}
/>
</div>
</>
)
}}
/>