Chore/kill yalc with fire (#629)

* init

* more shifting

* Move @babel dependencies up to workspace root

* Begin updating dependent libraries to use sync'd webpack builds w/ watches

* Commit changes before a long weekend

* Split packages into platform and extensions

* Consolidate how/where we specify file/module type entrypoints

* Make it possible to pass in extensions as App props

* Resolve viewer's modules

* Target for UMD bundle

* Add Webpack Stylus loader

* Provide WebPack build options for microscopy, vtk, and ui

* Better entrypoint for extensions

* Push changes up to switch PCs

* Fix ESM symlink build for Viewers

* Shift docs to root

* Specify additional lerna config props

* Begin providing basic readme info

* Rename example extension folder

* Shift links to bottom of doc

* First attempt at a netlify deploy preview

* Attempt to fix deploy preview build

* push workspace enabled to initial command; remove second yarn install; use npx to call gitbook cli commands

* Try to use global gitbook-cli

* Export node_modules path

* More agressive with modifying PATH

* Follow deploy log output advice

* try again to set bin path

* Try swapping bash with a new shell

* Try bash -l instead of exec bash

* Annd let's try again

* and again

* We may have figured it out johnny, boy

* We need to pull cornerstone-wado-image-loader files from hoisted node_modules

* Attempt to fix ticks/escapes

* Change copy syntax; try running tests on viewers from root for circleci

* Lower version to match circleci image

* Try alternative jest-canvas-mock location and version file syntax

* Run version command instead of calling node directly

* Try alternative jest transform

* Set path and enable workspaces

* Add codecov flags

* Use cpx so our copying finishes?

* Get all platform unit tests to run

* Run and report individually and in parallel

* Try once relying on codecov yaml to split w/ flags

* Add projects to split by flags

* Generate separate reports

* Trigger codecov after everything has finished running; these may not support flags

* Use recommended flags from issue comments for codecov

* Fix typo

* Remove clear flag

* Shift back to calling codecov from root

* Generate example for codecov issue

* Try fixing paths

* Simplify

* Try using codecov orb

* Bump circleci version

* Upload core and viewer

* Use full string paths

* Remove individual codecov calls

* Fix dir

* Combine files before upload

* Escape anchors

* tryfix syntax

* Escape string literal

* Combine lines to reduce path

* long paths

* Use home alias

* Fix filename

* Try to see the contents of our cat'd file

* Also upload `core`

* Try running with aliased folder

* Fix path; split PR and Merge unit tests into separate jobs

* Set default threshold

* Fix typo

* Changing to scoped package names

* Adding note

* Catch more updates

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.0
 - @ohif/extension-dicom-html@0.0.4-alpha.0
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.0
 - @ohif/extension-dicom-pdf@0.0.8-alpha.0
 - @ohif/extension-vtk@0.1.4-alpha.0
 - @ohif/core@0.11.1-alpha.0
 - @ohif/i18n@0.2.3-alpha.0
 - @ohif/ui@0.2.18-alpha.0
 - @ohif/viewer@0.0.22-alpha.0

* Clean up primary readme

* Support for dev and dev:* commands

* Shift build command; satisfy default PWA build

* Remove old scripts

* Clean up UI to set webpack scripts

* Tidy up project links

* Clean duplicate code in UI project

* Update UI project's docs

* Clean up ui and i18n config

* Reduce duplicate code in `@ohif/core`

* Fix @ohif/ui versions

* bump @ohif/core versions

* Redux testkit dep

* Remove unused/duplicate config files for projects/packages

* ohif-core --> @ohif/core

* Safer publish command

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.1
 - @ohif/extension-dicom-html@0.0.4-alpha.1
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.1
 - @ohif/extension-dicom-pdf@0.0.8-alpha.1
 - @ohif/extension-vtk@0.1.4-alpha.1
 - @ohif/core@0.11.1-alpha.1
 - @ohif/i18n@0.2.3-alpha.1
 - @ohif/ui@0.2.18-alpha.1
 - @ohif/viewer@0.0.22-alpha.1

* test version bump

* Add global prettier config

* chore(release): publish %s [skip ci]

 - @ohif/viewer@0.0.22-alpha.2

* test lerna publish

* Update package.jsons

* Fix build scripts

* catch changes

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.2
 - @ohif/extension-dicom-html@0.0.4-alpha.2
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.2
 - @ohif/extension-dicom-pdf@0.0.8-alpha.2
 - @ohif/extension-vtk@0.1.4-alpha.2
 - @ohif/core@0.11.1-alpha.2
 - @ohif/i18n@0.2.3-alpha.2
 - @ohif/ui@0.2.18-alpha.2
 - @ohif/viewer@0.0.22-alpha.3

* fix eslint issues caused by eslint 6 and our react-app config

* Publish with canary tag

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.3
 - @ohif/extension-dicom-html@0.0.4-alpha.3
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.3
 - @ohif/extension-dicom-pdf@0.0.8-alpha.3
 - @ohif/extension-vtk@0.1.4-alpha.3
 - @ohif/core@0.11.1-alpha.3
 - @ohif/i18n@0.2.3-alpha.3
 - @ohif/ui@0.2.18-alpha.3
 - @ohif/viewer@0.0.22-alpha.4

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.4
 - @ohif/extension-dicom-html@0.0.4-alpha.4
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.4
 - @ohif/extension-dicom-pdf@0.0.8-alpha.4
 - @ohif/extension-vtk@0.1.4-alpha.4
 - @ohif/core@0.11.1-alpha.4
 - @ohif/i18n@0.2.3-alpha.4
 - @ohif/ui@0.2.18-alpha.4
 - @ohif/viewer@0.0.22-alpha.5

* Simplify build

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.5
 - @ohif/extension-dicom-html@0.0.4-alpha.5
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.5
 - @ohif/extension-dicom-pdf@0.0.8-alpha.5
 - @ohif/extension-vtk@0.1.4-alpha.5
 - @ohif/core@0.11.1-alpha.5
 - @ohif/i18n@0.2.3-alpha.5
 - @ohif/ui@0.2.18-alpha.5
 - @ohif/viewer@0.0.22-alpha.6

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.6
 - @ohif/extension-dicom-html@0.0.4-alpha.6
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.6
 - @ohif/extension-dicom-pdf@0.0.8-alpha.6
 - @ohif/extension-vtk@0.1.4-alpha.6
 - @ohif/core@0.11.1-alpha.6
 - @ohif/i18n@0.2.3-alpha.6
 - @ohif/ui@0.2.18-alpha.6
 - @ohif/viewer@0.0.22-alpha.7

* Split version and publish

* chore(release): publish %s [skip ci]

 - @ohif/extension-cornerstone@0.0.39-alpha.7
 - @ohif/extension-dicom-html@0.0.4-alpha.7
 - @ohif/extension-dicom-microscopy@0.0.9-alpha.7
 - @ohif/extension-dicom-pdf@0.0.8-alpha.7
 - @ohif/extension-vtk@0.1.4-alpha.7
 - @ohif/core@0.11.1-alpha.7
 - @ohif/i18n@0.2.3-alpha.7
 - @ohif/ui@0.2.18-alpha.7
 - @ohif/viewer@0.0.22-alpha.8

* Try simplify netlify build

* react-viewerbase --> @ohif/ui

* add test for stylus loader

* Try new way to specify publish path

* Use an output folder that won't be ignored

* Try move command

* fiddle with paths

* Dig for some information

* Make sure we're in root for our commands

* Remove discovery

* Setup script-tag example

* Fix typo

* Move config folder

* Update cornerstone extension to use shared/root webpack configs

* capture changes

* Update extension webpack configs

* Don't check in docz output

* Fix babel runtime resolution issue

* Simplify ui build steps

* Push in progress

* Shift directories

* Get viewer dev path to work

* Fix viewer commonjs build

* Fix build pwa config for viewers

* Make sure our package specific command can be called from root

* Update notes

* Fix imports and misc. merge notes

* Bump wado-image-loader version

* Switch up commands to remove webworkers copy

* Fix CI config

* Fix more broken package references

* Add react-dropzone

* Persist so e2e test can use

* Fix lockfile

* Use cypress orb to simplify CI

* Fix config

* Try again

* Install yarn latest for orb

* Install yarn a different way

* Wait on correct port

* Run tests in viewer project

* Fix cypress config

* Make Cypress happier - use default location

* Simplify e2e testing

* Capture UI changes since fork

* Catch core up

* Grab latest deps after merge

* Fix broken import

* Make sure cornerstone-wado-image-loader resolves to 3+

* Note regarding orbs

* Fix refresh on 404 routes

* Fix odd error for cornerstone-wado-image-loader

* Different target for UMD

* Simplify dev script

* Try to get fancy with netlify output

* Let's do it again

* Try to fix measurementTools issue

* See if we can get our lerna magic to work on merge to monorepo branch
This commit is contained in:
Danny Brown authored and GitHub committed 2019-08-13 15:36:46 -04:00
1 parent cac911f08e
commit 08ff214ea6
919 files changed
+45652 -52097

No files matched your search

@@ -0,0 +1,37 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
@@ -0,0 +1,6 @@
.editDescriptionDialog {
position: absolute;
z-index: 300;
width: 320px;
transition: all 300ms linear;
}
@@ -0,0 +1,83 @@
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 = {
measurementData: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
componentRef: PropTypes.object,
componentStyle: PropTypes.object,
onUpdate: PropTypes.func.isRequired,
};
constructor(props) {
super(props);
this.state = {
description: props.measurementData.description || '',
};
this.mainElement = React.createRef();
}
componentDidMount = () => {
bounding(this.mainElement);
};
componentDidUpdate(prevProps) {
if (this.props.description !== prevProps.description) {
this.setState({
description: this.props.description,
});
}
}
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}
className="simpleDialogInput"
id="description"
autoComplete="off"
autoFocus
onChange={this.handleChange}
/>
</SimpleDialog>
);
}
onClose = () => {
this.props.onCancel();
};
onConfirm = e => {
e.preventDefault();
this.props.onUpdate(this.state.description);
};
handleChange = event => {
this.setState({ description: event.target.value });
};
}
@@ -0,0 +1,108 @@
.entry-header {
padding: 10px 10px 0;
height: var(--top-bar-height);
}
.entry-header.header-big {
background: rgba(21,25,30,0.7);
padding: 10px var(--study-list-padding);
height: auto;
display: inline-block;
width: 100%;
}
.header-left-box {
display: inline-block;
position: relative;
}
.header-viewerLink {
float: left;
}
.header-brand {
height: 30px;
text-decoration: none;
float: left;
clear: both;
}
.entry-header.header-big .header-brand {
height: auto;
padding: 25px 0;
}
.header-logo-image {
fill: transparent;
height: 100%;
margin: 0 8px 0 0;
width: 30px;
float: left;
}
.entry-header.header-big .header-logo-image {
margin: 0 20px 0 0;
width: 50px;
height: 50px;
}
.header-logo-text {
display: inline-block;
font-family: var(--logo-font-family);
font-size: 14px;
font-weight: var(--logo-font-weight);
color: var(--text-primary-color);
line-height: 30px;
}
.entry-header.header-big .header-logo-text {
font-size: 30px;
line-height: 36px;
}
.header-btn {
color: var(--text-secondary-color);
cursor: pointer;
font-size: 13px;
font-weight: 500;
line-height: 26px;
}
.header-btn:hover {
color: var(--hover-color);
}
.header-btn:active {
color: var(--active-color);
}
.header-studyListLinkSection {
border-left: var(--ui-border-thickness) solid var(--ui-border-color);
margin: 3px 0 0 10px;
padding: 0 0 0 10px;
display: inline-block;
}
.header-menu {
color: var(--text-primary-color);
float: right;
padding: 4px 0;
font-size: 13px;
font-weight: 400;
line-height: 18px;
text-decoration: none;
margin-right: 10px;
}
.entry-header.header-big .header-menu {
margin-right: 0;
}
.header-menu .research-use {
float: left;
font-size: 13px;
font-weight: 700;
color: #91b9cd;
margin-right: 1rem;
}
@@ -0,0 +1,133 @@
import './Header.css';
import './Header.css';
import { Link, withRouter } from 'react-router-dom';
import React, { Component } from 'react';
import { Dropdown } from '@ohif/ui';
import OHIFLogo from '../OHIFLogo/OHIFLogo.js';
import PropTypes from 'prop-types';
import { AboutModal } from '@ohif/ui';
import { hotkeysManager } from './../../App.js';
import { withTranslation } from 'react-i18next';
class Header extends Component {
static propTypes = {
home: PropTypes.bool.isRequired,
location: PropTypes.object.isRequired,
children: PropTypes.node,
t: PropTypes.func.isRequired,
userManager: PropTypes.object
};
static defaultProps = {
home: true,
children: OHIFLogo(),
};
// onSave: data => {
// const contextName = store.getState().commandContext.context;
// const preferences = cloneDeep(store.getState().preferences);
// preferences[contextName] = data;
// dispatch(setUserPreferences(preferences));
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys(data.hotKeysData);
// },
// onResetToDefaults: () => {
// dispatch(setUserPreferences());
// dispatch(setUserPreferencesModalOpen(false));
// OHIF.hotkeysUtil.setHotkeys();
// },
constructor(props) {
super(props);
this.state = { isUserPreferencesOpen: false, isOpen: false };
this.loadOptions();
}
loadOptions() {
const { t } = this.props;
this.options = [
{
title: t('About'),
icon: { name: 'info' },
onClick: () => {
this.setState({
isOpen: true,
});
},
},
];
if (this.props.user && this.props.userManager) {
this.options.push({
title: t('Logout'),
icon: { name: 'power-off' },
onClick: () => {
this.props.userManager.signoutRedirect();
},
});
}
this.hotKeysData = hotkeysManager.hotkeyDefinitions;
}
onUserPreferencesSave({ windowLevelData, hotKeysData }) {
// console.log(windowLevelData);
// console.log(hotKeysData);
// TODO: Update hotkeysManager
// TODO: reset `this.hotKeysData`
}
render() {
const { t } = this.props;
const showStudyList =
window.config.showStudyList !== undefined
? window.config.showStudyList
: true;
return (
<div className={`entry-header ${this.props.home ? 'header-big' : ''}`}>
<div className="header-left-box">
{this.props.location && this.props.location.studyLink && (
<Link
to={this.props.location.studyLink}
className="header-btn header-viewerLink"
>
{t('Back to Viewer')}
</Link>
)}
{this.props.children}
{showStudyList && !this.props.home && (
<Link
className="header-btn header-studyListLinkSection"
to={{
pathname: '/',
state: { studyLink: this.props.location.pathname },
}}
>
{t('Study list')}
</Link>
)}
</div>
<div className="header-menu">
<span className="research-use">{t('INVESTIGATIONAL USE ONLY')}</span>
<Dropdown title={t('Options')} list={this.options} align="right" />
<AboutModal
{...this.state}
onCancel={() =>
this.setState({
isOpen: false,
})
}
/>
</div>
</div>
);
}
}
export default withTranslation('Header')(withRouter(Header));
@@ -0,0 +1,3 @@
import Header from './Header';
export default Header;
@@ -0,0 +1,317 @@
import { Icon, SelectTree } from '@ohif/ui';
import React, { Component } from 'react';
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,
editDescription: PropTypes.bool,
};
constructor(props) {
super(props);
const { location, locationLabel, description } = props.measurementData;
let style = props.componentStyle;
if (!props.skipAddLabelButton) {
style = getAddLabelButtonStyle(props.measurementData, props.eventData);
}
this.state = {
location,
locationLabel,
description,
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();
}
};
render() {
let mainElementClassName = 'labellingComponent';
if (this.state.editDescription) {
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}
>
{this.labellingStateFragment()}
</div>
</>
</LabellingTransition>
);
}
labellingStateFragment = () => {
const {
skipAddLabelButton,
editLocation,
description,
locationLabel,
} = this.state;
if (!skipAddLabelButton) {
return (
<>
<button
type="button"
className="addLabelButton"
onClick={this.showLabelling}
>
{this.state.location ? 'Edit' : 'Add'} Label
</button>
</>
);
} else {
if (editLocation) {
return (
<SelectTree
items={this.currentItems}
columns={1}
onSelected={this.selectTreeSelectCalback}
selectTreeFirstTitle="Assign Label"
onComponentChange={this.repositionComponent}
/>
);
} else {
return (
<>
<div
className="checkIconWrapper"
onClick={this.fadeOutAndLeaveFast}
>
<Icon name="check" className="checkIcon" />
</div>
<div className="locationDescriptionWrapper">
<div className="location">{locationLabel}</div>
<div className="description">
<input
id="descriptionInput"
ref={this.descriptionInput}
defaultValue={description || ''}
autoComplete="off"
/>
</div>
</div>
<div className="commonButtons">
<button
type="button"
className="commonButton left"
onClick={this.relabel}
>
Relabel
</button>
<button
type="button"
className="commonButton right"
onClick={this.setDescriptionUpdateMode}
>
{description ? 'Edit ' : 'Add '}
Description
</button>
</div>
<div className="editDescriptionButtons">
<button
type="button"
className="commonButton left"
onClick={this.descriptionCancel}
>
Cancel
</button>
<button
type="button"
className="commonButton right"
onClick={this.descriptionSave}
>
Save
</button>
</div>
</>
);
}
}
};
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,
});
};
setDescriptionUpdateMode = () => {
this.descriptionInput.current.focus();
this.setState({
editDescription: true,
});
};
descriptionCancel = () => {
const { description = '' } = cloneDeep(this.state);
this.descriptionInput.current.value = description;
this.setState({
editDescription: false,
});
};
descriptionSave = () => {
const description = this.descriptionInput.current.value;
this.props.updateLabelling({ description });
this.setState({
description,
editDescription: false,
});
};
selectTreeSelectCalback = (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) {
this.setTimeout = setTimeout(() => {
this.setState({
displayComponent: false,
});
}, 2000);
}
};
showLabelling = () => {
this.setState({
skipAddLabelButton: true,
editLocation: false,
});
};
fadeOutAndLeave = () => {
// Wait for 1 sec to dismiss the labelling component
this.fadeOutTimer = setTimeout(() => {
this.setState({
displayComponent: false,
});
}, 1000);
};
fadeOutAndLeaveFast = () => {
this.setState({
displayComponent: false,
});
};
clearFadeOutTimer = () => {
if (!this.fadeOutTimer) {
return;
}
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);
};
}
@@ -0,0 +1,140 @@
.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;
text-align: center;
z-index: 999;
transition: all 200ms linear;
}
.labellingComponent .selectedLabel,
.labellingComponent .selectedDescription {
padding: 5px;
background-color: white;
width: 150px;
}
.labellingComponent .addLabelButton {
color: #000000;
background-color: #20a5d6;
border: 2px solid #44626f;
border-radius: 14px;
cursor: pointer;
font-weight: bold;
font-size: 13px;
line-height: 24px;
opacity: 1;
padding: 0 14px;
transition: opacity 0.3s ease;
outline: none;
cursor: pointer;
}
.labellingComponent .commonButton {
border: 1px solid #44626f;
color: #ffffff;
background-color: #000000;
border-radius: 16px;
font-weight: bold;
font-size: 13px;
line-height: 26px;
padding: 0 12px;
margin: 10px 5px 0 0;
outline: none;
cursor: pointer;
}
.labellingComponent .locationDescriptionWrapper {
background-color: #ffffff;
line-height: 46px;
height: 46px;
font-size: 13px;
position: relative;
width: 290px;
min-width: 260px;
padding: 0 12px;
margin: 0 auto;
display: inline-block;
}
.labellingComponent .locationDescriptionWrapper .location {
transition: all 300ms linear;
position: absolute;
white-space: nowrap;
bottom: 0;
}
.labellingComponent.editDescription .locationDescriptionWrapper .location {
bottom: 38px;
}
.labellingComponent .locationDescriptionWrapper #descriptionInput {
transition-delay: all 300ms linear;
visibility: hidden;
outline: none;
height: 46px;
width: 100%;
line-height: 20px;
font-size: 13px;
border: none;
}
.labellingComponent.editDescription
.locationDescriptionWrapper
#descriptionInput {
visibility: visible;
}
.labellingComponent.editDescription .location {
color: #337ab7;
}
.labellingComponent .commonButtons,
.labellingComponent.editDescription .editDescriptionButtons {
display: block;
margin-left: 55px;
}
.labellingComponent.editDescription .commonButtons,
.labellingComponent .editDescriptionButtons {
display: none;
}
.labellingComponent .commonButtons {
text-align: center;
margin-left: 55px;
}
.labellingComponent .commonButton.left {
float: left;
}
.labellingComponent .commonButton.right {
float: right;
}
.labellingComponent .checkIconWrapper {
display: inline-block;
background-color: #337ab7;
border-radius: 46px;
width: 46px;
height: 46px;
margin-right: 10px;
vertical-align: bottom;
cursor: pointer;
}
.labellingComponent .checkIcon {
width: 20px;
height: 20px;
margin: 13px;
fill: black;
}
@@ -0,0 +1,130 @@
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,
editDescriptionOnDialog: PropTypes.bool,
};
static defaultProps = {
skipAddLabelButton: false,
editLocation: false,
editDescription: false,
editDescriptionOnDialog: false,
};
constructor(props) {
super(props);
const measurementData = cloneDeep(props.measurementData);
this.treatMeasurementData(measurementData);
let editLocation = props.editLocation;
if (!props.editDescription && !props.editLocation) {
editLocation = true;
}
this.state = {
componentStyle: getComponentPosition(props.eventData),
skipAddLabelButton: props.skipAddLabelButton,
editLocation: editLocation,
editDescription: props.editDescription,
editDescriptionOnDialog: props.editDescriptionOnDialog,
measurementData: measurementData,
};
}
componentDidMount = () => {
document.addEventListener('touchstart', this.onTouchStart);
};
componentWillUnmount = () => {
document.removeEventListener('touchstart', this.onTouchStart);
};
render() {
return this.getRenderComponent();
}
getRenderComponent = () => {
const {
editLocation,
editDescription,
editDescriptionOnDialog,
measurementData,
} = this.state;
if (editDescriptionOnDialog) {
return (
<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}
/>
);
}
};
treatMeasurementData = measurementData => {
const { editDescription, editLocation } = this.props;
if (editDescription) {
measurementData.description = undefined;
}
if (editLocation) {
measurementData.location = undefined;
}
};
responseDialogUpdate = response => {
this.props.updateLabelling({
response,
});
this.props.labellingDoneCallback();
};
descriptionDialogUpdate = description => {
this.props.updateLabelling({
description,
});
this.props.labellingDoneCallback();
};
}
function getComponentPosition(eventData) {
const {
event: { clientX: left, clientY: top },
} = eventData;
return {
left,
top,
};
}
@@ -0,0 +1,21 @@
.labelling-appear {
opacity: 0;
}
.labelling-appear.labelling-appear-active {
opacity: 1;
transition: opacity 500ms linear;
}
.labelling-exit {
opacity: 1;
}
.labelling-exit.labelling-exit-active {
opacity: 0;
transition: opacity 500ms linear;
}
.labelling-exit-done {
opacity: 0;
}
@@ -0,0 +1,31 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { CSSTransition } from 'react-transition-group';
import './LabellingTransition.css';
// If these variables changes, CSS must be updated
const transitionDuration = 500;
const transitionClassName = 'labelling';
const transitionOnAppear = true;
export default class LabellingTransition extends Component {
static propTypes = {
children: PropTypes.node.isRequired,
displayComponent: PropTypes.bool.isRequired,
onTransitionExit: PropTypes.func.isRequired,
};
render() {
return (
<CSSTransition
in={this.props.displayComponent}
appear={transitionOnAppear}
timeout={transitionDuration}
classNames={transitionClassName}
onExited={this.props.onTransitionExit}
>
{this.props.children}
</CSSTransition>
);
}
}
@@ -0,0 +1,39 @@
const items = [
'Abdomen/Chest Wall',
'Adrenal',
'Bladder',
'Bone',
'Brain',
'Breast',
'Colon',
'Esophagus',
'Extremities',
'Gallbladder',
'Kidney',
'Liver',
'Lung',
'Lymph Node',
'Mediastinum/Hilum',
'Muscle',
'Neck',
'Other Soft Tissue',
'Ovary',
'Pancreas',
'Pelvis',
'Peritoneum/Omentum',
'Prostate',
'Retroperitoneum',
'Small Bowel',
'Spleen',
'Stomach',
'Subcutaneous',
];
const OHIFLabellingData = items.map(item => {
return {
label: item,
value: item,
};
});
export default OHIFLabellingData;
@@ -0,0 +1,53 @@
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;
}
@@ -0,0 +1,32 @@
import React from 'react';
const Bar = ({ progress, animationDuration }) => (
<div
style={{
background: '#29d',
height: 2,
left: 0,
marginLeft: `${(-1 + progress) * 100}%`,
position: 'fixed',
top: 0,
transition: `margin-left ${animationDuration}ms linear`,
width: '100%',
zIndex: 1031,
}}
>
<div
style={{
boxShadow: '0 0 10px #29d, 0 0 5px #29d',
display: 'block',
height: '100%',
opacity: 1,
position: 'absolute',
right: 0,
transform: 'rotate(3deg) translate(0px, -4px)',
width: 100,
}}
/>
</div>
);
export default Bar;
@@ -0,0 +1,15 @@
import React from 'react';
const Container = ({ children, isFinished, animationDuration }) => (
<div
style={{
opacity: isFinished ? 0 : 1,
pointerEvents: 'none',
transition: `opacity ${animationDuration}ms linear`,
}}
>
{children}
</div>
);
export default Container;
@@ -0,0 +1,4 @@
import Bar from './Bar.js';
import Container from './Container.js';
export { Bar, Container };
@@ -0,0 +1,29 @@
.header-brand {
height: 30px;
text-decoration: none;
text-decoration-color: none;
display: flex;
color: var(--text-primary-color);
font-size: 14px;
font-weight: var(--logo-font-weight);
font-family: var(--logo-font-family);
}
.header-brand:hover,
.header-brand:active,
.header-band:visited {
color: var(--text-primary-color);
text-decoration: none;
}
.header-logo-text {
line-height: 30px;
margin-left: 8px;
}
.header-logo-image {
height: 30px;
width: 30px;
font-size: 30px;
}
@@ -0,0 +1,20 @@
import './OHIFLogo.css';
import { Icon } from '@ohif/ui';
import React from 'react';
function OHIFLogo() {
return (
<a
target="_blank"
rel="noopener noreferrer"
className="header-brand"
href="http://ohif.org"
>
<Icon name="ohif-logo" className="header-logo-image" />
<div className="header-logo-text">Open Health Imaging Foundation</div>
</a>
);
}
export default OHIFLogo;
@@ -0,0 +1,40 @@
.FlexboxLayout {
display: flex;
flex: 1;
flex-flow: row nowrap;
align-items: stretch;
height: calc(100% - var(--toolbar-height) - var(--top-bar-height));
width: 100%;
overflow: hidden;
}
.sidepanel {
flex: 1;
height: 100%;
transition: var(--sidepanel-transition);
}
.from-left {
border-right: var(--ui-border-thickness) solid var(--ui-border-color);
margin-left: calc(var(--left-sidepanel-menu-width) * -1);
max-width: var(--left-sidepanel-menu-width);
}
.from-right {
border-left: var(--ui-border-thickness) solid var(--ui-border-color);
margin-right: calc(var(--right-sidepanel-menu-width) * -1);
max-width: var(--right-sidepanel-menu-width);
}
.sidepanel.is-open {
margin-right: 0;
margin-left: 0;
}
.main-content {
flex: 1;
height: 100%;
overflow: hidden;
transition: var(--sidepanel-transition);
width: 100%;
}
@@ -0,0 +1,41 @@
import './SidePanel.css';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
class SidePanel extends Component {
static propTypes = {
from: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
children: PropTypes.node,
width: PropTypes.string,
};
render() {
const fromSideClass =
this.props.from === 'right' ? 'from-right' : 'from-left';
const styles = this.props.width
? {
maxWidth: this.props.width,
marginRight: this.props.isOpen
? '0'
: Number.parseInt(this.props.width) * -1,
}
: {};
return (
<section
style={styles}
className={classNames('sidepanel', fromSideClass, {
'is-open': this.props.isOpen,
})}
>
{this.props.children}
</section>
);
}
}
export default SidePanel;
@@ -0,0 +1,133 @@
.simpleDialog {
position: fixed;
border: 0;
border-radius: 6px;
background-color: #151a1f;
}
.simpleDialog .header {
border-bottom-width: 3px;
border-bottom-style: solid;
border-bottom-color: #000;
padding: 19px 22px 17px;
position: relative;
}
.simpleDialog .header .title {
font-size: 20px;
font-weight: 600;
line-height: 24px;
padding-right: 40px;
color: #91b9cd;
margin: 0;
}
.simpleDialog .header .closeBtn {
height: 20px;
opacity: 1;
overflow: hidden;
padding: 2px;
text-align: center;
text-shadow: none;
width: 20px;
color: #91b9cd;
cursor: pointer;
position: absolute;
right: 21px;
top: 50%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
transition: color 0.3s ease;
}
.simpleDialog .header .closeIcon {
color: transparent;
display: block;
font-size: 0;
height: 100%;
line-height: 0;
overflow: hidden;
position: relative;
width: 100%;
}
.simpleDialog .header .closeIcon:after,
.simpleDialog .header .closeIcon:before {
content: ' ';
display: block;
height: 2px;
transition: background-color 0.3s ease;
width: 19px;
background-color: #91b9cd;
}
.simpleDialog .header .closeIcon:before {
left: 1px;
position: absolute;
top: 1px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
-webkit-transform-origin: 1px 50%;
transform-origin: 1px 50%;
}
.simpleDialog .header .closeIcon:after {
right: 1px;
position: absolute;
top: 1px;
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-transform-origin: calc(100% - 1px) 50%;
transform-origin: calc(100% - 1px) 50%;
}
.simpleDialog .content {
padding: 16px 22px 25px;
position: relative;
color: #fff;
}
.simpleDialog .content .simpleDialogSelect,
.simpleDialog .content .simpleDialogInput {
background-color: #fff;
color: #000;
border: 0;
border-radius: 2px;
font-size: 14px;
height: 30px;
width: 100%;
line-height: 16px;
padding: 8px 9px 6px;
margin-top: 10px;
display: block;
}
.simpleDialog .content .simpleDialogInputLabel {
font-size: 14px;
font-weight: 700;
line-height: 16px;
color: #fff;
}
.simpleDialog .footer {
padding: 15px;
text-align: right;
}
.simpleDialog .footer .btn {
transition: background-color 0.3s ease;
color: #000;
border: 0;
border-radius: 4px;
font-size: 15px;
font-weight: 400;
height: 37px;
line-height: 37px;
padding: 0 12px;
margin-bottom: 0;
margin-left: 5px;
}
.simpleDialog .footer .btn-confirm {
color: #fff;
background-color: #337ab7;
}
@@ -0,0 +1,56 @@
import { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import './SimpleDialog.css';
class SimpleDialog extends Component {
static defaultProps = {
componentStyle: {},
rootClass: '',
};
render() {
return (
<div
className={`simpleDialog ${this.props.rootClass}`}
ref={this.props.componentRef}
style={this.props.componentStyle}
>
<form onSubmit={this.props.onConfirm}>
<div className="header">
<span className="closeBtn" onClick={this.props.onClose}>
<span className="closeIcon">x</span>
</span>
<h4 className="title">{this.props.headerTitle}</h4>
</div>
<div className="content">{this.props.children}</div>
<div className="footer">
<button
type="button"
className="btn btn-cancel"
onClick={this.props.onClose}
>
Cancel
</button>
<button
type="button"
className="btn btn-confirm"
onClick={this.props.onConfirm}
>
Confirm
</button>
</div>
</form>
</div>
);
}
}
SimpleDialog.propTypes = {
headerTitle: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onConfirm: PropTypes.func.isRequired,
};
export default SimpleDialog;
@@ -0,0 +1,42 @@
import { Component } from "react";
import PropTypes from "prop-types";
import OHIF from "@ohif/core";
class StudyLoadingMonitor extends Component {
static propTypes = {
studies: PropTypes.array.isRequired,
setStudyLoadingProgress: PropTypes.func.isRequired,
clearStudyLoadingProgress: PropTypes.func.isRequired
};
componentDidMount() {
// TODO: This is pretty ugly. The thing is that the StudyLoadingListener
// needs to update the Redux store, but shouldn't know that it exists.
// I am therefore passing in some functions to update the store instead,
// but this should definitely be cleaned up somehow.
const options = {
_setProgressData: (progressId, progressData) => {
this.props.setStudyLoadingProgress(progressId, progressData);
},
_clearProgressById: progressId => {
this.props.clearStudyLoadingProgress(progressId);
}
};
const { StudyLoadingListener } = OHIF.classes;
this.studyLoadingListener = StudyLoadingListener.getInstance(options);
this.studyLoadingListener.clear();
this.studyLoadingListener.addStudies(this.props.studies);
}
render() {
return null;
}
componentWillUnmount() {
// Destroy stack loading listeners when we close the viewer
this.studyLoadingListener.clear();
}
}
export default StudyLoadingMonitor;
@@ -0,0 +1,29 @@
import { Component } from "react";
import PropTypes from "prop-types";
import OHIF from "@ohif/core";
const { StudyPrefetcher } = OHIF.classes;
class StudyPrefetcherComponent extends Component {
static propTypes = {
studies: PropTypes.array
};
componentDidMount() {
const { studies } = this.props;
this.studyPrefetcher = StudyPrefetcher.getInstance();
this.studyPrefetcher.setStudies(studies);
}
render() {
return null;
}
componentWillUnmount() {
// Stop prefetching when we close the viewer
this.studyPrefetcher.destroy();
}
}
export default StudyPrefetcherComponent;