Replace measurementsPanel w/ a format that reduces nesting

This commit is contained in:
dannyrb 2020-06-04 11:28:10 -04:00
parent 98497f666c
commit 95249928a8
26 changed files with 276 additions and 772 deletions

View File

@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, ButtonGroup, Icon, IconButton } from '@ohif/ui';
function ActionButtons() {
return (
<React.Fragment>
<ButtonGroup onClick={() => alert('Export')}>
<Button
className="px-2 py-2 text-base text-white bg-black border-primary-main"
size="initial"
color="inherit"
>
Export
</Button>
<IconButton
className="px-2 text-white bg-black border-primary-main"
color="inherit"
size="initial"
>
<Icon name="arrow-down" />
</IconButton>
</ButtonGroup>
<Button
className="px-2 py-2 ml-2 text-base text-white bg-black border border-primary-main"
variant="outlined"
size="initial"
color="inherit"
onClick={() => alert('Create Report')}
>
Create Report
</Button>
</React.Fragment>
);
}
export default ActionButtons;

View File

@ -1,73 +0,0 @@
import React from 'react';
import {
MeasurementsPanel,
Button,
ButtonGroup,
Icon,
IconButton,
} from '@ohif/ui';
export default function MeasurementTable({ servicesManager, commandsManager }) {
const { MeasurementService } = servicesManager.services;
console.log('MeasurementTable rendering!!!!!!!!!!!!!');
const actionButtons = (
<React.Fragment>
<ButtonGroup onClick={() => alert('Export')}>
<Button
className="text-white border-primary-main bg-black text-base py-2 px-2"
size="initial"
color="inherit"
>
Export
</Button>
<IconButton
className="bg-black border-primary-main px-2 text-white px-2"
color="inherit"
size="initial"
>
<Icon name="arrow-down" />
</IconButton>
</ButtonGroup>
<Button
className="text-white border border-primary-main bg-black text-base py-2 px-2 ml-2"
variant="outlined"
size="initial"
color="inherit"
onClick={() => alert('Create Report')}
>
Create Report
</Button>
</React.Fragment>
);
const descriptionData = {
date: '07-Sep-2010',
modality: 'CT',
description: 'CHEST/ABD/PELVIS W CONTRAST',
};
const activeMeasurementItem = 0;
const measurementTableData = {
title: 'Measurements',
amount: 10,
data: new Array(10).fill({}).map((el, i) => ({
id: i + 1,
label: 'Label short description',
displayText: '24.0 x 24.0 mm (S:4, I:22)',
isActive: activeMeasurementItem === i + 1,
})),
onClick: id => setActiveMeasurementItem(s => (s === id ? null : id)),
onEdit: id => alert(`Edit: ${id}`),
};
return (
<MeasurementsPanel
descriptionData={descriptionData}
measurementTableData={measurementTableData}
actionButtons={actionButtons}
/>
);
}

View File

@ -0,0 +1,55 @@
import React from 'react';
import { StudySummary, MeasurementTable } from '@ohif/ui';
import ActionButtons from './ActionButtons.jsx';
export default function PanelMeasurementTable({
servicesManager,
commandsManager,
}) {
const { MeasurementService } = servicesManager.services;
console.log('MeasurementTable rendering!!!!!!!!!!!!!');
const descriptionData = {
date: '07-Sep-2010',
modality: 'CT',
description: 'CHEST/ABD/PELVIS W CONTRAST',
};
const activeMeasurementItem = 0;
const measurementTableData = {
title: 'Measurements',
amount: 10,
data: new Array(10).fill({}).map((el, i) => ({
id: i + 1,
label: 'Label short description',
displayText: '24.0 x 24.0 mm (S:4, I:22)',
isActive: activeMeasurementItem === i + 1,
})),
onClick: id => setActiveMeasurementItem(s => (s === id ? null : id)),
onEdit: id => alert(`Edit: ${id}`),
};
return (
<>
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
<StudySummary
date={descriptionData.date}
modality={descriptionData.modality}
description={descriptionData.description}
/>
<MeasurementTable
title="Measurements"
amount={measurementTableData.data.length}
data={measurementTableData.data}
onClick={() => {}}
onEdit={id => alert(`Edit: ${id}`)}
/>
</div>
<div className="flex justify-center p-4">
<ActionButtons />
</div>
</>
);
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { WrappedPanelStudyBrowser } from './Panels';
import MeasurementTable from './MeasurementTable.js';
import PanelMeasurementTable from './PanelMeasurementTable.js';
// TODO:
// - No loading UI exists yet
@ -15,7 +15,7 @@ function getPanelModule({
}) {
const wrappedMeasurementPanel = () => {
return (
<MeasurementTable
<PanelMeasurementTable
commandsManager={commandsManager}
servicesManager={servicesManager}
/>

View File

@ -1,8 +0,0 @@
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
};

View File

@ -1,38 +0,0 @@
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
const pkg = require('./../package.json');
const ROOT_DIR = path.join(__dirname, './..');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
module.exports = (env, argv) => {
const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
return merge(commonConfig, {
devtool: 'source-map',
stats: {
colors: true,
hash: true,
timings: true,
assets: true,
chunks: false,
chunkModules: false,
modules: false,
children: false,
warnings: true,
},
optimization: {
minimize: true,
sideEffects: true,
},
output: {
path: ROOT_DIR,
library: 'OHIFExtLesionTracker',
libraryTarget: 'umd',
libraryExport: 'default',
filename: pkg.main,
},
});
};

View File

@ -1,22 +0,0 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.1.0...@ohif/extension-lesion-tracker@0.2.0) (2020-02-10)
### Features
* Lesion tracker right panel ([#1428](https://github.com/OHIF/Viewers/issues/1428)) ([98a649b](https://github.com/OHIF/Viewers/commit/98a649b455ffc712938fc5035cdef40695e58440))
# 0.1.0 (2020-02-06)
### Features
* lesion-tracker extension ([#1420](https://github.com/OHIF/Viewers/issues/1420)) ([73e4409](https://github.com/OHIF/Viewers/commit/73e440968ce4699d081a9c9f2d21dd68095b3056))

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,38 +0,0 @@
# @ohif/extension-lesion-tracker
This project is an OHIF extension that can be used with the Core OHIF Platform,
either at runtime, or as an ES6 dependency, to create a medical image viewing
application similar to that of the [legacy Lesion
Tracker][legacy-lesion-tracker] viewer.
## About
...
## Scope
...
### Configuration
...
### Extensions
...
## Build & Deploy
...
## Funding/Support
...
<!--
LINKS
-->
<!-- prettier-ignore-start -->
[legacy-lesion-tracker]: http://lesiontracker.ohif.org/studylist
<!-- prettier-ignore-end -->

View File

@ -1,39 +0,0 @@
{
"name": "@ohif/extension-lesion-tracker",
"version": "0.2.0",
"description": "OHIF extension for Lesion Tracker",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "src/index.js",
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=10",
"npm": ">=6",
"yarn": ">=1.16.0"
},
"files": [
"dist",
"README.md"
],
"scripts": {
"dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
"dev:lesion-tracker": "yarn run dev",
"build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
"build:package": "yarn run build",
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "^0.50.0",
"prop-types": "^15.6.2",
"react": "^16.11.0",
"react-dom": "^16.11.0"
},
"dependencies": {
"@babel/runtime": "7.7.6",
"classnames": "^2.2.6"
}
}

View File

@ -1,60 +0,0 @@
.MeasurementComparisonTable {
height: 100%;
display: flex;
flex-direction: column;
}
.MeasurementComparisonTable .displayTexts {
display: flex;
}
.MeasurementComparisonTable .displayTexts .measurementDisplayText {
flex: 1;
}
.MeasurementComparisonTable .measurementTableHeader {
display: flex;
position: relative;
padding-left: 38px;
}
.MeasurementComparisonTable .measurementTableHeader .warning-status {
left: 0;
top: 3px;
cursor: pointer;
width: 40px;
height: 100%;
display: flex;
align-items: center;
}
.MeasurementComparisonTable .measurementTableHeader .warning-status .warning-border {
padding: 2px 3px;
width: 30px;
height: 30px;
}
.MeasurementComparisonTable .measurementTableHeader .warning-status svg {
width: 20px;
height: 18px;
}
.MeasurementComparisonTable .measurementTableHeader .measurementTableHeaderItem {
position: relative;
flex: 1;
}
.MeasurementComparisonTable .generate-report {
background-color: #151a1f;
margin-top: 2px;
padding: 10px 0;
display: flex;
justify-content: center;
align-items: center;
}
.MeasurementComparisonTable .generate-report button {
font-size: 12px;
color: black;
background-color: var(--active-color);
}

View File

@ -1,151 +0,0 @@
import React from 'react';
import { MeasurementTable } from '@ohif/ui';
import './MeasurementComparisonTable.css';
const overallWarnings = {
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
};
const measurements = [
{
measurementId: '125',
measurementNumber: '125',
itemNumber: 1,
label: '(No description)',
data: [{ displayText: '12.5 x 4.6' }],
},
{
measurementId: '124',
measurementNumber: '124',
itemNumber: 2,
label: '(No description)',
data: [{ displayText: '32.5 x 1.6' }],
},
{
measurementId: '123',
measurementNumber: '123',
itemNumber: 3,
hasWarnings: true,
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
label: '(No description)',
data: [{ displayText: '5.5 x 9.2' }],
},
];
const additionalFindings = [
{
measurementId: '122',
measurementNumber: '122',
itemNumber: 1,
hasWarnings: true,
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
label: '(No description)',
data: [{ displayText: '23.5 x 9.2' }],
},
{
measurementId: '121',
measurementNumber: '121',
itemNumber: 2,
hasWarnings: true,
warningList: [
'All measurements should have a location',
'Nodal lesions must be >= 15mm short axis AND >= double the acquisition slice thickness by CT and MR',
],
label: '(No description)',
data: [{ displayText: '11.2 x 9.2' }],
},
{
measurementId: '120',
measurementNumber: '120',
itemNumber: 3,
label: '(No description)',
data: [{ displayText: '2.9 x 9.2' }],
},
];
const currentCollections = [
{
selectorAction: () => {},
maxMeasurements: 3,
groupName: 'Measurements',
measurements: measurements,
},
{
selectorAction: () => {},
groupName: 'Additional Findings',
measurements: additionalFindings,
},
];
const comparisonColletions = [
{
selectorAction: () => {},
maxMeasurements: 3,
groupName: 'Measurements',
measurements: measurements,
},
{
selectorAction: () => {},
groupName: 'Additional Findings',
measurements: additionalFindings,
},
];
const comparisonCollections = currentCollections.map((group, index) => {
return {
...group,
measurements: group.measurements.map((measurement, measurementIndex) => {
const comparisonCollection = comparisonColletions[index].measurements;
if (measurementIndex < comparisonCollection.length) {
return {
...measurement,
data: [
...measurement.data,
...comparisonCollection[measurementIndex].data,
],
};
}
}),
};
});
const comparisonTimepoints = [
{
key: 'Current',
date: '10-Apr-18',
},
{
key: 'Comparison',
date: '15-Jun-18',
},
];
const MeasurementComparisonTable = () => {
return (
<div className="MeasurementComparisonTable">
<MeasurementTable
timepoints={comparisonTimepoints}
overallWarnings={overallWarnings}
measurementCollection={comparisonCollections}
onRelabelClick={() => {}}
onEditDescriptionClick={() => {}}
/>
<div className="generate-report">
<button className="btn btn-primary">Generate Report</button>
</div>
</div>
);
};
export default MeasurementComparisonTable;

View File

@ -1,52 +0,0 @@
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
export default {
/**
* Only required property. Should be a unique value across all extensions.
*/
id: 'lesion-tracker',
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
*/
getPanelModule({ servicesManager, commandsManager }) {
return {
menuOptions: [
{
icon: 'th-list',
label: 'Measurements',
target: 'lesion-tracker-panel',
},
],
components: [
{
id: 'lesion-tracker-panel',
component: MeasurementComparisonTable,
},
],
defaultContext: ['VIEWER'],
};
},
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
* @returns Object
*/
getToolbarModule() {
return null;
},
/**
* @param {object} params
* @param {ServicesManager} params.servicesManager
* @param {CommandsManager} params.commandsManager
* @returns Object
*/
getCommandsModule() {
return null;
},
};

View File

@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, ButtonGroup, Icon, IconButton } from '@ohif/ui';
function ActionButtons() {
return (
<React.Fragment>
<ButtonGroup onClick={() => alert('Export')}>
<Button
className="px-2 py-2 text-base text-white bg-black border-primary-main"
size="initial"
color="inherit"
>
Export
</Button>
<IconButton
className="px-2 text-white bg-black border-primary-main"
color="inherit"
size="initial"
>
<Icon name="arrow-down" />
</IconButton>
</ButtonGroup>
<Button
className="px-2 py-2 ml-2 text-base text-white bg-black border border-primary-main"
variant="outlined"
size="initial"
color="inherit"
onClick={() => alert('Create Report')}
>
Create Report
</Button>
</React.Fragment>
);
}
export default ActionButtons;

View File

@ -1,12 +1,8 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import {
MeasurementsPanel,
Button,
ButtonGroup,
Icon,
IconButton,
} from '@ohif/ui';
import { StudySummary, MeasurementTable } from '@ohif/ui';
import { DicomMetadataStore } from '@ohif/core';
import ActionButtons from './ActionButtons';
import { useTrackedMeasurements } from '../../getContextModule';
function PanelMeasurementTableTracking({ servicesManager, commandsManager }) {
@ -15,6 +11,11 @@ function PanelMeasurementTableTracking({ servicesManager, commandsManager }) {
trackedMeasurements,
sendTrackedMeasurementsEvent,
] = useTrackedMeasurements();
const [displayStudySummary, setDisplayStudySummary] = useState({
date: '', // '07-Sep-2010',
modality: '', // 'CT',
description: '', // 'CHEST/ABD/PELVIS W CONTRAST',
});
const [displayMeasurements, setDisplayMeasurements] = useState([]);
// TODO: initial measurements + initial tracked?
@ -24,8 +25,8 @@ function PanelMeasurementTableTracking({ servicesManager, commandsManager }) {
// Initial?
useEffect(() => {
const measurements = MeasurementService.getMeasurements();
const mappedMeasurements = measurements.map(m =>
_mapMeasurementToDisplay(m)
const mappedMeasurements = measurements.map((m, index) =>
_mapMeasurementToDisplay(m, index)
);
setDisplayMeasurements(mappedMeasurements);
// eslint-ignore-next-line
@ -51,93 +52,101 @@ function PanelMeasurementTableTracking({ servicesManager, commandsManager }) {
// return unsubscribe;
}, [MeasurementService, sendTrackedMeasurementsEvent]);
console.log('MeasurementTable rendering!!!!!!!!!!!!!');
const actionButtons = (
<React.Fragment>
<ButtonGroup onClick={() => alert('Export')}>
<Button
className="px-2 py-2 text-base text-white bg-black border-primary-main"
size="initial"
color="inherit"
>
Export
</Button>
<IconButton
className="px-2 text-white bg-black border-primary-main"
color="inherit"
size="initial"
>
<Icon name="arrow-down" />
</IconButton>
</ButtonGroup>
<Button
className="px-2 py-2 ml-2 text-base text-white bg-black border border-primary-main"
variant="outlined"
size="initial"
color="inherit"
onClick={() => alert('Create Report')}
>
Create Report
</Button>
</React.Fragment>
);
const descriptionData = {
date: '07-Sep-2010',
modality: 'CT',
description: 'CHEST/ABD/PELVIS W CONTRAST',
};
const activeMeasurementItem = 0;
const measurementTableData = {
title: 'Measurements',
amount: displayMeasurements.length,
data: displayMeasurements,
// new Array(10).fill({}).map((el, i) => ({
// id: i + 1,
// label: 'Label short description',
// displayText: '24.0 x 24.0 mm (S:4, I:22)',
// isActive: activeMeasurementItem === i + 1,
// })),
// onClick: id => setActiveMeasurementItem(s => (s === id ? null : id)),
onClick: () => {},
onEdit: id => alert(`Edit: ${id}`),
};
return (
<MeasurementsPanel
descriptionData={descriptionData}
measurementTableData={measurementTableData}
actionButtons={actionButtons}
/>
<>
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
<StudySummary
date={displayStudySummary.date}
modality={displayStudySummary.modality}
description={displayStudySummary.description}
/>
<MeasurementTable
title="Measurements"
amount={displayMeasurements.length}
data={displayMeasurements}
onClick={() => {}}
onEdit={id => alert(`Edit: ${id}`)}
/>
</div>
<div className="flex justify-center p-4">
<ActionButtons />
</div>
</>
);
}
PanelMeasurementTableTracking.propTypes = {};
// 'id',
// 'SOPInstanceUID',
// 'FrameOfReferenceUID',
// 'referenceStudyUID',
// 'referenceSeriesUID',
// 'label',
// 'description',
// 'type',
// 'unit',
// 'area', // TODO: Add concept names instead (descriptor)
// 'points',
// 'source',
function _mapMeasurementToDisplay(measurement) {
const { id, label, description: displayText } = measurement;
return {
// TODO: This could be a MeasurementService mapper
function _mapMeasurementToDisplay(measurement, index) {
const {
id,
label, // 'Label short description',
displayText, // '24.0 x 24.0 mm (S:4, I:22)',
label,
description,
// Reference IDs
referenceStudyUID,
referenceSeriesUID,
SOPInstanceUID,
} = measurement;
const instance = DicomMetadataStore.getInstance(
referenceStudyUID,
referenceSeriesUID,
SOPInstanceUID
);
const { PixelSpacing, SeriesNumber, InstanceNumber } = instance;
console.log('mapping....', measurement);
console.log(instance);
return {
id: index + 1,
label: '(empty)', // 'Label short description',
displayText: _getDisplayText(
measurement.points,
PixelSpacing,
SeriesNumber,
InstanceNumber
),
// TODO: handle one layer down
isActive: false, // activeMeasurementItem === i + 1,
};
}
/**
*
* @param {*} points
* @param {*} pixelSpacing
*/
function _getDisplayText(points, pixelSpacing, seriesNumber, instanceNumber) {
// TODO: determination of shape influences text
// Length: 'xx.x unit (S:x, I:x)'
// Rectangle: 'xx.x x xx.x unit (S:x, I:x)',
// Ellipse?
// Bidirectional?
// Freehand?
const hasPixelSpacing =
pixelSpacing !== undefined &&
Array.isArray(pixelSpacing) &&
pixelSpacing.length === 2;
const [rowPixelSpacing, colPixelSpacing] = hasPixelSpacing
? pixelSpacing
: [1, 1];
const unit = hasPixelSpacing ? 'mm' : 'px';
const { x: x1, y: y1 } = points[0];
const { x: x2, y: y2 } = points[1];
const dx = x2 - x1 * colPixelSpacing;
const dy = y2 - y1 * rowPixelSpacing;
const length = _round(Math.sqrt(dx * dx + dy * dy), 1);
return `${length} ${unit} (S:${seriesNumber}, I:${instanceNumber})`;
}
function _round(value, decimals) {
return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
}
export default PanelMeasurementTableTracking;

View File

@ -16,14 +16,12 @@
"scripts": {
"cm": "npx git-cz",
"build": "lerna run build:viewer --stream",
"build:lt": "lerna run build:viewer:lesion-tracker --stream",
"build:ci": "lerna run build:viewer:ci --stream",
"build:ui:deploy-preview": "lerna run build:ui:deploy-preview --stream",
"build:demo": "lerna run build:viewer:demo --stream",
"build:package": "lerna run build:viewer:package --stream",
"build:package-all": "lerna run build:package --parallel --stream",
"dev": "lerna run dev:viewer --stream",
"dev:lt": "lerna run dev:viewer:lesion-tracker --stream",
"dev:project": ".scripts/dev.sh",
"dev:orthanc": "lerna run dev:orthanc --stream",
"orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up",

View File

@ -41,7 +41,6 @@ export {
InputMultiSelect,
InputText,
Label,
MeasurementsPanel,
MeasurementTable,
Modal,
NavBar,
@ -56,6 +55,7 @@ export {
StudyListPagination,
StudyListTable,
StudyListTableRow,
StudySummary,
Svg,
Table,
TableBody,

View File

@ -1,65 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { MeasurementTable } from '@ohif/ui';
const MeasurementsPanel = ({
descriptionData,
measurementTableData,
actionButtons,
}) => {
const { date, modality, description } = descriptionData;
return (
<>
<div className="overflow-y-auto overflow-x-hidden invisible-scrollbar">
<div className="p-2">
<div className="leading-none">
<span className="text-white text-base mr-2">{date}</span>
<span className="px-1 text-black bg-common-bright text-base rounded-sm font-bold">
{modality}
</span>
</div>
<div className="leading-none">
<span className="text-base text-primary-light">{description}</span>
</div>
</div>
<MeasurementTable
data={measurementTableData.data}
title={measurementTableData.title}
amount={measurementTableData.amount}
onClick={measurementTableData.onClick}
onEdit={measurementTableData.onEdit}
/>
</div>
<div className="p-4 flex justify-center">{actionButtons}</div>
</>
);
};
MeasurementsPanel.defaultProps = {
actionButtons: null,
};
MeasurementsPanel.propTypes = {
descriptionData: PropTypes.shape({
date: PropTypes.string,
modality: PropTypes.string,
description: PropTypes.string,
}).isRequired,
measurementTableData: PropTypes.shape({
title: PropTypes.string,
amount: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
data: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.string,
displayText: PropTypes.string,
isActive: PropTypes.bool,
})
),
onClick: PropTypes.func,
onEdit: PropTypes.func,
}).isRequired,
actionButtons: PropTypes.node,
};
export default MeasurementsPanel;

View File

@ -1,101 +0,0 @@
---
name: Measurements Panel
menu: Data Display
route: components/measurementsPanel
---
import { useState } from 'react';
import { Playground, Props } from 'docz';
import {
SidePanel,
MeasurementsPanel,
ButtonGroup,
Button,
IconButton,
Icon,
} from '@ohif/ui';
# Measurements Panel
## Import
```javascript
import { MeasurementsPanel } from '@ohif/ui';
```
## Basic usage
<Playground>
{() => {
const [activeMeasurementItem, setActiveMeasurementItem] = useState(null);
const descriptionData = {
date: '07-Sep-2010',
modality: 'CT',
description: 'CHEST/ABD/PELVIS W CONTRAST',
};
const measurementTableData = {
title: 'Measurements',
amount: 10,
data: new Array(10).fill({}).map((el, i) => ({
id: i + 1,
label: 'Label short description',
displayText: '24.0 x 24.0 mm (S:4, I:22)',
isActive: activeMeasurementItem === i + 1,
})),
onClick: (id) => setActiveMeasurementItem((s) => (s === id ? null : id)),
onEdit: (id) => alert(`Edit: ${id}`),
};
return (
<div className="flex flex-row flex-no-wrap flex-1 items-stretch overflow-hidden w-full h-screen">
<div className="flex flex-1 h-100 overflow-hidden bg-primary-main items-center justify-center text-white">
CONTENT
</div>
<SidePanel
side="right"
iconName="list-bullets"
iconLabel="Measure"
componentLabel="Measurements"
defaultIsOpen={true}
>
<MeasurementsPanel
descriptionData={descriptionData}
measurementTableData={measurementTableData}
actionButtons={
<>
<ButtonGroup onClick={() => alert('Export')}>
<Button
className="text-white border-primary-main bg-black text-base py-2 px-2"
size="initial"
color="inherit"
>
Export
</Button>
<IconButton
className="bg-black border-primary-main px-2 text-white px-2"
color="inherit"
size="initial"
>
<Icon name="arrow-down" />
</IconButton>
</ButtonGroup>
<Button
className="text-white border border-primary-main bg-black text-base py-2 px-2 ml-2"
variant="outlined"
size="initial"
color="inherit"
onClick={() => alert('Create Report')}
>
Create Report
</Button>
</>
}
/>
</SidePanel>
</div>
);
}}
</Playground>
## Properties
<Props of={MeasurementsPanel} />

View File

@ -1,2 +0,0 @@
import MeasurementsPanel from './MeasurementsPanel';
export default MeasurementsPanel;

View File

@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
const StudySummary = ({ date, modality, description }) => {
return (
<div className="p-2">
<div className="leading-none">
<span className="mr-2 text-base text-white">{date}</span>
<span className="px-1 text-base font-bold text-black rounded-sm bg-common-bright">
{modality}
</span>
</div>
<div className="leading-none">
<span className="text-base text-primary-light">{description}</span>
</div>
</div>
);
};
StudySummary.propTypes = {
date: PropTypes.string.isRequired,
modality: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};
export default StudySummary;

View File

@ -0,0 +1,3 @@
import StudySummary from './StudySummary.jsx';
export default StudySummary;

View File

@ -12,7 +12,6 @@ import InputLabelWrapper from './InputLabelWrapper';
import InputMultiSelect from './InputMultiSelect';
import InputText from './InputText';
import Label from './Label';
import MeasurementsPanel from './MeasurementsPanel';
import MeasurementTable from './MeasurementTable';
import Modal from './Modal';
import NavBar from './NavBar';
@ -21,12 +20,13 @@ import Select from './Select';
import SegmentationTable from './SegmentationTable';
import SidePanel from './SidePanel';
import StudyBrowser from './StudyBrowser';
import StudyItem from './StudyItem';
import StudyListExpandedRow from './StudyListExpandedRow';
import StudyListFilter from './StudyListFilter';
import StudyListPagination from './StudyListPagination';
import { StudyListTable, StudyListTableRow } from './StudyListTable';
import StudySummary from './StudySummary';
import Svg from './Svg';
import StudyItem from './StudyItem';
import StudyListFilter from './StudyListFilter';
import Table from './Table';
import TableBody from './TableBody';
import TableCell from './TableCell';
@ -61,7 +61,6 @@ export {
InputMultiSelect,
InputText,
Label,
MeasurementsPanel,
MeasurementTable,
Modal,
NavBar,
@ -76,6 +75,7 @@ export {
StudyListPagination,
StudyListTable,
StudyListTableRow,
StudySummary,
Svg,
Table,
TableBody,

View File

@ -10,7 +10,6 @@ import {
NavBar,
SidePanel,
Svg,
MeasurementsPanel,
SegmentationTable,
ButtonGroup,
Button,
@ -125,10 +124,22 @@ import { tabs } from './studyBrowserMockData';
label: 'Measurements',
name: 'measurements',
content: (
<MeasurementsPanel
descriptionData={descriptionData}
measurementTableData={measurementTableData}
actionButtons={
<>
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
<StudySummary
date={descriptionData.date}
modality={descriptionData.modality}
description={descriptionData.description}
/>
<MeasurementTable
title="Measurements"
amount={measurementTableData.data.length}
data={measurementTableData.data}
onClick={() => {}}
onEdit={id => alert(`Edit: ${id}`)}
/>
</div>
<div className="flex justify-center p-4">
<React.Fragment>
<ButtonGroup onClick={() => alert('Export')}>
<Button
@ -156,8 +167,8 @@ import { tabs } from './studyBrowserMockData';
Create Report
</Button>
</React.Fragment>
}
/>
</div>
</>
)
}}
/>

View File

@ -19,14 +19,12 @@
"scripts": {
"build:package": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.commonjs.js --progress",
"build:viewer": "cross-env NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
"build:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=production node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js --progress",
"build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/pwa/ APP_CONFIG=config/netlify.js QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --config .webpack/webpack.pwa.js",
"build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=true node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js",
"build:viewer:package": "yarn run build:package",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
"dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js --watch",
"dev:viewer": "yarn run dev",
"dev:viewer:lesion-tracker": "cross-env ENTRY_TARGET=index-lesion-tracker.js NODE_ENV=development webpack-dev-server --config .webpack/webpack.pwa.js --watch",
"start": "yarn run dev",
"test:e2e": "cypress open",
"test:e2e:ci": "percy exec -- cypress run --config video=false --record --browser chrome --spec 'cypress/integration/visual-regression/**/*'",