chore: Data Mocking for Study List (#1515)
* feat: Data mocking for studies * feat: Data Mocking for Study List * fix empty study position * rename variables for numOfStudies * fix studies query * remove alert :| * minor refactor * split utils and refactoring * refactoring -- format, clean up icons, mdx mock
This commit is contained in:
parent
2138565990
commit
2bf5486112
@ -1,3 +1,5 @@
|
|||||||
|
import * as utils from './src/utils/';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Button,
|
Button,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
@ -9,4 +11,6 @@ export {
|
|||||||
Typography,
|
Typography,
|
||||||
} from './src/components';
|
} from './src/components';
|
||||||
|
|
||||||
|
export { utils };
|
||||||
|
|
||||||
export { StudyList } from './src/views';
|
export { StudyList } from './src/views';
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
|
"date-fns": "^2.10.0",
|
||||||
"docz": "^2.2.0",
|
"docz": "^2.2.0",
|
||||||
"theme-ui": "^0.2.38",
|
"theme-ui": "^0.2.38",
|
||||||
"gatsby": "2.19.24",
|
"gatsby": "2.19.24",
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="20" viewBox="0 0 21 20">
|
|
||||||
<g fill="#348CFD">
|
|
||||||
<path d="M14.75.917c.693 0 1.263.528 1.327 1.205l.006.128v4.167c0 .276-.224.5-.5.5-.245 0-.45-.177-.492-.41l-.008-.09V2.25c0-.161-.114-.295-.266-.327l-.067-.006H2.25c-.161 0-.295.114-.327.266l-.006.067v12.5c0 .161.114.295.266.327l.067.006h4.167c.276 0 .5.224.5.5 0 .246-.177.45-.41.492l-.09.008H2.25c-.693 0-1.263-.528-1.327-1.205l-.006-.128V2.25c0-.693.528-1.263 1.205-1.327L2.25.917h12.5z" transform="translate(-.5 -1)"/>
|
|
||||||
<path d="M19.583 5.917H7.417c-.829 0-1.5.671-1.5 1.5v12.166c0 .829.671 1.5 1.5 1.5h12.166c.829 0 1.5-.671 1.5-1.5V7.417c0-.829-.671-1.5-1.5-1.5zm-12.166 1h12.166c.276 0 .5.224.5.5v12.166c0 .276-.224.5-.5.5H7.417c-.276 0-.5-.224-.5-.5V7.417c0-.276.224-.5.5-.5z" transform="translate(-.5 -1)"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 856 B |
7
platform/ui/src/assets/icons/magnifier.svg
Normal file
7
platform/ui/src/assets/icons/magnifier.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="111" height="111" viewBox="0 0 111 111">
|
||||||
|
<g fill="none" fill-rule="evenodd" stroke="#3A3F99" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" transform="translate(2 2)">
|
||||||
|
<circle cx="53.419" cy="53.419" r="53.419" fill="#06081D"/>
|
||||||
|
<circle cx="49.411" cy="49.411" r="23.862"/>
|
||||||
|
<path d="M66.282 66.282L81.29 81.29"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 861 B After Width: | Height: | Size: 861 B |
|
Before Width: | Height: | Size: 849 B After Width: | Height: | Size: 849 B |
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
// Icons
|
// Icons
|
||||||
import active from './../../assets/icons/active.svg';
|
import seriesActive from './../../assets/icons/series-active.svg';
|
||||||
|
import seriesInactive from './../../assets/icons/series-inactive.svg';
|
||||||
import cancel from './../../assets/icons/cancel.svg';
|
import cancel from './../../assets/icons/cancel.svg';
|
||||||
import chevronDown from './../../assets/icons/chevron-down.svg';
|
import chevronDown from './../../assets/icons/chevron-down.svg';
|
||||||
import chevronRight from './../../assets/icons/chevron-right.svg';
|
import chevronRight from './../../assets/icons/chevron-right.svg';
|
||||||
@ -8,6 +9,7 @@ import infoLink from './../../assets/icons/info-link.svg';
|
|||||||
import launchArrow from './../../assets/icons/launch-arrow.svg';
|
import launchArrow from './../../assets/icons/launch-arrow.svg';
|
||||||
import launchInfo from './../../assets/icons/launch-info.svg';
|
import launchInfo from './../../assets/icons/launch-info.svg';
|
||||||
import logoOhifSmall from './../../assets/icons/logo-ohif-small.svg';
|
import logoOhifSmall from './../../assets/icons/logo-ohif-small.svg';
|
||||||
|
import magnifier from './../../assets/icons/magnifier.svg';
|
||||||
import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg';
|
import notificationwarningDiamond from './../../assets/icons/notificationwarning-diamond.svg';
|
||||||
import settings from './../../assets/icons/settings.svg';
|
import settings from './../../assets/icons/settings.svg';
|
||||||
import sorting from './../../assets/icons/sorting.svg';
|
import sorting from './../../assets/icons/sorting.svg';
|
||||||
@ -15,7 +17,8 @@ import sortingActiveDown from './../../assets/icons/sorting-active-down.svg';
|
|||||||
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
import sortingActiveUp from './../../assets/icons/sorting-active-up.svg';
|
||||||
|
|
||||||
const ICONS = {
|
const ICONS = {
|
||||||
active: active,
|
'series-active': seriesActive,
|
||||||
|
'series-inactive': seriesInactive,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
'chevron-down': chevronDown,
|
'chevron-down': chevronDown,
|
||||||
'chevron-right': chevronRight,
|
'chevron-right': chevronRight,
|
||||||
@ -23,6 +26,7 @@ const ICONS = {
|
|||||||
'launch-arrow': launchArrow,
|
'launch-arrow': launchArrow,
|
||||||
'launch-info': launchInfo,
|
'launch-info': launchInfo,
|
||||||
'logo-ohif-small': logoOhifSmall,
|
'logo-ohif-small': logoOhifSmall,
|
||||||
|
magnifier: magnifier,
|
||||||
'notificationwarning-diamond': notificationwarningDiamond,
|
'notificationwarning-diamond': notificationwarningDiamond,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
'sorting-active-down': sortingActiveDown,
|
'sorting-active-down': sortingActiveDown,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { ICONS } from './getIcon';
|
|||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<Icon name="active" className="text-custom-aquaBright m-4" />
|
<Icon name="series-active" className="text-custom-aquaBright m-4" />
|
||||||
</div>
|
</div>
|
||||||
</Playground>
|
</Playground>
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,13 @@
|
|||||||
{
|
{
|
||||||
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
|
"StudyInstanceUID": "1.2.840.113619.2.5.1762583153.215519.978957063.78",
|
||||||
"StudyDescription": "BRAIN SELLA",
|
"StudyDescription": "BRAIN SELLA",
|
||||||
|
"AccessionNumber": "11788761116031",
|
||||||
"StudyDate": "20010108",
|
"StudyDate": "20010108",
|
||||||
"StudyTime": "120022",
|
"StudyTime": "120022",
|
||||||
"PatientName": "MISTER^MR",
|
"PatientName": "MISTER^MR",
|
||||||
"PatientId": "832040",
|
"PatientId": "832040",
|
||||||
|
"Instances": 33,
|
||||||
|
"Modalities": "MR",
|
||||||
"series": [
|
"series": [
|
||||||
{
|
{
|
||||||
"SeriesDescription": "SAG T-1",
|
"SeriesDescription": "SAG T-1",
|
||||||
|
|||||||
@ -1,270 +0,0 @@
|
|||||||
/* eslint-disable no-use-before-define */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a number whose value is limited to the given range.
|
|
||||||
*
|
|
||||||
* @param {number} value The value to be clamped
|
|
||||||
* @param {number} min The lower boundary of the output range
|
|
||||||
* @param {number} max The upper boundary of the output range
|
|
||||||
* @returns {number} A number in the range [min, max]
|
|
||||||
*/
|
|
||||||
function clamp(value, min = 0, max = 1) {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
if (value < min || value > max) {
|
|
||||||
console.error(
|
|
||||||
`@ohif/ui: the value provided ${value} is out of range [${min}, ${max}].`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.min(Math.max(min, value), max);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a color from CSS hex format to CSS rgb format.
|
|
||||||
*
|
|
||||||
* @param {string} color - Hex color, i.e. #nnn or #nnnnnn
|
|
||||||
* @returns {string} A CSS rgb color string
|
|
||||||
*/
|
|
||||||
export function hexToRgb(color) {
|
|
||||||
color = color.substr(1);
|
|
||||||
|
|
||||||
const re = new RegExp(`.{1,${color.length / 3}}`, 'g');
|
|
||||||
let colors = color.match(re);
|
|
||||||
|
|
||||||
if (colors && colors[0].length === 1) {
|
|
||||||
colors = colors.map(n => n + n);
|
|
||||||
}
|
|
||||||
|
|
||||||
return colors ? `rgb(${colors.map(n => parseInt(n, 16)).join(', ')})` : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function intToHex(int) {
|
|
||||||
const hex = int.toString(16);
|
|
||||||
return hex.length === 1 ? `0${hex}` : hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a color from CSS rgb format to CSS hex format.
|
|
||||||
*
|
|
||||||
* @param {string} color - RGB color, i.e. rgb(n, n, n)
|
|
||||||
* @returns {string} A CSS rgb color string, i.e. #nnnnnn
|
|
||||||
*/
|
|
||||||
export function rgbToHex(color) {
|
|
||||||
// Idempotent
|
|
||||||
if (color.indexOf('#') === 0) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { values } = decomposeColor(color);
|
|
||||||
return `#${values.map(n => intToHex(n)).join('')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a color from hsl format to rgb format.
|
|
||||||
*
|
|
||||||
* @param {string} color - HSL color values
|
|
||||||
* @returns {string} rgb color values
|
|
||||||
*/
|
|
||||||
export function hslToRgb(color) {
|
|
||||||
color = decomposeColor(color);
|
|
||||||
const { values } = color;
|
|
||||||
const h = values[0];
|
|
||||||
const s = values[1] / 100;
|
|
||||||
const l = values[2] / 100;
|
|
||||||
const a = s * Math.min(l, 1 - l);
|
|
||||||
const f = (n, k = (n + h / 30) % 12) =>
|
|
||||||
l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
||||||
|
|
||||||
let type = 'rgb';
|
|
||||||
const rgb = [
|
|
||||||
Math.round(f(0) * 255),
|
|
||||||
Math.round(f(8) * 255),
|
|
||||||
Math.round(f(4) * 255),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (color.type === 'hsla') {
|
|
||||||
type += 'a';
|
|
||||||
rgb.push(values[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return recomposeColor({ type, values: rgb });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an object with the type and values of a color.
|
|
||||||
*
|
|
||||||
* Note: Does not support rgb % values.
|
|
||||||
*
|
|
||||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @returns {object} - A MUI color object: {type: string, values: number[]}
|
|
||||||
*/
|
|
||||||
export function decomposeColor(color) {
|
|
||||||
// Idempotent
|
|
||||||
if (color.type) {
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (color.charAt(0) === '#') {
|
|
||||||
return decomposeColor(hexToRgb(color));
|
|
||||||
}
|
|
||||||
|
|
||||||
const marker = color.indexOf('(');
|
|
||||||
const type = color.substring(0, marker);
|
|
||||||
|
|
||||||
if (['rgb', 'rgba', 'hsl', 'hsla'].indexOf(type) === -1) {
|
|
||||||
throw new Error(
|
|
||||||
[
|
|
||||||
`@ohif/ui: unsupported \`${color}\` color.`,
|
|
||||||
'We support the following formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla().',
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let values = color.substring(marker + 1, color.length - 1).split(',');
|
|
||||||
values = values.map(value => parseFloat(value));
|
|
||||||
|
|
||||||
return { type, values };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a color object with type and values to a string.
|
|
||||||
*
|
|
||||||
* @param {object} color - Decomposed color
|
|
||||||
* @param {string} color.type - One of: 'rgb', 'rgba', 'hsl', 'hsla'
|
|
||||||
* @param {array} color.values - [n,n,n] or [n,n,n,n]
|
|
||||||
* @returns {string} A CSS color string
|
|
||||||
*/
|
|
||||||
export function recomposeColor(color) {
|
|
||||||
const { type } = color;
|
|
||||||
let { values } = color;
|
|
||||||
|
|
||||||
if (type.indexOf('rgb') !== -1) {
|
|
||||||
// Only convert the first 3 values to int (i.e. not alpha)
|
|
||||||
values = values.map((n, i) => (i < 3 ? parseInt(n, 10) : n));
|
|
||||||
} else if (type.indexOf('hsl') !== -1) {
|
|
||||||
values[1] = `${values[1]}%`;
|
|
||||||
values[2] = `${values[2]}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${type}(${values.join(', ')})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the contrast ratio between two colors.
|
|
||||||
*
|
|
||||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
|
||||||
*
|
|
||||||
* @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @returns {number} A contrast ratio value in the range 0 - 21.
|
|
||||||
*/
|
|
||||||
export function getContrastRatio(foreground, background) {
|
|
||||||
const lumA = getLuminance(foreground);
|
|
||||||
const lumB = getLuminance(background);
|
|
||||||
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The relative brightness of any point in a color space,
|
|
||||||
* normalized to 0 for darkest black and 1 for lightest white.
|
|
||||||
*
|
|
||||||
* Formula: https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
|
|
||||||
*
|
|
||||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @returns {number} The relative brightness of the color in the range 0 - 1
|
|
||||||
*/
|
|
||||||
export function getLuminance(color) {
|
|
||||||
color = decomposeColor(color);
|
|
||||||
|
|
||||||
let rgb =
|
|
||||||
color.type === 'hsl'
|
|
||||||
? decomposeColor(hslToRgb(color)).values
|
|
||||||
: color.values;
|
|
||||||
rgb = rgb.map(val => {
|
|
||||||
val /= 255; // normalized
|
|
||||||
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Truncate at 3 digits
|
|
||||||
return Number(
|
|
||||||
(0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Darken or lighten a color, depending on its luminance.
|
|
||||||
* Light colors are darkened, dark colors are lightened.
|
|
||||||
*
|
|
||||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @param {number} coefficient=0.15 - multiplier in the range 0 - 1
|
|
||||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
|
||||||
*/
|
|
||||||
export function emphasize(color, coefficient = 0.15) {
|
|
||||||
return getLuminance(color) > 0.5
|
|
||||||
? darken(color, coefficient)
|
|
||||||
: lighten(color, coefficient);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the absolute transparency of a color.
|
|
||||||
* Any existing alpha values are overwritten.
|
|
||||||
*
|
|
||||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @param {number} value - value to set the alpha channel to in the range 0 -1
|
|
||||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
|
||||||
*/
|
|
||||||
export function fade(color, value) {
|
|
||||||
color = decomposeColor(color);
|
|
||||||
value = clamp(value);
|
|
||||||
|
|
||||||
if (color.type === 'rgb' || color.type === 'hsl') {
|
|
||||||
color.type += 'a';
|
|
||||||
}
|
|
||||||
color.values[3] = value;
|
|
||||||
|
|
||||||
return recomposeColor(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Darkens a color.
|
|
||||||
*
|
|
||||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
|
||||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
|
||||||
*/
|
|
||||||
export function darken(color, coefficient) {
|
|
||||||
color = decomposeColor(color);
|
|
||||||
coefficient = clamp(coefficient);
|
|
||||||
|
|
||||||
if (color.type.indexOf('hsl') !== -1) {
|
|
||||||
color.values[2] *= 1 - coefficient;
|
|
||||||
} else if (color.type.indexOf('rgb') !== -1) {
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
color.values[i] *= 1 - coefficient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return recomposeColor(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightens a color.
|
|
||||||
*
|
|
||||||
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
|
|
||||||
* @param {number} coefficient - multiplier in the range 0 - 1
|
|
||||||
* @returns {string} A CSS color string. Hex input values are returned as rgb
|
|
||||||
*/
|
|
||||||
export function lighten(color, coefficient) {
|
|
||||||
color = decomposeColor(color);
|
|
||||||
coefficient = clamp(coefficient);
|
|
||||||
|
|
||||||
if (color.type.indexOf('hsl') !== -1) {
|
|
||||||
color.values[2] += (100 - color.values[2]) * coefficient;
|
|
||||||
} else if (color.type.indexOf('rgb') !== -1) {
|
|
||||||
for (let i = 0; i < 3; i += 1) {
|
|
||||||
color.values[i] += (255 - color.values[i]) * coefficient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return recomposeColor(color);
|
|
||||||
}
|
|
||||||
15
platform/ui/src/utils/getInstances.js
Normal file
15
platform/ui/src/utils/getInstances.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Calculates the number of instances in series
|
||||||
|
* @param {array} series
|
||||||
|
* @returns {number} Number of instances in series
|
||||||
|
*/
|
||||||
|
|
||||||
|
const getInstances = series => {
|
||||||
|
const instances = series.reduce((acc, item) => {
|
||||||
|
return acc + item.instances.length;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getInstances;
|
||||||
17
platform/ui/src/utils/getMockedStudies.js
Normal file
17
platform/ui/src/utils/getMockedStudies.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
20
platform/ui/src/utils/getModalities.js
Normal file
20
platform/ui/src/utils/getModalities.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* Get formatted Modalities
|
||||||
|
* @param {array} series
|
||||||
|
* @returns {string} Formatted modalities
|
||||||
|
*/
|
||||||
|
const getModalities = series => {
|
||||||
|
const modalities = series.reduce((acc, item) => {
|
||||||
|
const { Modality } = item;
|
||||||
|
if (acc.includes(Modality)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.push(Modality);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return modalities.join('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getModalities;
|
||||||
6
platform/ui/src/utils/index.js
Normal file
6
platform/ui/src/utils/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import capitalize from './capitalize';
|
||||||
|
import getMockedStudies from './getMockedStudies';
|
||||||
|
import getModalities from './getModalities';
|
||||||
|
import getInstances from './getInstances';
|
||||||
|
|
||||||
|
export { capitalize, getMockedStudies, getModalities, getInstances };
|
||||||
@ -1,18 +1,38 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import StudyListFilter from './components/StudyListFilter';
|
import StudyListFilter from './components/StudyListFilter';
|
||||||
import StudyListTable from './components/StudyListTable';
|
import StudyListTable from './components/StudyListTable';
|
||||||
import StudyListPagination from './components/StudyListPagination';
|
import StudyListPagination from './components/StudyListPagination';
|
||||||
|
|
||||||
const StudyList = () => {
|
const StudyList = ({ studies, perPage }) => {
|
||||||
|
const studiesData = studies.slice(0, perPage);
|
||||||
|
const numOfStudies = studies.length;
|
||||||
|
const isEmptyStudies = numOfStudies === 0;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
className={classnames('bg-black h-full', {
|
||||||
|
'h-screen': isEmptyStudies,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<Header />
|
<Header />
|
||||||
<StudyListFilter />
|
<StudyListFilter numOfStudies={numOfStudies} />
|
||||||
<StudyListTable />
|
<StudyListTable studies={studiesData} numOfStudies={numOfStudies} />
|
||||||
<StudyListPagination />
|
{!isEmptyStudies && <StudyListPagination />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
StudyList.defaultProps = {
|
||||||
|
studies: [],
|
||||||
|
perPage: 25,
|
||||||
|
};
|
||||||
|
|
||||||
|
StudyList.propTypes = {
|
||||||
|
studies: PropTypes.array,
|
||||||
|
perPage: PropTypes.number,
|
||||||
|
};
|
||||||
|
|
||||||
export default StudyList;
|
export default StudyList;
|
||||||
|
|||||||
@ -4,8 +4,11 @@ menu: Views
|
|||||||
route: views/studyList
|
route: views/studyList
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
import { Playground, Props } from 'docz';
|
import { Playground, Props } from 'docz';
|
||||||
import StudyList from './';
|
import StudyList from './';
|
||||||
|
import Button from '../../components/Button';
|
||||||
|
import { getMockedStudies } from '../../utils/'
|
||||||
|
|
||||||
# StudyList
|
# StudyList
|
||||||
|
|
||||||
@ -20,9 +23,24 @@ import { StudyList } from '@ohfi/ui';
|
|||||||
## StudyList
|
## StudyList
|
||||||
|
|
||||||
<Playground>
|
<Playground>
|
||||||
<>
|
{() => {
|
||||||
<StudyList />
|
const [studies, setStudies] = useState([])
|
||||||
</>
|
const handleStudyList = (number) => {
|
||||||
|
const studies = getMockedStudies(number)
|
||||||
|
setStudies(studies)
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
<StudyList studies={studies} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
</Playground>
|
</Playground>
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|||||||
@ -1,19 +1,24 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Button } from '@ohif/ui';
|
import { format } from 'date-fns';
|
||||||
|
import { Button, Icon, Typography } from '@ohif/ui';
|
||||||
|
|
||||||
/** TODO: Icon component should be used instead of importing the icons directly */
|
const TableRow = props => {
|
||||||
import ChevronRight from '../../../assets/icons/chevron-right.svg';
|
const {
|
||||||
import ChevronDown from '../../../assets/icons/chevron-down.svg';
|
AccessionNumber,
|
||||||
import InstancesActive from '../../../assets/icons/instances-active.svg';
|
Modalities,
|
||||||
import InstancesInactive from '../../../assets/icons/instances-inactive.svg';
|
Instances,
|
||||||
import LaunchInfo from '../../../assets/icons/launch-info.svg';
|
StudyDescription,
|
||||||
|
PatientId,
|
||||||
|
PatientName,
|
||||||
|
StudyDate,
|
||||||
|
series,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const TableRow = () => {
|
|
||||||
const [isOpened, setIsOpened] = useState(false);
|
const [isOpened, setIsOpened] = useState(false);
|
||||||
const toggleRow = () => setIsOpened(!isOpened);
|
const toggleRow = () => setIsOpened(!isOpened);
|
||||||
const ChevronIcon = isOpened ? ChevronDown : ChevronRight;
|
const ChevronIconName = isOpened ? 'chevron-down' : 'chevron-right';
|
||||||
const InstancesIcon = isOpened ? InstancesActive : InstancesInactive;
|
|
||||||
const tdClasses = [
|
const tdClasses = [
|
||||||
'px-4 py-2',
|
'px-4 py-2',
|
||||||
{ 'border-b border-custom-violetPale': !isOpened },
|
{ 'border-b border-custom-violetPale': !isOpened },
|
||||||
@ -23,7 +28,6 @@ const TableRow = () => {
|
|||||||
small: 'px-2 flex-0.3',
|
small: 'px-2 flex-0.3',
|
||||||
};
|
};
|
||||||
const seriesBodyClasses = 'border-r border-custom-violetPale';
|
const seriesBodyClasses = 'border-r border-custom-violetPale';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<tr>
|
<tr>
|
||||||
@ -50,31 +54,39 @@ const TableRow = () => {
|
|||||||
onClick={toggleRow}
|
onClick={toggleRow}
|
||||||
>
|
>
|
||||||
<td className={classnames(...tdClasses)}>
|
<td className={classnames(...tdClasses)}>
|
||||||
<ChevronIcon />
|
<Icon name={ChevronIconName} />
|
||||||
</td>
|
</td>
|
||||||
<td className={classnames(...tdClasses)}>Patient name</td>
|
<td className={classnames(...tdClasses)}>{PatientName}</td>
|
||||||
<td className={classnames(...tdClasses)}>11000002</td>
|
<td className={classnames(...tdClasses)}>{PatientId}</td>
|
||||||
<td className={classnames(...tdClasses)}>
|
<td className={classnames(...tdClasses)}>
|
||||||
Mar-29-2013 11:26 AM
|
{format(StudyDate, 'MMM-DD-YYYY')}
|
||||||
</td>
|
</td>
|
||||||
<td className={classnames(...tdClasses)}>
|
<td className={classnames(...tdClasses)}>
|
||||||
PET^1_PETCT_WB_AC (Adult)
|
{StudyDescription}
|
||||||
</td>
|
</td>
|
||||||
<td className={classnames(...tdClasses)}>CT/OT/PT</td>
|
<td className={classnames(...tdClasses)}>{Modalities}</td>
|
||||||
<td className={classnames(...tdClasses)}>00000001</td>
|
|
||||||
<td className={classnames(...tdClasses)}>
|
<td className={classnames(...tdClasses)}>
|
||||||
<InstancesIcon className="inline-flex mr-2" />
|
{AccessionNumber}
|
||||||
902
|
</td>
|
||||||
|
<td className={classnames(...tdClasses)}>
|
||||||
|
<Icon
|
||||||
|
name="series-active"
|
||||||
|
className={classnames('inline-flex mr-2', {
|
||||||
|
'text-custom-blueBright': isOpened,
|
||||||
|
'text-custom-violetPale': !isOpened,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{Instances}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{isOpened && (
|
{isOpened && (
|
||||||
<tr className={classnames('bg-black')}>
|
<tr className={classnames('bg-black')}>
|
||||||
<td colSpan="8" className="py-4 pl-20 pr-2">
|
<td colSpan="8" className="py-4 pl-20 pr-2">
|
||||||
<div>
|
<div className="flex">
|
||||||
<Button
|
<Button
|
||||||
rounded="full"
|
rounded="full"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
endIcon={<LaunchInfo />}
|
endIcon={<Icon name="launch-info" />}
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
>
|
>
|
||||||
Basic Viewer
|
Basic Viewer
|
||||||
@ -82,7 +94,7 @@ const TableRow = () => {
|
|||||||
<Button
|
<Button
|
||||||
rounded="full"
|
rounded="full"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
endIcon={<LaunchInfo />}
|
endIcon={<Icon name="launch-info" />}
|
||||||
className="mr-4"
|
className="mr-4"
|
||||||
>
|
>
|
||||||
Segmentation
|
Segmentation
|
||||||
@ -90,14 +102,17 @@ const TableRow = () => {
|
|||||||
<Button
|
<Button
|
||||||
rounded="full"
|
rounded="full"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
endIcon={<LaunchInfo />}
|
endIcon={<Icon name="launch-info" />}
|
||||||
>
|
>
|
||||||
Module 3
|
Module 3
|
||||||
</Button>
|
</Button>
|
||||||
<span className="ml-4 text-lg text-custom-grayBright">
|
<div className="ml-5 text-lg text-custom-grayBright flex items-center">
|
||||||
{/* ADD ICON HERE */}
|
<Icon
|
||||||
|
name="notificationwarning-diamond"
|
||||||
|
className="mr-2 w-5 h-5"
|
||||||
|
/>
|
||||||
Feedback text lorem ipsum dolor sit amet
|
Feedback text lorem ipsum dolor sit amet
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="w-full text-lg">
|
<div className="w-full text-lg">
|
||||||
@ -124,7 +139,7 @@ const TableRow = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 h-48 overflow-y-scroll ohif-scrollbar">
|
<div className="mt-2 h-48 overflow-y-scroll ohif-scrollbar">
|
||||||
{new Array(30).fill('').map((el, i) => (
|
{series.map((seriesItem, i) => (
|
||||||
<div className="w-full flex" key={i}>
|
<div className="w-full flex" key={i}>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
@ -140,7 +155,7 @@ const TableRow = () => {
|
|||||||
seriesBodyClasses
|
seriesBodyClasses
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
#
|
{seriesItem.SeriesNumber}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
@ -148,10 +163,10 @@ const TableRow = () => {
|
|||||||
seriesBodyClasses
|
seriesBodyClasses
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
CT
|
{seriesItem.Modality}
|
||||||
</div>
|
</div>
|
||||||
<div className={classnames('pl-3 flex-1')}>
|
<div className={classnames('pl-3 flex-1')}>
|
||||||
149
|
{seriesItem.instances.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -170,27 +185,85 @@ const TableRow = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const StudyListTable = () => {
|
TableRow.propTypes = {
|
||||||
|
AccessionNumber: PropTypes.string.isRequired,
|
||||||
|
Modalities: PropTypes.string.isRequired,
|
||||||
|
Instances: PropTypes.number.isRequired,
|
||||||
|
PatientId: PropTypes.string.isRequired,
|
||||||
|
PatientName: PropTypes.string.isRequired,
|
||||||
|
StudyDescription: PropTypes.string.isRequired,
|
||||||
|
StudyDate: PropTypes.string.isRequired,
|
||||||
|
series: PropTypes.array.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StudyListTable = ({ studies, numOfStudies }) => {
|
||||||
|
const renderTable = () => {
|
||||||
|
return (
|
||||||
|
<table className="w-full text-white">
|
||||||
|
<tbody>
|
||||||
|
{studies.map((study, i) => (
|
||||||
|
<TableRow
|
||||||
|
key={i}
|
||||||
|
AccessionNumber={study.AccessionNumber || ''}
|
||||||
|
Modalities={study.Modalities || ''}
|
||||||
|
Instances={study.Instances || ''}
|
||||||
|
StudyDescription={study.StudyDescription || ''}
|
||||||
|
PatientId={study.PatientId || ''}
|
||||||
|
PatientName={study.PatientName || ''}
|
||||||
|
StudyDate={study.StudyDate || ''}
|
||||||
|
series={study.series || []}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderEmpty = () => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center pt-48">
|
||||||
|
<Icon name="magnifier" className="mb-4" />
|
||||||
|
<Typography className="text-custom-aquaBright" variant="h5">
|
||||||
|
No studies available
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="bg-black">
|
<div className="bg-black">
|
||||||
<div className="container m-auto relative">
|
<div className="container m-auto relative">
|
||||||
|
{numOfStudies > 100 && (
|
||||||
<div className="bg-custom-blue text-center text-base py-1 rounded-b sticky top-0">
|
<div className="bg-custom-blue text-center text-base py-1 rounded-b sticky top-0">
|
||||||
<p className="text-white">
|
<p className="text-white">
|
||||||
Filter list to 100 studies or less to enable sorting
|
Filter list to 100 studies or less to enable sorting
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<table className="w-full text-white">
|
)}
|
||||||
<tbody>
|
|
||||||
{new Array(30).fill('').map((empty, i) => (
|
{numOfStudies > 0 && renderTable()}
|
||||||
<TableRow key={i} />
|
{numOfStudies === 0 && renderEmpty()}
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
StudyListTable.propTypes = {
|
||||||
|
studies: PropTypes.arrayOf(
|
||||||
|
PropTypes.shape({
|
||||||
|
AccessionNumber: PropTypes.string.isRequired,
|
||||||
|
Modalities: PropTypes.string.isRequired,
|
||||||
|
Instances: PropTypes.number.isRequired,
|
||||||
|
PatientId: PropTypes.string.isRequired,
|
||||||
|
PatientName: PropTypes.string.isRequired,
|
||||||
|
StudyDescription: PropTypes.string.isRequired,
|
||||||
|
StudyDate: PropTypes.string.isRequired,
|
||||||
|
series: PropTypes.array.isRequired,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
numOfStudies: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default StudyListTable;
|
export default StudyListTable;
|
||||||
|
|||||||
@ -1,8 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StudyList } from '@ohif/ui';
|
import { StudyList } from '@ohif/ui';
|
||||||
|
|
||||||
|
// TEMPORARY MOCKING DATA FOR VISUALIZATION PURPOSES
|
||||||
|
import { utils } from '@ohif/ui';
|
||||||
|
|
||||||
const ConnectedStudyList = () => {
|
const ConnectedStudyList = () => {
|
||||||
return <StudyList />;
|
const studies = utils.getMockedStudies();
|
||||||
|
return <StudyList studies={studies} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ConnectedStudyList;
|
export default ConnectedStudyList;
|
||||||
|
|||||||
@ -6280,7 +6280,7 @@ date-fns@^1.27.2:
|
|||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||||
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
||||||
|
|
||||||
date-fns@^2.2.1:
|
date-fns@^2.10.0, date-fns@^2.2.1:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.10.0.tgz#abd10604d8bafb0bcbd2ba2e9b0563b922ae4b6b"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.10.0.tgz#abd10604d8bafb0bcbd2ba2e9b0563b922ae4b6b"
|
||||||
integrity sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==
|
integrity sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user