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
@@ -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