feat(DICOM Upload): Added new DICOM upload dialogue launched from the worklist page (#3326)
* feat(DICOM Upload) OHIF #3297 - DicomWebDataSource.store.dicom now accepts an ArrayBuffer of data sets to store - DicomWebDataSource.store.dicom now also accepts optional callbacks to track upload and an AbortSignal object to cancel upload - Added DicomFileUploader class that performs and tracks the upload of a DICOM file - Added various UI pieces for the upload: DicomUpload, DicomUploadProgress, DicomUploadProgressItem - ProgressLoadingBar was extracted from LoadingIndicatorProgress so it can be reused - Modal dialogues can now optionally prevent an outside click from closing the Modal * Passing an XMLHttpRequest to the dataSource.store.dicom method instead of callbacks and AbortSignal. Cleanup of various UI pieces to minimize code and increase readability. * Made the DicomUpload component a customization module exported by the cornerstone extension. * Exposed a copy of the data source configuration via the IWebApiDataSource interface. Added dicomUploadEnabled to a data source's configuration. * Code clean up. * Upgraded the dicomweb-client version to one that provides the ability to pass a custom HTTP request. DICOM upload uses that custom HTTP request to track progress and cancel requests. * Distinguished between failed and cancelled uploads. * Allow no selection in the upload dialogue. Fixed the styling of various progress information so that everything aligns. * Switched from cornerstone wado image loader to cornerstone dicom image loader for DICOM upload. * Added special cancelled icon to differentiate from failed. * Added a bit of spacing between the upload progress bar and percentage. * Fixed minor issue with upload rejection. * Performance improvement for cancel all uploads: - use React memo for each upload item progress (row) - do not await each request of a cancel all * Fixed various padding/spacing for the DICOM upload drop zone component. Changed the border dashing for the DICOM upload drop zone to be a background image gradient. Added hover and active effects to the 'Cancel All Uploads' text.
This commit is contained in:
1 parent
f377153b60
commit
66f6e3eade
32 files changed
+1126
-350
No files matched your search
@@ -43,7 +43,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"dcmjs": "^0.29.5",
|
||||
"dicomweb-client": "^0.8.4",
|
||||
"dicomweb-client": "^0.10.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
"lodash.merge": "^4.6.1",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ function create({
|
||||
deleteStudyMetadataPromise,
|
||||
getImageIdsForDisplaySet,
|
||||
getImageIdsForInstance,
|
||||
getConfig,
|
||||
}) {
|
||||
const defaultQuery = {
|
||||
studies: {
|
||||
@@ -57,6 +58,11 @@ function create({
|
||||
};
|
||||
|
||||
const defaultReject = {};
|
||||
|
||||
const defaultGetConfig = () => {
|
||||
return { dicomUploadEnabled: false };
|
||||
};
|
||||
|
||||
return {
|
||||
query: query || defaultQuery,
|
||||
retrieve: retrieve || defaultRetrieve,
|
||||
@@ -66,6 +72,7 @@ function create({
|
||||
deleteStudyMetadataPromise,
|
||||
getImageIdsForDisplaySet,
|
||||
getImageIdsForInstance,
|
||||
getConfig: getConfig || defaultGetConfig,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M16 7.863a8.117 8.117 0 0 1-8 8.136 7.882 7.882 0 0 1-8-7.86A8.117 8.117 0 0 1 8 .002a7.883 7.883 0 0 1 8 7.862z" fill="#B70D11"/>
|
||||
<g stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M7.827 12.439a.313.313 0 0 0-.174.05c-.045.033-.07.076-.067.12.005.09.117.163.253.163h0c.066 0 .129-.02.174-.051.046-.032.07-.075.067-.12-.004-.088-.11-.16-.244-.162h-.005M7.836 8.667V4"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 601 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path d="M24 11.794c.017 6.667-5.333 12.108-12 12.205a11.823 11.823 0 0 1-12-11.79C-.019 5.541 5.331.1 12 .001a11.824 11.824 0 0 1 12 11.793z" fill="#B70D11"/>
|
||||
<g stroke="#FFF" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
|
||||
<path d="M11.494 17.158a.245.245 0 0 0-.241.255.254.254 0 0 0 .253.245h0a.246.246 0 0 0 .241-.255.253.253 0 0 0-.244-.245h-.005M11.503 13V6"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 566 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="20" height="16" viewBox="0 0 20 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="#5ACCE6" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m1 6.467 2.222 3.31 2.556-3.06M19 9.898l-2.22-3.311-2.558 3.061"/>
|
||||
<path d="M16.75 6.617a6.876 6.876 0 0 1-5.192 7.758A6.773 6.773 0 0 1 5.234 12.6M3.226 9.758a7.06 7.06 0 0 1 5.213-8.575 6.773 6.773 0 0 1 6.638 2.107"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(1 1)" stroke="#348CFD" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="8" cy="8" r="8"/>
|
||||
<path d="M8 3.273v9.454M4 7.273l4-4 4 4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 320 B |
@@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g stroke="#5ACCE6" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16 7.863a8.117 8.117 0 0 1-8 8.136 7.882 7.882 0 0 1-8-7.86A8.117 8.117 0 0 1 8 .002a7.883 7.883 0 0 1 8 7.862z" stroke-width="1.5"/>
|
||||
<g stroke-width="2">
|
||||
<path d="M7.827 12.439a.313.313 0 0 0-.174.05c-.045.033-.07.076-.067.12.005.09.117.163.253.163h0c.066 0 .129-.02.174-.051.046-.032.07-.075.067-.12-.004-.088-.11-.16-.244-.162h-.005M7.836 8.667V4"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 608 B |
@@ -58,6 +58,8 @@ const variants = {
|
||||
contained: {
|
||||
default: 'text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
primary: 'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
primaryDark:
|
||||
'text-primary-active hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
secondary:
|
||||
'text-white hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
white: 'text-black hover:opacity-80 active:opacity-100 focus:opacity-80',
|
||||
@@ -93,6 +95,7 @@ const defaultVariantBackGroundColor = {
|
||||
contained: {
|
||||
default: 'bg-primary-light',
|
||||
primary: 'bg-primary-main',
|
||||
primaryDark: 'bg-primary-dark',
|
||||
secondary: 'bg-secondary-light',
|
||||
white: 'bg-white',
|
||||
black: 'bg-black',
|
||||
@@ -231,6 +234,7 @@ Button.propTypes = {
|
||||
color: PropTypes.oneOf([
|
||||
'default',
|
||||
'primary',
|
||||
'primaryDark',
|
||||
'primaryActive',
|
||||
'secondary',
|
||||
'white',
|
||||
|
||||
@@ -50,6 +50,8 @@ import tracked from './../../assets/icons/tracked.svg';
|
||||
import unlink from './../../assets/icons/unlink.svg';
|
||||
import checkboxChecked from './../../assets/icons/checkbox-checked.svg';
|
||||
import checkboxUnchecked from './../../assets/icons/checkbox-unchecked.svg';
|
||||
import iconAlertOutline from './../../assets/icons/icons-alert-outline.svg';
|
||||
import iconAlertSmall from './../../assets/icons/icon-alert-small.svg';
|
||||
import iconClose from './../../assets/icons/icon-close.svg';
|
||||
import iconNextInactive from './../../assets/icons/icon-next-inactive.svg';
|
||||
import iconNext from './../../assets/icons/icon-next.svg';
|
||||
@@ -57,6 +59,9 @@ import iconPlay from './../../assets/icons/icon-play.svg';
|
||||
import iconPause from './../../assets/icons/icon-pause.svg';
|
||||
import iconPrevInactive from './../../assets/icons/icon-prev-inactive.svg';
|
||||
import iconPrev from './../../assets/icons/icon-prev.svg';
|
||||
import iconStatusAlert from './../../assets/icons/icon-status-alert.svg';
|
||||
import iconTransferring from './../../assets/icons/icon-transferring.svg';
|
||||
import iconUpload from './../../assets/icons/icon-upload.svg';
|
||||
import navigationPanelRightHide from './../../assets/icons/navigation-panel-right-hide.svg';
|
||||
import navigationPanelRightReveal from './../../assets/icons/navigation-panel-right-reveal.svg';
|
||||
import tabLinear from './../../assets/icons/tab-linear.svg';
|
||||
@@ -144,9 +149,13 @@ const ICONS = {
|
||||
'external-link': externalLink,
|
||||
'group-layers': groupLayers,
|
||||
info: info,
|
||||
'icon-alert-outline': iconAlertOutline,
|
||||
'icon-alert-small': iconAlertSmall,
|
||||
'icon-close': iconClose,
|
||||
'icon-play': iconPlay,
|
||||
'icon-pause': iconPause,
|
||||
'icon-status-alert': iconStatusAlert,
|
||||
'icon-transferring': iconTransferring,
|
||||
'info-action': infoAction,
|
||||
'info-link': infoLink,
|
||||
'arrow-left': arrowLeft,
|
||||
@@ -230,6 +239,7 @@ const ICONS = {
|
||||
'icon-next': iconNext,
|
||||
'icon-prev-inactive': iconPrevInactive,
|
||||
'icon-prev': iconPrev,
|
||||
'icon-upload': iconUpload,
|
||||
'navigation-panel-right-hide': navigationPanelRightHide,
|
||||
'navigation-panel-right-reveal': navigationPanelRightReveal,
|
||||
'tab-linear': tabLinear,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from '@ohif/ui';
|
||||
|
||||
import './LoadingIndicatorProgress.css';
|
||||
import ProgressLoadingBar from '../ProgressLoadingBar';
|
||||
|
||||
/**
|
||||
* A React component that renders a loading indicator.
|
||||
@@ -19,18 +18,8 @@ function LoadingIndicatorProgress({ className, textBlock, progress }) {
|
||||
)}
|
||||
>
|
||||
<Icon name="loading-ohif-mark" className="text-white w-12 h-12" />
|
||||
<div className="loading">
|
||||
{progress === undefined || progress === null ? (
|
||||
<div className="infinite-loading-bar bg-primary-light"></div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-primary-light"
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
height: '8px',
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
<div className="w-48">
|
||||
<ProgressLoadingBar></ProgressLoadingBar>
|
||||
</div>
|
||||
{textBlock}
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ const Modal = ({
|
||||
title,
|
||||
onClose,
|
||||
children,
|
||||
shouldCloseOnOverlayClick,
|
||||
}) => {
|
||||
const { hide } = useModal();
|
||||
|
||||
@@ -56,6 +57,7 @@ const Modal = ({
|
||||
onRequestClose={handleClose}
|
||||
isOpen={isOpen}
|
||||
title={title}
|
||||
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
|
||||
>
|
||||
{renderHeader()}
|
||||
<section className="ohif-scrollbar modal-content overflow-y-auto px-4 py-6 rounded-bl rounded-br bg-primary-dark">
|
||||
@@ -67,6 +69,7 @@ const Modal = ({
|
||||
|
||||
Modal.defaultProps = {
|
||||
shouldCloseOnEsc: true,
|
||||
shouldCloseOnOverlayClick: true,
|
||||
};
|
||||
|
||||
Modal.propTypes = {
|
||||
@@ -80,6 +83,7 @@ Modal.propTypes = {
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]).isRequired,
|
||||
shouldCloseOnOverlayClick: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
+1
-9
@@ -4,7 +4,7 @@
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 12em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.infinite-loading-bar {
|
||||
@@ -15,14 +15,6 @@
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
animation: side2side 2s ease-in-out infinite;
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@keyframes side2side {
|
||||
0%,
|
||||
100% {
|
||||
@@ -0,0 +1,34 @@
|
||||
import React, { ReactElement } from 'react';
|
||||
|
||||
import './ProgressLoadingBar.css';
|
||||
|
||||
export type ProgressLoadingBarProps = {
|
||||
progress?: number;
|
||||
};
|
||||
/**
|
||||
* A React component that renders a loading progress bar.
|
||||
* If progress is not provided, it will render an infinite loading bar
|
||||
* If progress is provided, it will render a progress bar
|
||||
* The progress text can be optionally displayed to the left of the bar.
|
||||
*/
|
||||
function ProgressLoadingBar({
|
||||
progress,
|
||||
}: ProgressLoadingBarProps): ReactElement {
|
||||
return (
|
||||
<div className="loading">
|
||||
{progress === undefined || progress === null ? (
|
||||
<div className="infinite-loading-bar bg-primary-light"></div>
|
||||
) : (
|
||||
<div
|
||||
className="bg-primary-light"
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
height: '8px',
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProgressLoadingBar;
|
||||
@@ -0,0 +1,2 @@
|
||||
import ProgressLoadingBar from './ProgressLoadingBar';
|
||||
export default ProgressLoadingBar;
|
||||
@@ -11,6 +11,7 @@ const StudyListFilter = ({
|
||||
clearFilters,
|
||||
isFiltering,
|
||||
numOfStudies,
|
||||
onUploadClick,
|
||||
}) => {
|
||||
const { t } = useTranslation('StudyList');
|
||||
const { sortBy, sortDirection } = filterValues;
|
||||
@@ -33,6 +34,15 @@ const StudyListFilter = ({
|
||||
<Typography variant="h4" className="mr-6 text-primary-light">
|
||||
{t('StudyList')}
|
||||
</Typography>
|
||||
{onUploadClick && (
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer text-primary-active text-lg self-center font-semibold"
|
||||
onClick={onUploadClick}
|
||||
>
|
||||
<Icon name="icon-upload"></Icon>
|
||||
<span>Upload</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
{isFiltering && (
|
||||
@@ -119,6 +129,7 @@ StudyListFilter.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
clearFilters: PropTypes.func.isRequired,
|
||||
isFiltering: PropTypes.bool.isRequired,
|
||||
onUploadClick: PropTypes.func,
|
||||
};
|
||||
|
||||
export default StudyListFilter;
|
||||
@@ -70,6 +70,7 @@ import CheckBox from './CheckBox';
|
||||
import LoadingIndicatorProgress from './LoadingIndicatorProgress';
|
||||
import LoadingIndicatorTotalPercent from './LoadingIndicatorTotalPercent';
|
||||
import ViewportActionBar from './ViewportActionBar';
|
||||
import ProgressLoadingBar from './ProgressLoadingBar';
|
||||
|
||||
export {
|
||||
AboutModal,
|
||||
@@ -110,6 +111,7 @@ export {
|
||||
Modal,
|
||||
NavBar,
|
||||
Notification,
|
||||
ProgressLoadingBar,
|
||||
Select,
|
||||
SegmentationTable,
|
||||
SegmentationGroupTable,
|
||||
|
||||
@@ -32,6 +32,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
content: null,
|
||||
contentProps: null,
|
||||
shouldCloseOnEsc: true,
|
||||
shouldCloseOnOverlayClick: true,
|
||||
isOpen: true,
|
||||
closeButton: true,
|
||||
title: null,
|
||||
@@ -39,7 +40,6 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
};
|
||||
const { t } = useTranslation('Modals');
|
||||
|
||||
|
||||
const [options, setOptions] = useState(DEFAULT_OPTIONS);
|
||||
|
||||
/**
|
||||
@@ -57,9 +57,9 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
*
|
||||
* @returns void
|
||||
*/
|
||||
const hide = useCallback(() => setOptions(DEFAULT_OPTIONS), [
|
||||
DEFAULT_OPTIONS,
|
||||
]);
|
||||
const hide = useCallback(() => {
|
||||
setOptions(DEFAULT_OPTIONS);
|
||||
}, [DEFAULT_OPTIONS]);
|
||||
|
||||
/**
|
||||
* Sets the implementation of a modal service that can be used by extensions.
|
||||
@@ -80,6 +80,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
customClassName,
|
||||
shouldCloseOnEsc,
|
||||
closeButton,
|
||||
shouldCloseOnOverlayClick,
|
||||
} = options;
|
||||
|
||||
return (
|
||||
@@ -92,6 +93,7 @@ const ModalProvider = ({ children, modal: Modal, service }) => {
|
||||
title={t(title)}
|
||||
closeButton={closeButton}
|
||||
onClose={hide}
|
||||
shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
|
||||
>
|
||||
<ModalContent {...contentProps} show={show} hide={hide} />
|
||||
</Modal>
|
||||
|
||||
@@ -70,6 +70,7 @@ export {
|
||||
Modal,
|
||||
NavBar,
|
||||
Notification,
|
||||
ProgressLoadingBar,
|
||||
Select,
|
||||
SegmentationTable,
|
||||
SegmentationGroupTable,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
window.config = {
|
||||
routerBasename: '/',
|
||||
customizationService: {
|
||||
dicomUploadComponent:
|
||||
'@ohif/extension-cornerstone.customizationModule.cornerstoneDicomUploadComponent',
|
||||
},
|
||||
showStudyList: true,
|
||||
extensions: [],
|
||||
modes: [],
|
||||
@@ -26,6 +30,7 @@ window.config = {
|
||||
requestOptions: {
|
||||
auth: 'admin:admin',
|
||||
},
|
||||
dicomUploadEnabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,6 +3,10 @@ window.config = {
|
||||
// whiteLabelling: {},
|
||||
extensions: [],
|
||||
modes: [],
|
||||
customizationService: {
|
||||
dicomUploadComponent:
|
||||
'@ohif/extension-cornerstone.customizationModule.cornerstoneDicomUploadComponent',
|
||||
},
|
||||
showStudyList: true,
|
||||
maxNumberOfWebWorkers: 3,
|
||||
showLoadingIndicator: true,
|
||||
@@ -28,6 +32,7 @@ window.config = {
|
||||
useBulkDataURI: false,
|
||||
supportsFuzzyMatching: true,
|
||||
supportsWildcard: true,
|
||||
dicomUploadEnabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -56,13 +56,14 @@ function DataSourceWrapper(props) {
|
||||
// But only for LayoutTemplate type of 'list'?
|
||||
// Or no data fetching here, and just hand down my source
|
||||
const STUDIES_LIMIT = 101;
|
||||
const [data, setData] = useState({
|
||||
const DEFAULT_DATA = {
|
||||
studies: [],
|
||||
total: 0,
|
||||
resultsPerPage: 25,
|
||||
pageNumber: 1,
|
||||
location: 'Not a valid location, causes first load to occur',
|
||||
});
|
||||
};
|
||||
const [data, setData] = useState(DEFAULT_DATA);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -125,6 +126,8 @@ function DataSourceWrapper(props) {
|
||||
dataTotal={data.total}
|
||||
dataSource={dataSource}
|
||||
isLoadingData={isLoading}
|
||||
// To refresh the data, simply reset it to DEFAULT_DATA which invalidates it and triggers a new query to fetch the data.
|
||||
onRefresh={() => setData(DEFAULT_DATA)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import filtersMeta from './filtersMeta.js';
|
||||
import { useAppConfig } from '@state';
|
||||
import { useDebounce, useSearchParams } from '@hooks';
|
||||
import { utils, hotkeys } from '@ohif/core';
|
||||
import { utils, hotkeys, ServicesManager } from '@ohif/core';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
@@ -47,6 +47,8 @@ function WorkList({
|
||||
dataSource,
|
||||
hotkeysManager,
|
||||
dataPath,
|
||||
onRefresh,
|
||||
servicesManager,
|
||||
}) {
|
||||
const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager;
|
||||
const { show, hide } = useModal();
|
||||
@@ -429,6 +431,33 @@ function WorkList({
|
||||
});
|
||||
}
|
||||
|
||||
const { customizationService } = servicesManager.services;
|
||||
const { component: dicomUploadComponent } =
|
||||
customizationService.get('dicomUploadComponent') ?? {};
|
||||
const uploadProps =
|
||||
dicomUploadComponent && dataSource.getConfig().dicomUploadEnabled
|
||||
? {
|
||||
title: 'Upload files',
|
||||
closeButton: true,
|
||||
shouldCloseOnEsc: false,
|
||||
shouldCloseOnOverlayClick: false,
|
||||
content: dicomUploadComponent.bind(null, {
|
||||
dataSource,
|
||||
onComplete: () => {
|
||||
hide();
|
||||
onRefresh();
|
||||
},
|
||||
onStarted: () => {
|
||||
show({
|
||||
...uploadProps,
|
||||
// when upload starts, hide the default close button as closing the dialogue must be handled by the upload dialogue itself
|
||||
closeButton: false,
|
||||
});
|
||||
},
|
||||
}),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className="bg-black h-screen flex flex-col ">
|
||||
<Header
|
||||
@@ -445,6 +474,7 @@ function WorkList({
|
||||
onChange={setFilterValues}
|
||||
clearFilters={() => setFilterValues(defaultFilterValues)}
|
||||
isFiltering={isFiltering(filterValues, defaultFilterValues)}
|
||||
onUploadClick={uploadProps ? () => show(uploadProps) : undefined}
|
||||
/>
|
||||
{hasStudies ? (
|
||||
<>
|
||||
@@ -478,8 +508,10 @@ WorkList.propTypes = {
|
||||
data: PropTypes.array.isRequired,
|
||||
dataSource: PropTypes.shape({
|
||||
query: PropTypes.object.isRequired,
|
||||
getConfig: PropTypes.func,
|
||||
}).isRequired,
|
||||
isLoadingData: PropTypes.bool.isRequired,
|
||||
servicesManager: PropTypes.instanceOf(ServicesManager),
|
||||
};
|
||||
|
||||
const defaultFilterValues = {
|
||||
|
||||
@@ -25,12 +25,6 @@ const bakedInRoutes = [
|
||||
|
||||
// NOT FOUND (404)
|
||||
const notFoundRoute = { component: NotFound };
|
||||
const WorkListRoute = {
|
||||
path: '/',
|
||||
children: DataSourceWrapper,
|
||||
private: true,
|
||||
props: { children: WorkList },
|
||||
};
|
||||
|
||||
const createRoutes = ({
|
||||
modes,
|
||||
@@ -54,6 +48,13 @@ const createRoutes = ({
|
||||
|
||||
const { customizationService } = servicesManager.services;
|
||||
|
||||
const WorkListRoute = {
|
||||
path: '/',
|
||||
children: DataSourceWrapper,
|
||||
private: true,
|
||||
props: { children: WorkList, servicesManager },
|
||||
};
|
||||
|
||||
const customRoutes = customizationService.getGlobalCustomization(
|
||||
'customRoutes'
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user