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:
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;
|
||||
}
|
||||
Reference in new issue
Block a user