fixes for hotkeys usage in docusaurus

This commit is contained in:
Erik Ziegler 2021-07-21 12:58:15 +02:00
parent 7a9f113b91
commit ff28a2264b
24 changed files with 61 additions and 659 deletions

View File

@ -2,7 +2,7 @@
<!-- markdownlint-disable -->
<div align="center">
<h1>OHIF Medical Imaging Viewer</h1>
<p><strong>The OHIF Viewer</strong> is a zero-footprint medical image viewer provided by the <a href="http://ohif.org/">Open Health Imaging Foundation (OHIF)</a>. It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support <a href="https://www.dicomstandard.org/dicomweb/">DICOMweb</a>.</p>
<p><strong>The OHIF Viewer</strong> is a zero-footprint medical image viewer provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF)</a>. It is a configurable and extensible progressive web application with out-of-the-box support for image archives which support <a href="https://www.dicomstandard.org/dicomweb/">DICOMweb</a>.</p>
</div>

View File

@ -11,11 +11,9 @@ import {
} from '@ohif/ui';
import i18n from '@ohif/i18n';
import { utils } from '@ohif/ui';
import { hotkeys } from '@ohif/core';
import { useNavigate } from 'react-router-dom';
const { hotkeys } = utils;
const { availableLanguages, defaultLanguage, currentLanguage } = i18n;
import { useAppConfig } from '@state';

View File

@ -1,6 +1,5 @@
import toolbarButtons from './toolbarButtons.js';
import { utils } from '@ohif/ui';
const { hotkeys } = utils;
import { hotkeys } from '@ohif/core';
const ohif = {
layout: 'org.ohif.default.layoutTemplateModule.viewerLayout',

View File

@ -34,7 +34,8 @@
"cornerstone-tools": "5.1.2",
"cornerstone-math": "0.1.9",
"cornerstone-wado-image-loader": "^3.1.2",
"dicom-parser": "^1.8.3"
"dicom-parser": "^1.8.3",
"@ohif/ui": "^1.8.2"
},
"dependencies": {
"@babel/runtime": "7.7.6",

View File

@ -1,6 +1,6 @@
import objectHash from 'object-hash';
import log from './../log.js';
import hotkeys from './../utils/hotkeys';
import { hotkeys } from '../utils';
/**
*

View File

@ -46,7 +46,7 @@ const utils = {
resolveObjectPath,
hierarchicalListUtils,
progressTrackingUtils,
isLowPriorityModality
isLowPriorityModality,
};
export {
@ -69,7 +69,7 @@ export {
resolveObjectPath,
hierarchicalListUtils,
progressTrackingUtils,
isLowPriorityModality
isLowPriorityModality,
};
export default utils;

View File

@ -70,7 +70,7 @@ and registered extension's features, are configured using this file.
The easiest way to apply your own configuration is to modify the `default.js`
file. For more advanced cofiguration options, check out our
[configuration essentials guide](../configuring/index.md).
[configuration essentials guide](../../configuration/index.md).
## Next Steps

View File

@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 4
sidebar_label: Issue & PR Triage Process
---

View File

@ -2,27 +2,17 @@
sidebar_position: 3
sidebar_label: Data Source
---
# Module: Data Source
## Overview
The internal data structure of OHIFs metadata follows naturalized DICOM JSON, A format pioneered by `dcmjs`. In short DICOM metadata headers with DICOM Keywords instead of tags and sequences as arrays, for easy development and clear code.
The internal data structure of OHIFs metadata follows naturalized DICOM JSON, A
format pioneered by `dcmjs`. In short DICOM metadata headers with DICOM Keywords
instead of tags and sequences as arrays, for easy development and clear code.
We have built a standard for fetching and mapping data into OHIFs native format, which we call DataSources, and have provided one implementation of this standard.
We have built a standard for fetching and mapping data into OHIFs native
format, which we call DataSources, and have provided one implementation of this
standard.
You can make another datasource implementation which communicates to your backend and maps to OHIFs native format, then use any existing mode on your platform. Your data doesnt even need to be DICOM if you can map some proprietary data to the correct format.
You can make another datasource implementation which communicates to your
backend and maps to OHIFs native format, then use any existing mode on your
platform. Your data doesnt even need to be DICOM if you can map some
proprietary data to the correct format.
The DataSource is also a place to add easy helper methods that platform-specific
extensions can call in order to interact with the backend, meaning proprietary
data interactions can be wrapped in extensions.
The DataSource is also a place to add easy helper methods that platform-specific extensions can call in order to interact with the backend, meaning proprietary data interactions can be wrapped in extensions.
```js
const getDataSourcesModule = () => [
@ -36,13 +26,13 @@ const getDataSourcesModule = () => [
];
```
Default extension provides two main data sources that are commonly used:
`dicomweb` and `dicomjson`
Default extension provides two main data sources that are commonly used: `dicomweb` and `dicomjson`
```js
import { createDicomWebApi } from './DicomWebDataSource/index.js';
import { createDicomJSONApi } from './DicomJSONDataSource/index.js';
function getDataSourcesModule() {
return [
{
@ -59,14 +49,13 @@ function getDataSourcesModule() {
}
```
## Custom DataSource
You can add your custom datasource by creating the implementation using `IWebApiDataSource.create` from `@ohif/core`. This factory function creates a new "Web API" data source that fetches data over HTTP.
You need to make sure, you implement the following functions for the data source.
You can add your custom datasource by creating the implementation using
`IWebApiDataSource.create` from `@ohif/core`. This factory function creates a
new "Web API" data source that fetches data over HTTP.
You need to make sure, you implement the following functions for the data
source.
```js title="platform/core/src/DataSources/IWebApiDataSource.js"
function create({
@ -87,9 +76,8 @@ function create({
You can take a look at `dicomweb` data source implementation to get an idea
`extensions/default/src/DicomWebDataSource/index.js`
## DicomMetadataStore
In `OHIF-v3` we have a central location for the metadata of studies and they are
located in `DicomMetadataStore`. Your custom datasource can communicate with
`DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will
learn more about `DicomMetadataStore` in services.
## DicomMetadataStore
In `OHIF-v3` we have a central location for the metadata of studies and they are located
in `DicomMetadataStore`. Your custom datasource can communicate with `DicomMetadataStore` to store, and fetch Study/Series/Instance metadata. We will learn more about `DicomMetadataStore` in services.

View File

@ -78,7 +78,7 @@ There are two types of `routes` that are created by the mode.
- Routes with dataSourceName `/${mode.id}/${dataSourceName}`
- Routes without dataSourceName `/${mode.id}`
Therefore navigating to `http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` will run the app with the layout and functionalities of the `viewer` mode using the `defaultDataSourceName` which is defined in the [App Config](../configuring/index.md)
Therefore navigating to `http://localhost:3000/viewer/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1` will run the app with the layout and functionalities of the `viewer` mode using the `defaultDataSourceName` which is defined in the [App Config](../../configuration/index.md)
You can use the same exact mode using a different registered data source (e.g., `dicomjson`) by navigating to `http://localhost:3000/viewer/dicomjson/?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125113417.1`

View File

@ -72,7 +72,7 @@ const AboutModal = ({buildNumber, versionNumber}) => {
</Link>
</span>
<span className="ml-4">
<Link href="http://ohif.org/" showIcon={true}>
<Link href="https://ohif.org/" showIcon={true}>
More details
</Link>
</span>

View File

@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import Input from '../Input';
import { hotkeys } from '../../utils/'
import { getKeys, formatKeysForInput } from './utils';
/**
@ -16,7 +15,7 @@ import { getKeys, formatKeysForInput } from './utils';
* @param {string} props.className input classes
* @param {Array[]} props.modifierKeys
*/
const HotkeyField = ({ disabled, keys, onChange, className, modifierKeys }) => {
const HotkeyField = ({ disabled, keys, onChange, className, modifierKeys, hotkeys }) => {
const inputValue = formatKeysForInput(keys);
useEffect(() => {
@ -57,6 +56,13 @@ HotkeyField.propTypes = {
className: PropTypes.string,
modifierKeys: PropTypes.array,
disabled: PropTypes.bool,
hotkeys: PropTypes.object({
initialize: PropTypes.func.isRequired,
pause: PropTypes.func.isRequired,
unpause: PropTypes.func.isRequired,
startRecording: PropTypes.func.isRequired,
record: PropTypes.func.isRequired,
}).isRequired
};
HotkeyField.defaultProps = {

View File

@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
import { MODIFIER_KEYS } from './hotkeysConfig';
import { validate, splitHotkeyDefinitionsAndCreateTuples } from './utils';
const HotkeysPreferences = ({ disabled, hotkeyDefinitions, errors: controlledErrors, onChange }) => {
const HotkeysPreferences = ({ disabled, hotkeyDefinitions, errors: controlledErrors, onChange, hotkeysModule }) => {
const { t } = useTranslation('UserPreferencesModal');
const visibleHotkeys = Object.keys(hotkeyDefinitions)
@ -83,6 +83,7 @@ const HotkeysPreferences = ({ disabled, hotkeyDefinitions, errors: controlledErr
keys={definition.keys}
modifierKeys={MODIFIER_KEYS}
onChange={onChangeHandler}
hotkeys={hotkeysModule}
className='text-lg h-8'
/>
{error && <span className='p-2 text-left text-red-600 text-sm'>{error}</span>}
@ -106,6 +107,13 @@ HotkeysPreferences.propTypes = {
onChange: PropTypes.func,
disabled: PropTypes.bool,
hotkeyDefinitions: PropTypes.object.isRequired,
hotkeysModule: PropTypes.object({
initialize: PropTypes.func.isRequired,
pause: PropTypes.func.isRequired,
unpause: PropTypes.func.isRequired,
startRecording: PropTypes.func.isRequired,
record: PropTypes.func.isRequired,
}).isRequired
};
HotkeysPreferences.defaultProps = {

View File

@ -46,7 +46,4 @@ const validate = ({ commandName, pressedKeys, hotkeys }) => {
return { error: undefined };
};
export {
validate,
splitHotkeyDefinitionsAndCreateTuples
};
export { validate, splitHotkeyDefinitionsAndCreateTuples };

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { Select, Typography, Button, HotkeysPreferences } from '../';
import { useTranslation } from 'react-i18next';
const UserPreferences = ({ availableLanguages, defaultLanguage, currentLanguage, disabled, hotkeyDefinitions, hotkeyDefaults, onCancel, onSubmit, onReset }) => {
const UserPreferences = ({ availableLanguages, defaultLanguage, currentLanguage, disabled, hotkeyDefinitions, hotkeyDefaults, onCancel, onSubmit, onReset, hotkeysModule }) => {
const { t } = useTranslation('UserPreferencesModal');
const [state, setState] = useState({
isDisabled: disabled,
@ -85,6 +85,7 @@ const UserPreferences = ({ availableLanguages, defaultLanguage, currentLanguage,
hotkeyDefinitions={state.hotkeyDefinitions}
onChange={onHotkeysChangeHandler}
errors={state.hotkeyErrors}
hotkeysModule={hotkeysModule}
/>
</Section>
<div className="flex flex-row justify-between">
@ -125,6 +126,13 @@ UserPreferences.propTypes = {
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
onReset: PropTypes.func,
hotkeysModule: PropTypes.object({
initialize: PropTypes.func.isRequired,
pause: PropTypes.func.isRequired,
unpause: PropTypes.func.isRequired,
startRecording: PropTypes.func.isRequired,
record: PropTypes.func.isRequired,
}).isRequired
};
UserPreferences.defaultProps = {

View File

@ -1,6 +1,6 @@
/** UTILS */
import utils from './utils';
export { utils };
//import utils from './utils';
//export { utils };
/** CONTEXT/HOOKS */
export {
@ -102,6 +102,3 @@ export {
export { getIcon, ICONS } from './components/Icon/getIcon';
export { BackgroundColor } from './pages/Colors/BackgroundColor';
export { ModalComponent } from './contextProviders/ModalComponent';
/** VIEWS */
export { StudyList, Viewer } from './views';

View File

@ -1,17 +0,0 @@
import studyListMock from '../mocks/studyList.json';
/** Values can be env vars */
const DEFAULT_MOCKED_STUDIES_LIMIT = 1000;
/**
* Method to get a mocked study list
* @param {number} items Number of studies to be loaded
* @returns {array} Study list
*/
const getMockedStudies = (items = 50) => {
const num =
items > DEFAULT_MOCKED_STUDIES_LIMIT ? DEFAULT_MOCKED_STUDIES_LIMIT : items;
return new Array(num).fill(studyListMock.studies[0]);
};
export default getMockedStudies;

View File

@ -1,14 +0,0 @@
import Mousetrap from 'mousetrap';
import pausePlugin from './pausePlugin';
import recordPlugin from './recordPlugin';
Mousetrap.initialize = () => {
if (!Mousetrap._initialized) {
recordPlugin(Mousetrap);
pausePlugin(Mousetrap);
Mousetrap._initialized = true;
}
};
export default Mousetrap;

View File

@ -1,32 +0,0 @@
/**
* adds a pause and unpause method to Mousetrap
* this allows you to enable or disable keyboard shortcuts
* without having to reset Mousetrap and rebind everything
*
* https://github.com/ccampbell/mousetrap/blob/master/plugins/pause/mousetrap-pause.js
*/
export default function(Mousetrap) {
var _originalStopCallback = Mousetrap.prototype.stopCallback;
Mousetrap.prototype.stopCallback = function(e, element, combo) {
var self = this;
if (self.paused) {
return true;
}
return _originalStopCallback.call(self, e, element, combo);
};
Mousetrap.prototype.pause = function() {
var self = this;
self.paused = true;
};
Mousetrap.prototype.unpause = function() {
var self = this;
self.paused = false;
};
Mousetrap.init();
}

View File

@ -1,218 +0,0 @@
/**
* This extension allows you to record a sequence using Mousetrap.
* {@link https://craig.is/killing/mice}
*
* @author Dan Tao <daniel.tao@gmail.com>
*/
export default function (Mousetrap) {
/**
* the sequence currently being recorded
*
* @type {Array}
*/
var _recordedSequence = [],
/**
* a callback to invoke after recording a sequence
*
* @type {Function|null}
*/
_recordedSequenceCallback = null,
/**
* a list of all of the keys currently held down
*
* @type {Array}
*/
_currentRecordedKeys = [],
/**
* temporary state where we remember if we've already captured a
* character key in the current combo
*
* @type {boolean}
*/
_recordedCharacterKey = false,
/**
* a handle for the timer of the current recording
*
* @type {null|number}
*/
_recordTimer = null,
/**
* the original handleKey method to override when Mousetrap.record() is
* called
*
* @type {Function}
*/
_origHandleKey = Mousetrap.prototype.handleKey;
/**
* handles a character key event
*
* @param {string} character
* @param {Array} modifiers
* @param {Event} e
* @returns void
*/
function _handleKey(character, modifiers, e) {
var self = this;
if (!self.recording) {
_origHandleKey.apply(self, arguments);
return;
}
// remember this character if we're currently recording a sequence
if (e.type == 'keydown') {
if (character.length === 1 && _recordedCharacterKey) {
_recordCurrentCombo();
}
for (let i = 0; i < modifiers.length; ++i) {
_recordKey(modifiers[i]);
}
_recordKey(character);
// once a key is released, all keys that were held down at the time
// count as a keypress
} else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {
_recordCurrentCombo();
}
}
/**
* marks a character key as held down while recording a sequence
*
* @param {string} key
* @returns void
*/
function _recordKey(key) {
// one-off implementation of Array.indexOf, since IE6-9 don't support it
for (let i = 0; i < _currentRecordedKeys.length; ++i) {
if (_currentRecordedKeys[i] === key) {
return;
}
}
_currentRecordedKeys.push(key);
if (key.length === 1) {
_recordedCharacterKey = true;
}
}
/**
* marks whatever key combination that's been recorded so far as finished
* and gets ready for the next combo
*
* @returns void
*/
function _recordCurrentCombo() {
_recordedSequence.push(_currentRecordedKeys);
_currentRecordedKeys = [];
_recordedCharacterKey = false;
_finishRecording();
}
/**
* ensures each combo in a sequence is in a predictable order and formats
* key combos to be '+'-delimited
*
* modifies the sequence in-place
*
* @param {Array} sequence
* @returns void
*/
function _normalizeSequence(sequence) {
for (let i = 0; i < sequence.length; ++i) {
sequence[i].sort(function (x, y) {
// modifier keys always come first, in alphabetical order
if (x.length > 1 && y.length === 1) {
return -1;
} else if (x.length === 1 && y.length > 1) {
return 1;
}
// character keys come next (list should contain no duplicates,
// so no need for equality check)
return x > y ? 1 : -1;
});
sequence[i] = sequence[i].join('+');
}
}
/**
* finishes the current recording, passes the recorded sequence to the stored
* callback, and sets Mousetrap.handleKey back to its original function
*
* @returns void
*/
function _finishRecording() {
if (_recordedSequenceCallback) {
_normalizeSequence(_recordedSequence);
_recordedSequenceCallback(_recordedSequence);
}
// reset all recorded state
_recordedSequence = [];
_recordedSequenceCallback = null;
_currentRecordedKeys = [];
}
/**
* called to set a 1 second timeout on the current recording
*
* this is so after each key press in the sequence the recording will wait for
* 1 more second before executing the callback
*
* @returns void
*/
function _restartRecordTimer() {
clearTimeout(_recordTimer);
_recordTimer = setTimeout(_finishRecording, 1000);
}
/**
* records the next sequence and passes it to a callback once it's
* completed
*
* @param {Function} callback
* @returns void
*/
Mousetrap.prototype.record = function (callback) {
var self = this;
self.recording = true;
_recordedSequenceCallback = function () {
self.recording = false;
callback.apply(self, arguments);
};
};
/**
* stop recording
*
* @param {Function} callback
* @returns void
*/
Mousetrap.prototype.stopRecord = function () {
var self = this;
self.recording = false;
};
/**
* start recording
*
* @param {Function} callback
* @returns void
*/
Mousetrap.prototype.startRecording = function () {
var self = this;
self.recording = true;
};
Mousetrap.prototype.handleKey = function () {
var self = this;
_handleKey.apply(self, arguments);
};
Mousetrap.init();
}

View File

@ -1,8 +0,0 @@
import getMockedStudies from './getMockedStudies';
import hotkeys from './hotkeys';
const utils = { getMockedStudies, hotkeys };
export { getMockedStudies, hotkeys };
export default utils;

View File

@ -1,295 +0,0 @@
/**
* THIS IS A TEMPORARY FILE -- SHOULD BE REMOVED
*/
import React, { useState } from 'react';
import classnames from 'classnames';
import moment from 'moment';
import {
EmptyStudies,
Icon,
StudyListExpandedRow,
Button,
StudyListPagination,
StudyListTable,
StudyListFilter,
} from '../../components';
import utils from '../../utils';
// fix imports after refactor
import Header from './components/Header';
const filtersMeta = [
{
name: 'patientName',
displayName: 'Patient Name',
inputType: 'Text',
isSortable: true,
gridCol: 4,
},
{
name: 'mrn',
displayName: 'MRN',
inputType: 'Text',
isSortable: true,
gridCol: 2,
},
{
name: 'studyDate',
displayName: 'Study date',
inputType: 'DateRange',
isSortable: true,
gridCol: 5,
},
{
name: 'description',
displayName: 'Description',
inputType: 'Text',
isSortable: true,
gridCol: 4,
},
{
name: 'modality',
displayName: 'Modality',
inputType: 'MultiSelect',
inputProps: {
options: [
{ value: 'SEG', label: 'SEG' },
{ value: 'CT', label: 'CT' },
{ value: 'MR', label: 'MR' },
{ value: 'SR', label: 'SR' },
],
},
isSortable: true,
gridCol: 3,
},
{
name: 'accession',
displayName: 'Accession',
inputType: 'Text',
isSortable: true,
gridCol: 4,
},
{
name: 'instances',
displayName: 'Instances',
inputType: 'None',
isSortable: true,
gridCol: 2,
},
];
const defaultFilterValues = {
patientName: '',
mrn: '',
studyDate: {
startDate: null,
endDate: null,
},
description: '',
modality: undefined,
accession: '',
sortBy: '',
sortDirection: 'none',
page: 0,
resultsPerPage: 25,
};
const isFiltering = (filterValues, defaultFilterValues) => {
return Object.keys(defaultFilterValues).some(name => {
return filterValues[name] !== defaultFilterValues[name];
});
};
const StudyList = () => {
const [filterValues, setFilterValues] = useState(defaultFilterValues);
const studies = utils.getMockedStudies();
const numOfStudies = studies.length;
const [expandedRows, setExpandedRows] = useState([]);
const tableDataSource = studies.map((study, key) => {
const rowKey = key + 1;
const isExpanded = expandedRows.some(k => k === rowKey);
const {
AccessionNumber,
Modalities,
Instances,
StudyDescription,
PatientId,
PatientName,
StudyDate,
series,
} = study;
const seriesTableColumns = {
description: 'Description',
seriesNumber: 'Series',
modality: 'Modality',
Instances: 'Instances',
};
const seriesTableDataSource = series.map(seriesItem => {
const { SeriesNumber, Modality, instances } = seriesItem;
return {
description: 'Patient Protocol',
seriesNumber: SeriesNumber,
modality: Modality,
Instances: instances.length,
};
});
return {
row: [
{
key: 'patientName',
content: PatientName,
gridCol: 4,
},
{
key: 'mrn',
content: PatientId,
gridCol: 2,
},
{
key: 'studyDate',
content: (
<div>
<span className="mr-4">
{moment(StudyDate).format('MMM-DD-YYYY')}
</span>
<span>{moment(StudyDate).format('hh:mm A')}</span>
</div>
),
gridCol: 5,
},
{
key: 'description',
content: StudyDescription,
gridCol: 4,
},
{
key: 'modality',
content: Modalities,
gridCol: 3,
},
{
key: 'accession',
content: AccessionNumber,
gridCol: 4,
},
{
key: 'instances',
content: (
<>
<Icon
name="series-active"
className={classnames('inline-flex mr-2', {
'text-primary-active': isExpanded,
'text-secondary-light': !isExpanded,
})}
/>
{Instances}
</>
),
gridCol: 4,
},
],
expandedContent: (
<StudyListExpandedRow
seriesTableColumns={seriesTableColumns}
seriesTableDataSource={seriesTableDataSource}
>
<Button
rounded="full"
variant="contained"
className="mr-4 font-bold"
endIcon={<Icon name="launch-arrow" style={{ color: '#21a7c6' }} />}
>
Basic Viewer
</Button>
<Button
rounded="full"
variant="contained"
className="mr-4 font-bold"
endIcon={<Icon name="launch-arrow" style={{ color: '#21a7c6' }} />}
>
Segmentation
</Button>
<Button
rounded="full"
variant="outlined"
endIcon={<Icon name="launch-info" />}
className="font-bold"
>
Module 3
</Button>
<div className="ml-5 text-lg text-common-bright inline-flex items-center">
<Icon name="notificationwarning-diamond" className="mr-2 w-5 h-5" />
Feedback text lorem ipsum dolor sit amet
</div>
</StudyListExpandedRow>
),
onClickRow: () =>
setExpandedRows(s =>
isExpanded ? s.filter(n => rowKey !== n) : [...s, rowKey]
),
isExpanded,
};
});
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(25);
const totalPages = Math.floor(numOfStudies / perPage);
const onChangePage = page => {
if (page > totalPages) {
return;
}
setCurrentPage(page);
};
const onChangePerPage = perPage => {
setPerPage(perPage);
setCurrentPage(1);
};
const hasStudies = numOfStudies > 0;
return (
<div
className={classnames('bg-black h-full', {
'h-screen': !hasStudies,
})}
>
<Header />
<StudyListFilter
numOfStudies={numOfStudies}
filtersMeta={filtersMeta}
filterValues={filterValues}
onChange={setFilterValues}
clearFilters={() => setFilterValues(defaultFilterValues)}
isFiltering={isFiltering(filterValues, defaultFilterValues)}
/>
{hasStudies ? (
<>
<StudyListTable
tableDataSource={tableDataSource}
numOfStudies={numOfStudies}
filtersMeta={filtersMeta}
/>
<StudyListPagination
onChangePage={onChangePage}
onChangePerPage={onChangePerPage}
currentPage={currentPage}
perPage={perPage}
/>
</>
) : (
<div className="flex flex-col items-center justify-center pt-48">
<EmptyStudies />
</div>
)}
</div>
);
};
export default StudyList;

View File

@ -12,6 +12,8 @@ import { useAppConfig } from '@state';
import { useDebounce, useQuery } from '@hooks';
import { utils } from '@ohif/core';
const { sortBySeriesDate, hotkeys } = utils;
import {
Icon,
StudyListExpandedRow,
@ -194,7 +196,7 @@ function WorkList({
const series = await dataSource.query.series.search(studyInstanceUid);
seriesInStudiesMap.set(
studyInstanceUid,
utils.sortBySeriesDate(series)
sortBySeriesDate(series)
);
setStudiesWithSeriesData([...studiesWithSeriesData, studyInstanceUid]);
} catch (ex) {
@ -399,6 +401,7 @@ function WorkList({
hide();
},
onReset: () => hotkeysManager.restoreDefaultBindings(),
hotkeysModule: hotkeys
},
}),
},

View File

@ -1114,27 +1114,13 @@
core-js-pure "^3.15.0"
regenerator-runtime "^0.13.4"
"@babel/runtime@7.1.2":
version "7.1.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.2.tgz#81c89935f4647706fc54541145e6b4ecfef4b8e3"
integrity sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==
dependencies:
regenerator-runtime "^0.12.0"
"@babel/runtime@7.7.6", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6":
"@babel/runtime@7.1.2", "@babel/runtime@7.7.6", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.7.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
dependencies:
regenerator-runtime "^0.13.2"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.14.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.6.tgz#535203bc0892efc7dec60bdc27b2ecf6e409062d"
integrity sha512-/PCB2uJ7oM44tz8YhC4Z/6PeOKXp4K588f+5M3clr1M4zbqztlo0XEfJ2LEzj/FgwfgGcIdl8n7YYjTCI0BYwg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/template@^7.12.7", "@babel/template@^7.14.5", "@babel/template@^7.4.0":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
@ -15604,11 +15590,6 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
regenerator-runtime@^0.12.0:
version "0.12.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4:
version "0.13.7"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"