feat!: Ability to configure cornerstone tools via extension configuration (#1229)

* Fix ExtensionManager bug and add test to bandaid

* Add tools configuration to extension manager preinit

* Fix reducing of configs

* Merge internal with external configs

* Merge internal with external configs

* Remove dialog from init in measurementstable

* Testing injected configuration

* New way to set config

* Add new prop to dialog provider to allow disabling last position

* Remove code from preinit in cornerstone

* Add new prop to dialog provider to allow disabling last position

* Add centralize to dialogs

* Reorder dialogs when adding them

* Fix draggable styles (cursor)

* Remove repositioning methods from labelling flow and remove overlay from labelling manager

* Fix empty array being set in bringToFront

* Add new command to update table and pass commands manager to modules/preinit hook

* Ad UIContextMenu service / factory

* Use new contextmenu service in measurementspanel extension

* Use dialogs for arrow annotate in default

* Remove positioning funcionality from tool context menu

* Add context menu service

* Pass commandsModule to extension

* Update edit description dialog and simple dialog to position relative

* Remove style code from labelling flow and manager

* Remove eventdata from labelling

* Remove labelling code from measurement init

* Add commandsmanager to provider

* Update contextmenu provider and service

* Add touchstart and mouseclick to hide contextmenu

* Hide labelling if click/touch

* Remove labelling and context menu dead code

* Fix undefined bug if ViewerMain grid has no children

* Fix broken prop on context menu

* Update commandsmodule based on master

* Fix broken configuration

* Update script tag config

* Remove cornerstone from toolcontextmenu

* Remove cornerstone from toolcontextmenu

* Split labelling and context menu providers

* Split labelling and context menu providers

* Update test

* Destructure extensions into new array

* CR Update: Move default arrow config to cornerstone instead of default

* CR Update: Fix app configuration props structure

* CR Update: Fix app configuration prop in script tag and extract commands manager from providers

* CR Update: Create custom providers to use commandsManager

* CR Update: Use services directly in measurementspanel

* CR Update: Pass components to providers

* CR Update: Remove position from dialog

* CR Update: fix dialog prop check

* CR Update: Fix comments

* CR Update: Update documentation

* CR Update: Add test default configuration

* CR Update: Add default empy array to extensions

* CR Update: Update i18n configuration all ot match current function configuration

* CR Update: Add defaults to injected dependencies in configuration and extension configuration

* CR Update: Add defaults to configuration with no args

* Update documentation

* CR Update: Add default for tools

* CR Update: Update config object to i18n

* CR Update: spread defaults

* CR Update: Add tool configuration example to cornerstone extension

* CR Update: Add tool configuration to netlify (testing)

* CR Update: Remove netlify config for tools

* CR Update: Rollback changes to i18n to be fixed later

* CR Update: Update documentation and pass whole cornerstone config object instead of tools key

SEE: https://www.conventionalcommits.org/en/v1.0.0/#commit-message-with-both-and-breaking-change-footer

BREAKING CHANGE: modifies the exposed react <App /> components props. The contract for providing configuration for the app has changed. Please reference updated documentation for guidance.
This commit is contained in:
Igor Octaviano authored and Danny Brown committed 2019-12-09 12:47:23 -05:00
1 parent fd7620ced9
commit 55a580659e
66 files changed
+1464 -1223

No files matched your search

@@ -1,5 +1,5 @@
.editDescriptionDialog {
position: absolute;
position: relative;
z-index: 300;
width: 320px;
transition: all 300ms linear;
@@ -1,24 +1,15 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import SimpleDialog from '../SimpleDialog/SimpleDialog.js';
import bounding from '../../lib/utils/bounding.js';
import { getDialogStyle } from './../Labelling/labellingPositionUtils.js';
import './EditDescriptionDialog.css';
export default class EditDescriptionDialog extends Component {
static defaultProps = {
componentRef: React.createRef(),
componentStyle: {},
};
static propTypes = {
description: PropTypes.string,
measurementData: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
componentRef: PropTypes.object,
componentStyle: PropTypes.object,
onUpdate: PropTypes.func.isRequired,
};
@@ -28,14 +19,8 @@ export default class EditDescriptionDialog extends Component {
this.state = {
description: props.measurementData.description || '',
};
this.mainElement = React.createRef();
}
componentDidMount = () => {
bounding(this.mainElement);
};
componentDidUpdate(prevProps) {
if (this.props.description !== prevProps.description) {
this.setState({
@@ -45,16 +30,12 @@ export default class EditDescriptionDialog extends Component {
}
render() {
const style = getDialogStyle(this.props.componentStyle);
return (
<SimpleDialog
headerTitle="Edit Description"
onClose={this.onClose}
onConfirm={this.onConfirm}
rootClass="editDescriptionDialog"
componentRef={this.mainElement}
componentStyle={style}
>
<input
value={this.state.description}
@@ -1,20 +1,16 @@
import { Icon, SelectTree } from '@ohif/ui';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cloneDeep from 'lodash.clonedeep';
import LabellingTransition from './LabellingTransition.js';
import OHIFLabellingData from './OHIFLabellingData.js';
import PropTypes from 'prop-types';
import bounding from '../../lib/utils/bounding.js';
import cloneDeep from 'lodash.clonedeep';
import { getAddLabelButtonStyle } from './labellingPositionUtils.js';
export default class LabellingFlow extends Component {
static propTypes = {
eventData: PropTypes.object.isRequired,
measurementData: PropTypes.object.isRequired,
labellingDoneCallback: PropTypes.func.isRequired,
updateLabelling: PropTypes.func.isRequired,
initialTopDistance: PropTypes.number,
skipAddLabelButton: PropTypes.bool,
editLocation: PropTypes.bool,
@@ -26,11 +22,6 @@ export default class LabellingFlow extends Component {
const { location, locationLabel, description } = props.measurementData;
let style = props.componentStyle;
if (!props.skipAddLabelButton) {
style = getAddLabelButtonStyle(props.measurementData, props.eventData);
}
this.state = {
location,
locationLabel,
@@ -38,20 +29,17 @@ export default class LabellingFlow extends Component {
skipAddLabelButton: props.skipAddLabelButton,
editDescription: props.editDescription,
editLocation: props.editLocation,
componentStyle: style,
confirmationState: false,
displayComponent: true,
};
this.mainElement = React.createRef();
this.descriptionInput = React.createRef();
this.initialItems = OHIFLabellingData;
this.currentItems = cloneDeep(this.initialItems);
}
componentDidUpdate = () => {
this.repositionComponent();
if (this.state.editDescription) {
this.descriptionInput.current.focus();
}
@@ -63,36 +51,14 @@ export default class LabellingFlow extends Component {
mainElementClassName += ' editDescription';
}
const style = Object.assign({}, this.state.componentStyle);
if (this.state.skipAddLabelButton) {
if (style.left - 160 < 0) {
style.left = 0;
} else {
style.left -= 160;
}
}
if (this.state.editLocation) {
style.maxHeight = '70vh';
if (!this.initialTopDistance) {
this.initialTopDistance = window.innerHeight - window.innerHeight * 0.3;
style.top = `${this.state.componentStyle.top -
this.initialTopDistance / 2}px`;
} else {
style.top = `${this.state.componentStyle.top}px`;
}
}
return (
<LabellingTransition
displayComponent={this.state.displayComponent}
onTransitionExit={this.props.labellingDoneCallback}
>
<>
<div className="labellingComponent-overlay"></div>
<div
className={mainElementClassName}
style={style}
ref={this.mainElement}
onMouseLeave={this.fadeOutAndLeave}
onMouseEnter={this.clearFadeOutTimer}
@@ -130,9 +96,8 @@ export default class LabellingFlow extends Component {
<SelectTree
items={this.currentItems}
columns={1}
onSelected={this.selectTreeSelectCalback}
onSelected={this.selectTreeSelectCallback}
selectTreeFirstTitle="Assign Label"
onComponentChange={this.repositionComponent}
/>
);
} else {
@@ -195,33 +160,17 @@ export default class LabellingFlow extends Component {
}
};
relabel = event => {
const viewportTopPosition = this.mainElement.current.offsetParent.offsetTop;
const componentStyle = {
top: event.nativeEvent.y - viewportTopPosition - 55,
left: event.nativeEvent.x,
};
this.setState({
editLocation: true,
componentStyle,
});
};
relabel = event => this.setState({ editLocation: true });
setDescriptionUpdateMode = () => {
this.descriptionInput.current.focus();
this.setState({
editDescription: true,
});
this.setState({ editDescription: true });
};
descriptionCancel = () => {
const { description = '' } = cloneDeep(this.state);
this.descriptionInput.current.value = description;
this.setState({
editDescription: false,
});
this.setState({ editDescription: false });
};
handleKeyPress = e => {
@@ -240,22 +189,15 @@ export default class LabellingFlow extends Component {
});
};
selectTreeSelectCalback = (event, itemSelected) => {
selectTreeSelectCallback = (event, itemSelected) => {
const location = itemSelected.value;
this.props.updateLabelling({ location });
const viewportTopPosition = this.mainElement.current.offsetParent.offsetTop;
const componentStyle = {
top: event.nativeEvent.y - viewportTopPosition - 25,
left: event.nativeEvent.x,
};
this.setState({
editLocation: false,
confirmationState: true,
location: itemSelected.value,
locationLabel: itemSelected.label,
componentStyle,
});
if (this.isTouchScreen) {
@@ -276,18 +218,13 @@ export default class LabellingFlow extends Component {
fadeOutAndLeave = () => {
// Wait for 1 sec to dismiss the labelling component
this.fadeOutTimer = setTimeout(() => {
this.setState({
displayComponent: false,
});
}, 1000);
this.fadeOutTimer = setTimeout(
() => this.setState({ displayComponent: false }),
1000
);
};
fadeOutAndLeaveFast = () => {
this.setState({
displayComponent: false,
});
};
fadeOutAndLeaveFast = () => this.setState({ displayComponent: false });
clearFadeOutTimer = () => {
if (!this.fadeOutTimer) {
@@ -296,29 +233,4 @@ export default class LabellingFlow extends Component {
clearTimeout(this.fadeOutTimer);
};
calculateTopDistance = () => {
const height = window.innerHeight - window.innerHeight * 0.3;
let top = this.state.componentStyle.top - height / 2 + 55;
if (top < 0) {
top = 0;
} else {
if (top + height > window.innerHeight) {
top -= top + height - window.innerHeight;
}
}
return top;
};
repositionComponent = () => {
// SetTimeout for the css animation to end.
setTimeout(() => {
bounding(this.mainElement);
if (this.state.editLocation) {
this.mainElement.current.style.maxHeight = '70vh';
const top = this.calculateTopDistance();
this.mainElement.current.style.top = `${top}px`;
}
}, 200);
};
}
@@ -1,18 +1,9 @@
.labellingComponent-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
background-color: rgba(0, 0, 0, 0.8);
}
.labellingComponent {
position: absolute;
position: relative;
text-align: center;
z-index: 999;
transition: all 200ms linear;
max-height: 500px;
}
.labellingComponent .selectedLabel,
@@ -87,9 +78,7 @@
border: none;
}
.labellingComponent.editDescription
.locationDescriptionWrapper
#descriptionInput {
.labellingComponent.editDescription .locationDescriptionWrapper #descriptionInput {
visibility: visible;
}
@@ -1,21 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cloneDeep from 'lodash.clonedeep';
import EditDescriptionDialog from './../EditDescriptionDialog/EditDescriptionDialog.js';
import LabellingFlow from './LabellingFlow.js';
import './LabellingManager.css';
export default class LabellingManager extends Component {
static propTypes = {
eventData: PropTypes.object.isRequired,
measurementData: PropTypes.object.isRequired,
labellingDoneCallback: PropTypes.func.isRequired,
updateLabelling: PropTypes.func.isRequired,
skipAddLabelButton: PropTypes.bool,
editLocation: PropTypes.bool,
editDescription: PropTypes.bool,
@@ -41,7 +36,6 @@ export default class LabellingManager extends Component {
}
this.state = {
componentStyle: getComponentPosition(props.eventData),
skipAddLabelButton: props.skipAddLabelButton,
editLocation: editLocation,
editDescription: props.editDescription,
@@ -75,20 +69,13 @@ export default class LabellingManager extends Component {
<EditDescriptionDialog
onCancel={this.props.labellingDoneCallback}
onUpdate={this.descriptionDialogUpdate}
componentRef={this.editDescriptionDialog}
componentStyle={this.state.componentStyle}
measurementData={measurementData}
/>
);
}
if (editLocation || editDescription) {
return (
<LabellingFlow
{...this.props}
componentStyle={this.state.componentStyle}
/>
);
return <LabellingFlow {...this.props} />;
}
};
@@ -98,33 +85,19 @@ export default class LabellingManager extends Component {
if (editDescription) {
measurementData.description = undefined;
}
if (editLocation) {
measurementData.location = undefined;
}
};
responseDialogUpdate = response => {
this.props.updateLabelling({
response,
});
this.props.updateLabelling({ response });
this.props.labellingDoneCallback();
};
descriptionDialogUpdate = description => {
this.props.updateLabelling({
description,
});
this.props.updateLabelling({ description });
this.props.labellingDoneCallback();
};
}
function getComponentPosition(eventData) {
const {
event: { clientX: left, clientY: top },
} = eventData;
return {
left,
top,
};
}
@@ -1,53 +0,0 @@
import cornerstone from 'cornerstone-core';
const buttonSize = {
width: 96,
height: 28,
};
export function getAddLabelButtonStyle(measurementData, eventData) {
const { start, end } = measurementData.handles;
const { client } = eventData.currentPoints;
const clientStart = cornerstone.pixelToCanvas(eventData.element, start);
const clientEnd = cornerstone.pixelToCanvas(eventData.element, end);
const canvasOffSetLeft = client.x - clientStart.x;
const canvasOffSetTop = client.y - clientStart.y;
const position = {
left: clientEnd.x + canvasOffSetLeft,
top: clientEnd.y + canvasOffSetTop,
};
if (start.y > end.y) {
position.top -= buttonSize.height;
}
if (start.x > end.x) {
position.left -= buttonSize.width;
}
return position;
}
export function getDialogStyle(componentStyle) {
const style = Object.assign({}, componentStyle);
const dialogProps = {
width: 320,
height: 230,
};
// Get max values to avoid position out of the screen
const maxLeft = window.innerWidth - dialogProps.width;
const maxTop = window.innerHeight - dialogProps.height;
// Positioning the dialog with its center on the click event
style.left -= dialogProps.width / 2;
style.top -= dialogProps.height / 2;
if (style.left > maxLeft) {
style.left = maxLeft;
}
if (style.top > maxTop) {
style.top = maxTop;
}
return style;
}