Add messages to sr pacs

This commit is contained in:
igoroctaviano 2020-06-30 12:33:38 -03:00
parent d5d219c271
commit 1bfb62d26d
8 changed files with 275 additions and 48 deletions

View File

@ -24,7 +24,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
measurementChangeTimestamp,
200
);
const { MeasurementService } = servicesManager.services;
const { MeasurementService, UINotificationService, UIDialogService } = servicesManager.services;
const [
trackedMeasurements,
sendTrackedMeasurementsEvent,
@ -117,21 +117,40 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
DICOMSR.downloadReport(trackedMeasurements, dataSource);
};
const onCreateReportClick = () => {
const measurements = MeasurementService.getMeasurements();
const trackedMeasurements = measurements.filter(
m =>
trackedStudy === m.referenceStudyUID &&
trackedSeries.includes(m.referenceSeriesUID)
);
const onCreateReportClick = async () => {
// TODO: Create a loading service that uses the dialog service with these options?
const loadingDialogId = UIDialogService.create({
showOverlay: true,
isDraggable: false,
centralize: true,
// TODO: Create a loading indicator component + zeplin design?
content: () => <div className="text-primary-active">Loading...</div>
});
try {
const measurements = MeasurementService.getMeasurements();
const trackedMeasurements = measurements.filter(
m =>
trackedStudy === m.referenceStudyUID &&
trackedSeries.includes(m.referenceSeriesUID)
);
const dataSources = extensionManager.getDataSources();
// TODO -> Eventually deal with multiple dataSources.
// Would need some way of saying which one is the "push" dataSource
const dataSource = dataSources[0];
const dataSources = extensionManager.getDataSources();
// TODO -> Eventually deal with multiple dataSources.
// Would need some way of saying which one is the "push" dataSource
const dataSource = dataSources[0];
DICOMSR.storeMeasurements(trackedMeasurements, dataSource);
const { message } = await DICOMSR.storeMeasurements(trackedMeasurements, dataSource);
UINotificationService.show({ title: 'STOW SR', message, type: 'success' });
} catch (error) {
UINotificationService.show({
title: 'STOW SR',
message: error.message || 'Failed to store measurements',
type: 'error',
});
} finally {
UIDialogService.dismiss({ id: loadingDialogId });
}
};
return (
@ -148,7 +167,7 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
title="Measurements"
amount={displayMeasurements.length}
data={displayMeasurements}
onClick={() => {}}
onClick={() => { }}
onEdit={id => alert(`Edit: ${id}`)}
/>
</div>

View File

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 19 19">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19">
<g fill="currentColor" fill-rule="evenodd">
<g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M.188.187L8.813 8.812M8.813.187L.188 8.812" transform="translate(5 5)"/>

Before

Width:  |  Height:  |  Size: 355 B

After

Width:  |  Height:  |  Size: 332 B

View File

@ -113,7 +113,7 @@ const IconButton = ({
};
IconButton.defaultProps = {
onClick: () => {},
onClick: () => { },
color: 'default',
disabled: false,
fullWidth: false,

View File

@ -0,0 +1,172 @@
/* TODO: Create tailwind styles for this component */
.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-hidden {
padding-top: 0;
padding-bottom: 0;
margin-bottom: 0;
height: 0;
opacity: 0;
transition: all 300ms ease;
}
@keyframes fadein {
from {
opacity: 0;
top: 30px;
}
to {
opacity: 1;
top: 0;
}
}
/* Internet Explorer */
@-ms-keyframes fadein {
from {
opacity: 0;
top: 30px;
}
to {
opacity: 1;
top: 0;
}
}

View File

@ -2,16 +2,18 @@ import React from 'react';
import SnackbarItem from './SnackbarItem';
import { useSnackbar } from '../../contextProviders';
import './Snackbar.css';
const SnackbarContainer = () => {
const { snackbarItems, hide } = useSnackbar();
const renderItem = item => {
return <SnackbarItem key={item.itemId} options={item} onClose={hide} />;
};
if (!snackbarItems) {
return null;
}
const renderItem = item => (
<SnackbarItem
key={item.itemId}
options={item}
onClose={hide}
/>
);
const renderItems = () => {
const items = {
@ -23,11 +25,9 @@ const SnackbarContainer = () => {
bottomRight: [],
};
snackbarItems.map(item => {
items[item.position].push(item);
});
snackbarItems.forEach(item => items[item.position].push(item));
return (
return snackbarItems && (
<div>
{Object.keys(items).map(pos => {
if (!items[pos].length) {
@ -35,7 +35,7 @@ const SnackbarContainer = () => {
}
return (
<div key={pos} className={`sb-container sb-${pos}`}>
<div key={pos} className={`fixed z-50 p-6 box-border h-auto sb-${pos}`}>
{items[pos].map((item, index) => (
<div key={item.id + index}>{renderItem(item)}</div>
))}

View File

@ -1,25 +1,36 @@
import React, { useEffect } from 'react';
import classNames from 'classnames';
import SnackbarTypes from './SnackbarTypes';
const SnackbarItem = ({ options, onClose }) => {
const handleClose = () => {
onClose(options.id);
};
const handleClose = () => onClose(options.id);
useEffect(() => {
if (options.autoClose) {
setTimeout(() => {
handleClose();
}, options.duration);
setTimeout(() => handleClose(), options.duration);
}
}, []);
const typeClasses = {
[SnackbarTypes.INFO]: 'bg-primary-active',
[SnackbarTypes.WARNING]: 'bg-yellow-600',
[SnackbarTypes.SUCCESS]: 'bg-green-600',
[SnackbarTypes.ERROR]: 'bg-red-600'
};
return (
<div>
<span onClick={handleClose}>
<span>x</span>
<div
className={classNames(
`${options.visible ? '' : 'sb-hidden'} sb-item`,
typeClasses[options.type]
)}
>
<span className="sb-closeBtn" onClick={handleClose}>
<span className="sb-closeIcon">x</span>
</span>
{options.title && <div>{options.title}</div>}
{options.message && <div>{options.message}</div>}
{options.title && <div className="sb-title">{options.title}</div>}
{options.message && <div className="sb-message">{options.message}</div>}
</div>
);
};

View File

@ -0,0 +1,8 @@
/* TODO: Find a better way to set the cursor for all contents of dialog. */
.DraggableItem.draggable div {
cursor: grab !important;
}
.DraggableItem.draggable.dragging div {
cursor: grabbing !important;
}

View File

@ -5,11 +5,14 @@ import React, {
useCallback,
useEffect,
} from 'react';
import PropTypes from 'prop-types';
import Draggable from 'react-draggable';
import classNames from 'classnames';
import { utils } from '@ohif/core';
import './DialogProvider.css';
const DialogContext = createContext(null);
@ -150,6 +153,7 @@ const DialogProvider = ({ children, service }) => {
onStart,
onStop,
onDrag,
showOverlay,
} = dialog;
let position =
@ -158,13 +162,13 @@ const DialogProvider = ({ children, service }) => {
position = centerPositions.find(position => position.id === id);
}
return (
const dragableItem = () => (
<Draggable
key={id}
disabled={!isDraggable}
position={position}
defaultPosition={position}
bounds="parent"
bounds='parent'
onStart={event => {
const e = event || window.event;
const target = e.target || e.srcElement;
@ -215,6 +219,21 @@ const DialogProvider = ({ children, service }) => {
</div>
</Draggable>
);
const withOverlay = component => {
const background = 'bg-black bg-opacity-50';
const overlay = 'fixed z-50 left-0 top-0 w-full h-full overflow-auto';
return (
<div
className={classNames(overlay, background)}
key={id}
>
{component}
</div>
);
};
return showOverlay ? withOverlay(dragableItem()) : dragableItem();
});
/**
@ -236,13 +255,11 @@ const DialogProvider = ({ children, service }) => {
return (
<DialogContext.Provider value={{ create, dismiss, dismissAll, isEmpty }}>
<div className="DraggableArea">
{dialogs.some(dialog => dialog.showOverlay) ? (
<div className="Overlay active">{renderDialogs()}</div>
) : (
renderDialogs()
)}
</div>
{!isEmpty() &&
<div className='w-full h-full absolute'>
{renderDialogs()}
</div>
}
{children}
</DialogContext.Provider>
);