Try to get views to work for component library

This commit is contained in:
Erik Ziegler 2021-07-21 13:16:50 +02:00
parent ff28a2264b
commit ca7506c5f0
19 changed files with 694 additions and 382 deletions

View 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;

View 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();
}

View 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();
}

View File

@ -61,7 +61,7 @@ window.config = {
> data!
>
> 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
inject dependencies like external services:

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](../../configuration/index.md).
[configuration essentials guide](../configuration/index.md).
## Next Steps

View File

@ -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>
);
}
```

View File

@ -0,0 +1,4 @@
{
"label": "Example Views",
"position": 6
}

View File

@ -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>
);
}}
```

View File

@ -2,23 +2,22 @@
sidebar_position: 2
sidebar_label: Theming
---
# Viewer: Theming
`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.
`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.
## 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
module.exports = {
@ -79,13 +78,11 @@ module.exports = {
},
},
},
}
};
```
You can also use the color variable like before. For instance:
```js
primary: {
default: var(--default-color),
@ -96,19 +93,22 @@ primary: {
}
```
## 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.
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.
Current white-labeling options are limited. 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.
```js
window.config = {
/** .. **/
whiteLabeling: {
createLogoComponentFn: function (React) {
createLogoComponentFn: function(React) {
return React.createElement(
'a',
{
@ -118,24 +118,22 @@ window.config = {
href: 'http://radicalimaging.com',
},
React.createElement('h5', {}, 'RADICAL IMAGING')
)
);
},
},
/** .. **/
}
};
```
> You can simply use the stylings from tailwind CSS in the whiteLabeling
In addition to text, you can also add your custom logo
```js
window.config = {
/** .. **/
whiteLabeling: {
createLogoComponentFn: function (React) {
createLogoComponentFn: function(React) {
return React.createElement(
'a',
{
@ -148,20 +146,17 @@ window.config = {
src: './customLogo.svg',
// className: 'w-8 h-8',
})
)
);
},
},
/** .. **/
}
};
```
The output will look like
![custom-logo](../assets/img/custom-logo.png)
<!--
Links
-->

View File

@ -35,6 +35,7 @@
"@mdx-js/react": "^1.6.21",
"@ohif/ui": "^2.0.0",
"@svgr/webpack": "^5.5.0",
"classnames": "^2.3.1",
"clsx": "^1.1.1",
"file-loader": "^6.2.0",
"plugin-image-zoom": "ataft/plugin-image-zoom",

View File

@ -6,13 +6,19 @@
*/
import React from 'react';
import classnames from 'classnames';
import moment from 'moment';
import * as ui from '@ohif/ui';
import utils from '../../utils/';
// Add react-live imports you need here
const ReactLiveScope = {
React,
...React,
...ui,
classnames,
utils,
moment,
};
export default ReactLiveScope;

View 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;

View File

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

View File

@ -56,7 +56,7 @@ HotkeyField.propTypes = {
className: PropTypes.string,
modifierKeys: PropTypes.array,
disabled: PropTypes.bool,
hotkeys: PropTypes.object({
hotkeys: PropTypes.shape({
initialize: PropTypes.func.isRequired,
pause: PropTypes.func.isRequired,
unpause: PropTypes.func.isRequired,

View File

@ -107,7 +107,7 @@ HotkeysPreferences.propTypes = {
onChange: PropTypes.func,
disabled: PropTypes.bool,
hotkeyDefinitions: PropTypes.object.isRequired,
hotkeysModule: PropTypes.object({
hotkeysModule: PropTypes.shape({
initialize: PropTypes.func.isRequired,
pause: PropTypes.func.isRequired,
unpause: PropTypes.func.isRequired,

View File

@ -126,7 +126,7 @@ UserPreferences.propTypes = {
onCancel: PropTypes.func,
onSubmit: PropTypes.func,
onReset: PropTypes.func,
hotkeysModule: PropTypes.object({
hotkeysModule: PropTypes.shape({
initialize: PropTypes.func.isRequired,
pause: PropTypes.func.isRequired,
unpause: PropTypes.func.isRequired,

View File

@ -1114,13 +1114,27 @@
core-js-pure "^3.15.0"
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"
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.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":
version "7.14.5"
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"
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"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e"
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"
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"