Try to get views to work for component library
This commit is contained in:
parent
ff28a2264b
commit
ca7506c5f0
14
platform/core/src/utils/hotkeys/index.js
Normal file
14
platform/core/src/utils/hotkeys/index.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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;
|
||||||
32
platform/core/src/utils/hotkeys/pausePlugin.js
Normal file
32
platform/core/src/utils/hotkeys/pausePlugin.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
218
platform/core/src/utils/hotkeys/recordPlugin.js
Normal file
218
platform/core/src/utils/hotkeys/recordPlugin.js
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
@ -61,7 +61,7 @@ window.config = {
|
|||||||
> data!
|
> data!
|
||||||
>
|
>
|
||||||
> You can read more about data sources at
|
> You can read more about data sources at
|
||||||
> [Data Source section in Modes](../modes/index.md)
|
> [Data Source section in Modes](../platform/modes/index.md)
|
||||||
|
|
||||||
The configuration can also be written as a JS Function in case you need to
|
The configuration can also be written as a JS Function in case you need to
|
||||||
inject dependencies like external services:
|
inject dependencies like external services:
|
||||||
|
|||||||
@ -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`
|
The easiest way to apply your own configuration is to modify the `default.js`
|
||||||
file. For more advanced cofiguration options, check out our
|
file. For more advanced cofiguration options, check out our
|
||||||
[configuration essentials guide](../../configuration/index.md).
|
[configuration essentials guide](../configuration/index.md).
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,345 @@
|
|||||||
|
---
|
||||||
|
name: Study List
|
||||||
|
menu: Views
|
||||||
|
route: examples/studyList
|
||||||
|
---
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Icon,
|
||||||
|
StudyListExpandedRow,
|
||||||
|
Button,
|
||||||
|
NavBar,
|
||||||
|
Svg,
|
||||||
|
IconButton,
|
||||||
|
EmptyStudies,
|
||||||
|
StudyListTable,
|
||||||
|
StudyListPagination,
|
||||||
|
StudyListFilter,
|
||||||
|
} from '@ohif/ui';
|
||||||
|
import utils from '../../../../src/utils';
|
||||||
|
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
# Study List
|
||||||
|
|
||||||
|
This example shows you how you can build a Study List page using the available
|
||||||
|
components.
|
||||||
|
|
||||||
|
```jsx live
|
||||||
|
function () {
|
||||||
|
const defaultFilterValues = {
|
||||||
|
patientName: '',
|
||||||
|
mrn: '',
|
||||||
|
studyDate: {
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
modality: undefined,
|
||||||
|
accession: '',
|
||||||
|
sortBy: '',
|
||||||
|
sortDirection: 'none',
|
||||||
|
page: 0,
|
||||||
|
resultsPerPage: 25,
|
||||||
|
};
|
||||||
|
const [filterValues, setFilterValues] = useState(defaultFilterValues);
|
||||||
|
const [studies, setStudies] = useState([]);
|
||||||
|
const numOfStudies = studies.length;
|
||||||
|
const [expandedRows, setExpandedRows] = useState([]);
|
||||||
|
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 isFiltering = (filterValues, defaultFilterValues) => {
|
||||||
|
return Object.keys(defaultFilterValues).some(name => {
|
||||||
|
return filterValues[name] !== defaultFilterValues[name];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
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="group-layers"
|
||||||
|
className={classnames('inline-flex mr-2 w-4', {
|
||||||
|
'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 handleStudyList = number => {
|
||||||
|
const studies = utils.getMockedStudies(number);
|
||||||
|
setStudies(studies);
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
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>
|
||||||
|
<div className="bg-white p-4">
|
||||||
|
<Button className="mr-2" onClick={() => handleStudyList(0)}>
|
||||||
|
0 Studies
|
||||||
|
</Button>
|
||||||
|
<Button className="mr-2" onClick={() => handleStudyList(50)}>
|
||||||
|
50 Studies
|
||||||
|
</Button>
|
||||||
|
<Button className="mr-2" onClick={() => handleStudyList(100)}>
|
||||||
|
100 Studies
|
||||||
|
</Button>
|
||||||
|
<Button className="mr-2" onClick={() => handleStudyList(1000)}>
|
||||||
|
1000 Studies
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={classnames('bg-black h-full', {
|
||||||
|
'h-screen': !hasStudies,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<NavBar className="justify-between border-b-4 border-black" isSticky>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="mx-3">
|
||||||
|
<Svg name="logo-ohif" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="mr-3 text-common-light text-lg">
|
||||||
|
FOR INVESTIGATIONAL USE ONLY
|
||||||
|
</span>
|
||||||
|
<IconButton
|
||||||
|
variant="text"
|
||||||
|
color="inherit"
|
||||||
|
className="text-primary-active"
|
||||||
|
onClick={() => {}}
|
||||||
|
>
|
||||||
|
<React.Fragment>
|
||||||
|
<Icon name="settings" />
|
||||||
|
<Icon name="chevron-down" />
|
||||||
|
</React.Fragment>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</NavBar>
|
||||||
|
<StudyListFilter
|
||||||
|
numOfStudies={numOfStudies}
|
||||||
|
filtersMeta={filtersMeta}
|
||||||
|
filterValues={filterValues}
|
||||||
|
onChange={setFilterValues}
|
||||||
|
clearFilters={() => setFilterValues(defaultFilterValues)}
|
||||||
|
isFiltering={isFiltering(filterValues, defaultFilterValues)}
|
||||||
|
/>
|
||||||
|
{hasStudies ? (
|
||||||
|
<>
|
||||||
|
<StudyListTable
|
||||||
|
tableDataSource={tableDataSource.slice(
|
||||||
|
(currentPage - 1) * perPage,
|
||||||
|
(currentPage - 1) * perPage + perPage
|
||||||
|
)}
|
||||||
|
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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "Example Views",
|
||||||
|
"position": 6
|
||||||
|
}
|
||||||
@ -1,346 +0,0 @@
|
|||||||
---
|
|
||||||
name: Study List
|
|
||||||
menu: Views
|
|
||||||
route: examples/studyList
|
|
||||||
---
|
|
||||||
|
|
||||||
import { useState } from 'react';
|
|
||||||
import {
|
|
||||||
Icon,
|
|
||||||
StudyListExpandedRow,
|
|
||||||
Button,
|
|
||||||
NavBar,
|
|
||||||
Svg,
|
|
||||||
IconButton,
|
|
||||||
EmptyStudies,
|
|
||||||
StudyListTable,
|
|
||||||
StudyListPagination,
|
|
||||||
StudyListFilter
|
|
||||||
} from '@ohif/ui';
|
|
||||||
import utils from '../../utils';
|
|
||||||
|
|
||||||
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
# Study List
|
|
||||||
|
|
||||||
This example shows you how you can build a Study List page using the available
|
|
||||||
components.
|
|
||||||
|
|
||||||
```jsx live
|
|
||||||
{() => {
|
|
||||||
const defaultFilterValues = {
|
|
||||||
patientName: '',
|
|
||||||
mrn: '',
|
|
||||||
studyDate: {
|
|
||||||
startDate: null,
|
|
||||||
endDate: null,
|
|
||||||
},
|
|
||||||
description: '',
|
|
||||||
modality: undefined,
|
|
||||||
accession: '',
|
|
||||||
sortBy: '',
|
|
||||||
sortDirection: 'none',
|
|
||||||
page: 0,
|
|
||||||
resultsPerPage: 25,
|
|
||||||
};
|
|
||||||
const [filterValues, setFilterValues] = useState(defaultFilterValues);
|
|
||||||
const [studies, setStudies] = useState([]);
|
|
||||||
const numOfStudies = studies.length;
|
|
||||||
const [expandedRows, setExpandedRows] = useState([]);
|
|
||||||
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 isFiltering = (filterValues, defaultFilterValues) => {
|
|
||||||
return Object.keys(defaultFilterValues).some((name) => {
|
|
||||||
return filterValues[name] !== defaultFilterValues[name];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
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="group-layers"
|
|
||||||
className={classnames('inline-flex mr-2 w-4', {
|
|
||||||
'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 handleStudyList = (number) => {
|
|
||||||
const studies = utils.getMockedStudies(number);
|
|
||||||
setStudies(studies);
|
|
||||||
setCurrentPage(1);
|
|
||||||
};
|
|
||||||
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>
|
|
||||||
<div className="bg-white p-4">
|
|
||||||
<Button className="mr-2" onClick={() => handleStudyList(0)}>
|
|
||||||
0 Studies
|
|
||||||
</Button>
|
|
||||||
<Button className="mr-2" onClick={() => handleStudyList(50)}>
|
|
||||||
50 Studies
|
|
||||||
</Button>
|
|
||||||
<Button className="mr-2" onClick={() => handleStudyList(100)}>
|
|
||||||
100 Studies
|
|
||||||
</Button>
|
|
||||||
<Button className="mr-2" onClick={() => handleStudyList(1000)}>
|
|
||||||
1000 Studies
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={classnames('bg-black h-full', {
|
|
||||||
'h-screen': !hasStudies,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<NavBar className="justify-between border-b-4 border-black" isSticky>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<div className="mx-3">
|
|
||||||
<Svg name="logo-ohif" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<span className="mr-3 text-common-light text-lg">
|
|
||||||
FOR INVESTIGATIONAL USE ONLY
|
|
||||||
</span>
|
|
||||||
<IconButton
|
|
||||||
variant="text"
|
|
||||||
color="inherit"
|
|
||||||
className="text-primary-active"
|
|
||||||
onClick={() => {}}
|
|
||||||
>
|
|
||||||
<React.Fragment>
|
|
||||||
<Icon name="settings" />
|
|
||||||
<Icon name="chevron-down" />
|
|
||||||
</React.Fragment>
|
|
||||||
</IconButton>
|
|
||||||
</div>
|
|
||||||
</NavBar>
|
|
||||||
<StudyListFilter
|
|
||||||
numOfStudies={numOfStudies}
|
|
||||||
filtersMeta={filtersMeta}
|
|
||||||
filterValues={filterValues}
|
|
||||||
onChange={setFilterValues}
|
|
||||||
clearFilters={() => setFilterValues(defaultFilterValues)}
|
|
||||||
isFiltering={isFiltering(filterValues, defaultFilterValues)}
|
|
||||||
/>
|
|
||||||
{hasStudies ? (
|
|
||||||
<>
|
|
||||||
<StudyListTable
|
|
||||||
tableDataSource={tableDataSource.slice(
|
|
||||||
(currentPage - 1) * perPage,
|
|
||||||
(currentPage - 1) * perPage + perPage
|
|
||||||
)}
|
|
||||||
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>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
```
|
|
||||||
@ -2,23 +2,22 @@
|
|||||||
sidebar_position: 2
|
sidebar_position: 2
|
||||||
sidebar_label: Theming
|
sidebar_label: Theming
|
||||||
---
|
---
|
||||||
|
|
||||||
# Viewer: Theming
|
# Viewer: Theming
|
||||||
|
|
||||||
|
`OHIF-v3` has introduced the
|
||||||
`OHIF-v3` has introduced the [`LayoutTemplateModule`](../extensions/modules/layout-template.md) which enables addition of custom layouts. You can easily design your custom components inside an extension and consume it via the layoutTemplate module you write.
|
[`LayoutTemplateModule`](./extensions/modules/layout-template.md) which enables
|
||||||
|
addition of custom layouts. You can easily design your custom components inside
|
||||||
|
an extension and consume it via the layoutTemplate module you write.
|
||||||
|
|
||||||
|
|
||||||
## Tailwind CSS
|
## Tailwind CSS
|
||||||
[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for creating custom user interfaces.
|
|
||||||
|
|
||||||
|
|
||||||
Below you can see a compiled version of the tailwind configs.
|
|
||||||
Each section can be edited accordingly. For instance screen size break points, primary
|
|
||||||
and secondary colors, etc.
|
|
||||||
|
|
||||||
|
[Tailwind CSS](https://tailwindcss.com/) is a utility-first CSS framework for
|
||||||
|
creating custom user interfaces.
|
||||||
|
|
||||||
|
Below you can see a compiled version of the tailwind configs. Each section can
|
||||||
|
be edited accordingly. For instance screen size break points, primary and
|
||||||
|
secondary colors, etc.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -79,13 +78,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
You can also use the color variable like before. For instance:
|
You can also use the color variable like before. For instance:
|
||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
primary: {
|
primary: {
|
||||||
default: ‘var(--default-color)‘,
|
default: ‘var(--default-color)‘,
|
||||||
@ -96,19 +93,22 @@ primary: {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## White Labeling
|
## White Labeling
|
||||||
|
|
||||||
A white-label product is a product or service produced by one company (the producer) that other companies (the marketers) rebrand to make it appear as if they had made it - [Wikipedia: White-Label Product](https://en.wikipedia.org/wiki/White-label_product)
|
A white-label product is a product or service produced by one company (the
|
||||||
|
producer) that other companies (the marketers) rebrand to make it appear as if
|
||||||
|
they had made it -
|
||||||
|
[Wikipedia: White-Label Product](https://en.wikipedia.org/wiki/White-label_product)
|
||||||
|
|
||||||
Current white-labeling options are limited.
|
Current white-labeling options are limited. We expose the ability to replace the
|
||||||
We expose the ability to replace the "Logo" section of the application with a custom "Logo" component. You can do this by adding a whiteLabeling key to your configuration file.
|
"Logo" section of the application with a custom "Logo" component. You can do
|
||||||
|
this by adding a whiteLabeling key to your configuration file.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.config = {
|
window.config = {
|
||||||
/** .. **/
|
/** .. **/
|
||||||
whiteLabeling: {
|
whiteLabeling: {
|
||||||
createLogoComponentFn: function (React) {
|
createLogoComponentFn: function(React) {
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
'a',
|
'a',
|
||||||
{
|
{
|
||||||
@ -118,24 +118,22 @@ window.config = {
|
|||||||
href: 'http://radicalimaging.com',
|
href: 'http://radicalimaging.com',
|
||||||
},
|
},
|
||||||
React.createElement('h5', {}, 'RADICAL IMAGING')
|
React.createElement('h5', {}, 'RADICAL IMAGING')
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/** .. **/
|
/** .. **/
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
> You can simply use the stylings from tailwind CSS in the whiteLabeling
|
> You can simply use the stylings from tailwind CSS in the whiteLabeling
|
||||||
|
|
||||||
|
|
||||||
In addition to text, you can also add your custom logo
|
In addition to text, you can also add your custom logo
|
||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.config = {
|
window.config = {
|
||||||
/** .. **/
|
/** .. **/
|
||||||
whiteLabeling: {
|
whiteLabeling: {
|
||||||
createLogoComponentFn: function (React) {
|
createLogoComponentFn: function(React) {
|
||||||
return React.createElement(
|
return React.createElement(
|
||||||
'a',
|
'a',
|
||||||
{
|
{
|
||||||
@ -148,20 +146,17 @@ window.config = {
|
|||||||
src: './customLogo.svg',
|
src: './customLogo.svg',
|
||||||
// className: 'w-8 h-8',
|
// className: 'w-8 h-8',
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/** .. **/
|
/** .. **/
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
The output will look like
|
The output will look like
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Links
|
Links
|
||||||
-->
|
-->
|
||||||
|
|||||||
@ -35,6 +35,7 @@
|
|||||||
"@mdx-js/react": "^1.6.21",
|
"@mdx-js/react": "^1.6.21",
|
||||||
"@ohif/ui": "^2.0.0",
|
"@ohif/ui": "^2.0.0",
|
||||||
"@svgr/webpack": "^5.5.0",
|
"@svgr/webpack": "^5.5.0",
|
||||||
|
"classnames": "^2.3.1",
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
"plugin-image-zoom": "ataft/plugin-image-zoom",
|
"plugin-image-zoom": "ataft/plugin-image-zoom",
|
||||||
|
|||||||
@ -6,13 +6,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import moment from 'moment';
|
||||||
import * as ui from '@ohif/ui';
|
import * as ui from '@ohif/ui';
|
||||||
|
import utils from '../../utils/';
|
||||||
|
|
||||||
// Add react-live imports you need here
|
// Add react-live imports you need here
|
||||||
const ReactLiveScope = {
|
const ReactLiveScope = {
|
||||||
React,
|
React,
|
||||||
...React,
|
...React,
|
||||||
...ui,
|
...ui,
|
||||||
|
classnames,
|
||||||
|
utils,
|
||||||
|
moment,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ReactLiveScope;
|
export default ReactLiveScope;
|
||||||
|
|||||||
17
platform/docs/src/utils/getMockedStudies.js
Normal file
17
platform/docs/src/utils/getMockedStudies.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import studyListMock from '../mocks/studyList';
|
||||||
|
|
||||||
|
/** 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;
|
||||||
7
platform/docs/src/utils/index.js
Normal file
7
platform/docs/src/utils/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import getMockedStudies from './getMockedStudies';
|
||||||
|
|
||||||
|
const utils = { getMockedStudies };
|
||||||
|
|
||||||
|
export { getMockedStudies };
|
||||||
|
|
||||||
|
export default utils;
|
||||||
@ -56,7 +56,7 @@ HotkeyField.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
modifierKeys: PropTypes.array,
|
modifierKeys: PropTypes.array,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
hotkeys: PropTypes.object({
|
hotkeys: PropTypes.shape({
|
||||||
initialize: PropTypes.func.isRequired,
|
initialize: PropTypes.func.isRequired,
|
||||||
pause: PropTypes.func.isRequired,
|
pause: PropTypes.func.isRequired,
|
||||||
unpause: PropTypes.func.isRequired,
|
unpause: PropTypes.func.isRequired,
|
||||||
|
|||||||
@ -107,7 +107,7 @@ HotkeysPreferences.propTypes = {
|
|||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
hotkeyDefinitions: PropTypes.object.isRequired,
|
hotkeyDefinitions: PropTypes.object.isRequired,
|
||||||
hotkeysModule: PropTypes.object({
|
hotkeysModule: PropTypes.shape({
|
||||||
initialize: PropTypes.func.isRequired,
|
initialize: PropTypes.func.isRequired,
|
||||||
pause: PropTypes.func.isRequired,
|
pause: PropTypes.func.isRequired,
|
||||||
unpause: PropTypes.func.isRequired,
|
unpause: PropTypes.func.isRequired,
|
||||||
|
|||||||
@ -126,7 +126,7 @@ UserPreferences.propTypes = {
|
|||||||
onCancel: PropTypes.func,
|
onCancel: PropTypes.func,
|
||||||
onSubmit: PropTypes.func,
|
onSubmit: PropTypes.func,
|
||||||
onReset: PropTypes.func,
|
onReset: PropTypes.func,
|
||||||
hotkeysModule: PropTypes.object({
|
hotkeysModule: PropTypes.shape({
|
||||||
initialize: PropTypes.func.isRequired,
|
initialize: PropTypes.func.isRequired,
|
||||||
pause: PropTypes.func.isRequired,
|
pause: PropTypes.func.isRequired,
|
||||||
unpause: PropTypes.func.isRequired,
|
unpause: PropTypes.func.isRequired,
|
||||||
|
|||||||
23
yarn.lock
23
yarn.lock
@ -1114,13 +1114,27 @@
|
|||||||
core-js-pure "^3.15.0"
|
core-js-pure "^3.15.0"
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@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":
|
"@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":
|
||||||
version "7.7.6"
|
version "7.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.6.tgz#d18c511121aff1b4f2cd1d452f1bac9601dd830f"
|
||||||
integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
|
integrity sha512-BWAJxpNVa0QlE5gZdWjSxXtemZyZ9RmrmVozxt3NUXeZhVIJ5ANyqmMc0JDrivBZyxUuQvFxlvH4OWWOogGfUw==
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.2"
|
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.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.8.tgz#7119a56f421018852694290b9f9148097391b446"
|
||||||
|
integrity sha512-twj3L8Og5SaCRCErB4x4ajbvBIVV77CGeFglHpeg5WC5FF8TZzBWXtTJ4MqaD9QszLYTtr+IsaAL2rEUevb+eg==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/template@^7.12.7", "@babel/template@^7.14.5", "@babel/template@^7.4.0":
|
"@babel/template@^7.12.7", "@babel/template@^7.14.5", "@babel/template@^7.4.0":
|
||||||
version "7.14.5"
|
version "7.14.5"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
|
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
|
||||||
@ -5450,7 +5464,7 @@ classnames@2.2.6:
|
|||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||||
|
|
||||||
classnames@^2.2.5, classnames@^2.2.6:
|
classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
|
||||||
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||||
@ -15590,6 +15604,11 @@ regenerate@^1.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
|
||||||
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
|
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:
|
regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4:
|
||||||
version "0.13.7"
|
version "0.13.7"
|
||||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
|
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user