Merge in feat/ui-v2-rebase-services
This commit is contained in:
commit
c5e6e97980
66 files changed
+3390
-3355
No files matched your search
@@ -47,6 +47,7 @@ const MEASUREMENT_SCHEMA_KEYS = [
|
||||
const EVENTS = {
|
||||
MEASUREMENT_UPDATED: 'event::measurement_updated',
|
||||
MEASUREMENT_ADDED: 'event::measurement_added',
|
||||
MEASUREMENT_REMOVED: 'event::measurement_removed',
|
||||
};
|
||||
|
||||
const VALUE_TYPES = {
|
||||
@@ -136,6 +137,9 @@ class MeasurementService {
|
||||
source.addOrUpdate = (definition, measurement) => {
|
||||
return this.addOrUpdate(source, definition, measurement);
|
||||
};
|
||||
source.remove = id => {
|
||||
return this.remove(source, id);
|
||||
};
|
||||
source.getAnnotation = (definition, measurementId) => {
|
||||
return this.getAnnotation(source, definition, measurementId);
|
||||
};
|
||||
@@ -338,6 +342,16 @@ class MeasurementService {
|
||||
return newMeasurement.id;
|
||||
}
|
||||
|
||||
remove(source, id) {
|
||||
if (!id || !this.measurements[id]) {
|
||||
log.warn(`No id provided, or unable to find measurement by id.`);
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.measurements[id];
|
||||
this._broadcastChange(this.EVENTS.MEASUREMENT_REMOVED, source, id);
|
||||
}
|
||||
|
||||
_getMappingByMeasurementSource(measurementId, definition) {
|
||||
const measurement = this.getMeasurement(measurementId);
|
||||
if (this._isValidSource(measurement.source)) {
|
||||
|
||||
@@ -17,7 +17,8 @@ const publicAPI = {
|
||||
};
|
||||
|
||||
const serviceImplementation = {
|
||||
_viewports: [],
|
||||
_hide: () => console.warn('hide() NOT IMPLEMENTED'),
|
||||
_show: () => console.warn('show() NOT IMPLEMENTED'),
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -25,39 +26,21 @@ const serviceImplementation = {
|
||||
*
|
||||
* @param {ViewportDialogProps} props { content, contentProps, viewportIndex }
|
||||
*/
|
||||
function _show({ content = null, contentProps = null, viewportIndex }) {
|
||||
const viewportIndexImplementation =
|
||||
(viewportIndex !== undefined &&
|
||||
serviceImplementation._viewports[viewportIndex]) ||
|
||||
{};
|
||||
|
||||
if (!viewportIndexImplementation._show) {
|
||||
console.warn('show() NOT IMPLEMENTED');
|
||||
return;
|
||||
}
|
||||
|
||||
return viewportIndexImplementation._show({
|
||||
content,
|
||||
contentProps,
|
||||
function _show({ viewportIndex, type, message, actions, onSubmit }) {
|
||||
return serviceImplementation._show({
|
||||
viewportIndex,
|
||||
type,
|
||||
message,
|
||||
actions,
|
||||
onSubmit,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides/dismisses the viewport dialog, if currently shown
|
||||
*
|
||||
* @param {*} { viewportIndex }
|
||||
*/
|
||||
function _hide({ viewportIndex }) {
|
||||
const viewportIndexImplementation =
|
||||
(viewportIndex && serviceImplementation._viewports[viewportIndex]) || {};
|
||||
|
||||
if (!viewportIndexImplementation._hide) {
|
||||
console.warn('hide() NOT IMPLEMENTED');
|
||||
return;
|
||||
}
|
||||
|
||||
return viewportIndexImplementation._hide();
|
||||
function _hide() {
|
||||
return serviceImplementation._hide();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,22 +55,12 @@ function _hide({ viewportIndex }) {
|
||||
function setServiceImplementation({
|
||||
hide: hideImplementation,
|
||||
show: showImplementation,
|
||||
viewportIndex,
|
||||
}) {
|
||||
if (viewportIndex !== undefined) {
|
||||
const newImplementations = {};
|
||||
if (hideImplementation) {
|
||||
newImplementations._hide = hideImplementation;
|
||||
}
|
||||
if (showImplementation) {
|
||||
newImplementations._show = showImplementation;
|
||||
}
|
||||
|
||||
serviceImplementation._viewports[viewportIndex] = Object.assign(
|
||||
{},
|
||||
serviceImplementation._viewports[viewportIndex],
|
||||
newImplementations
|
||||
);
|
||||
if (hideImplementation) {
|
||||
serviceImplementation._hide = hideImplementation;
|
||||
}
|
||||
if (showImplementation) {
|
||||
serviceImplementation._show = showImplementation;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ export {
|
||||
InputMultiSelect,
|
||||
InputText,
|
||||
Label,
|
||||
MeasurementsPanel,
|
||||
MeasurementTable,
|
||||
Modal,
|
||||
NavBar,
|
||||
@@ -56,6 +55,7 @@ export {
|
||||
StudyListPagination,
|
||||
StudyListTable,
|
||||
StudyListTableRow,
|
||||
StudySummary,
|
||||
Svg,
|
||||
Table,
|
||||
TableBody,
|
||||
|
||||
@@ -28,12 +28,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"docz": "^2.2.0",
|
||||
"gatsby": "2.19.24",
|
||||
"gatsby-plugin-postcss": "^2.1.20",
|
||||
"gatsby-plugin-react-svg": "^3.0.0",
|
||||
"gatsby-plugin-sass": "^2.1.28",
|
||||
"gatsby-theme-docz": "^2.2.0",
|
||||
"docz": "^2.3.0",
|
||||
"gatsby": "2.22.19",
|
||||
"gatsby-plugin-postcss": "2.3.3",
|
||||
"gatsby-plugin-react-svg": "3.0.0",
|
||||
"gatsby-plugin-sass": "2.3.3",
|
||||
"gatsby-theme-docz": "2.3.1",
|
||||
"moment": "^2.24.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"prop-types": "^15.7.2",
|
||||
|
||||
@@ -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;
|
||||
@@ -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); }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -19,14 +19,12 @@
|
||||
"scripts": {
|
||||
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress",
|
||||
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
|
||||
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
|
||||
"build:viewer:package": "yarn run build:package",
|
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"dev:viewer": "yarn run dev",
|
||||
"dev:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
|
||||
"start": "yarn run dev",
|
||||
"test:e2e": "cypress open",
|
||||
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",
|
||||
|
||||
@@ -6,8 +6,10 @@ import {
|
||||
DialogProvider,
|
||||
Modal,
|
||||
ModalProvider,
|
||||
Notification,
|
||||
SnackbarProvider,
|
||||
ThemeWrapper,
|
||||
ViewportDialogProvider,
|
||||
ViewportGridProvider,
|
||||
} from '@ohif/ui';
|
||||
// Viewer Project
|
||||
@@ -49,11 +51,11 @@ function App({ config, defaultExtensions }) {
|
||||
extensionManager,
|
||||
servicesManager
|
||||
);
|
||||
|
||||
const {
|
||||
UIDialogService,
|
||||
UIModalService,
|
||||
UINotificationService,
|
||||
UIViewportDialogService,
|
||||
} = servicesManager.services;
|
||||
|
||||
// A UI Service may need to use the ViewportGrid context
|
||||
@@ -63,13 +65,33 @@ function App({ config, defaultExtensions }) {
|
||||
switch (action.type) {
|
||||
case 'SET_ACTIVE_VIEWPORT_INDEX':
|
||||
return { ...state, ...{ activeViewportIndex: action.payload } };
|
||||
case 'SET_DISPLAYSET_FOR_VIEWPORT':
|
||||
case 'SET_DISPLAYSET_FOR_VIEWPORT': {
|
||||
const { viewportIndex, displaySetInstanceUID } = action.payload;
|
||||
const viewports = state.viewports.slice();
|
||||
|
||||
viewports[viewportIndex] = { displaySetInstanceUID };
|
||||
|
||||
return { ...state, ...{ viewports } };
|
||||
}
|
||||
case 'SET_LAYOUT': {
|
||||
const { numCols, numRows } = action.payload;
|
||||
const numPanes = numCols * numRows;
|
||||
const viewports = state.viewports.slice();
|
||||
const activeViewportIndex =
|
||||
state.activeViewportIndex >= numPanes ? 0 : state.activeViewportIndex;
|
||||
|
||||
while (viewports.length < numPanes) {
|
||||
viewports.push({});
|
||||
}
|
||||
while (viewports.length > numPanes) {
|
||||
viewports.pop();
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
...{ activeViewportIndex, numCols, numRows, viewports },
|
||||
};
|
||||
}
|
||||
default:
|
||||
return action.payload;
|
||||
}
|
||||
@@ -88,13 +110,15 @@ function App({ config, defaultExtensions }) {
|
||||
}}
|
||||
reducer={viewportGridReducer}
|
||||
>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={Modal} service={UIModalService}>
|
||||
{appRoutes}
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
<ViewportDialogProvider service={UIViewportDialogService}>
|
||||
<SnackbarProvider service={UINotificationService}>
|
||||
<DialogProvider service={UIDialogService}>
|
||||
<ModalProvider modal={Modal} service={UIModalService}>
|
||||
{appRoutes}
|
||||
</ModalProvider>
|
||||
</DialogProvider>
|
||||
</SnackbarProvider>
|
||||
</ViewportDialogProvider>
|
||||
</ViewportGridProvider>
|
||||
</ThemeWrapper>
|
||||
</Router>
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIViewportDialogService,
|
||||
MeasurementService,
|
||||
DisplaySetService,
|
||||
ToolBarSerivce,
|
||||
@@ -46,6 +47,7 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
UIDialogService,
|
||||
UIViewportDialogService,
|
||||
MeasurementService,
|
||||
DisplaySetService,
|
||||
ToolBarSerivce,
|
||||
|
||||
Reference in new issue
Block a user